中继器模型在复制项目时非常慢

Repeater model very slow when copying an item

提问人:Mohan S 提问时间:10/20/2023 最后编辑:Mohan S 更新时间:10/27/2023 访问量:139

问:

我们在项目中面临性能问题。我们正在使用Qt/QML和C++后端,我们正在开发网格按钮。如果我们要更改或更新数据模型,则中继器部分需要更多延迟来更新所有按钮,因此 UI 非常慢。根据按钮的数量,延迟会增加,如果更新了 64 个按钮,则意味着加载需要 6 秒。

当我们尝试更新数据或将对象数组复制到 Repeater 的模型容器(数据模型)中时,复制所有项目会消耗更多时间。例如,30 个对象消耗 2 秒。如果对象计数增加,则消耗的时间也会按比例增加。

根据控制台调试打印,执行此行后,执行下一个命令需要 2 秒。console.log("GRID update data model")

如何避免中继器的这种延迟?

  • 目标处理器名称:IMX537
  • Qt版本:Qt 5.15.2(GCC 9.3.0,64位)

注意:此延迟问题仅发生在目标设备中。在本地编译中没有看到这种延迟。

法典:

 function handleModelChanged() {
        delegateModel.model = areaViewModel;
        const newUiType = areaViewModel.uiType
        if (newUiType !== uiType || !modelDataIsEqual(delegateModel, dataModel) ) {
            var buttons = [] 

            for (var row = 0; row < delegateModel.model.rowCount(); row++) {
                var item = delegateModel.items.get(row).model;
                var button = dataModelItemToButton(item);
                
                buttons.push(button);
            }
            console.log("GRID clear data model")
            dataModel = []
            console.log("GRID change uiType " + uiType + " -> " + newUiType)
            uiType = newUiType
            console.log("GRID update data model")
            dataModel = buttons;
            console.log("GRID buttons changed uiType=" + uiType + " cls=" + areaViewModel.callClass);
             
        }  
    }
    Repeater 
    {
        id: areaRepeater
        model: dataModel

        delegate: {
                gridButton;
            }
        onItemAdded: {              
            if (index == areaRepeater.count - 1) {
                console.log("GRID repeater added " + areaRepeater.count + " buttons")
                updateItems()
                }
            }
        }
    }


function modelDataIsEqual(modelData, data) {
        if (!modelData || !data || modelData.model.rowCount() !== data.length)
            return false;
        for (var i = 0; i < modelData.model.rowCount(); ++i) {
            const item = modelData.items.get(i).model;
            const button = data[i];
            if (button.dataAreaShortName !== item.dataAreaShortName ||
                    button.dataAreaName !== item.dataAreaName ||
                    button.dataIsExitArea !== item.dataIsExitArea ||
                    button.dataAreaGridCols !== item.dataAreaGridCols ||
                    button.dataIsGroupedButton !== item.dataIsGroupedButton ||
                    button.dataAreaLocked !== item.dataAreaLocked ||
                    button.dataAreaIcon !== item.dataAreaIcon ||
                    button.dataIsDopArea !== item.dataIsDopArea ||
                    button.dataIsSideGroup !== item.dataIsSideGroup ||
                    dlaButtonStyle !== areaModel.combineBtnStyle ||
                    oddEvenBtnAppearance !== areaModel.getLockedButtonAppearance() ||
                    button.dataButtonAppearance !== item.dataButtonAppearance ||
                    button.dataCrowdedArea !== item.dataCrowdedArea ||
                    button.dataOverCrowdedArea !== item.dataOverCrowdedArea ||
                    button.dataPeopleIconArea  !== item.dataPeopleIconArea ||
                    button.dataSideStateIconArea !== item.dataSideStateIconArea)
            {
                return false;
            }
        }
        return true;
    }
function dataModelItemToButton(item) {
        if (!item)
            return null;
        return {
            dataAreaShortName: item.dataAreaShortName,
            dataAreaName: item.dataAreaName,
            dataIsExitArea: item.dataIsExitArea,
            dataAreaGridCols: item.dataAreaGridCols,
            dataIsGroupedButton: item.dataIsGroupedButton,
            dataAreaLocked: item.dataAreaLocked,
            dataAreaIcon: item.dataAreaIcon,
            dataIsDopArea: item.dataIsDopArea,
            dataIsSideGroup: item.dataIsSideGroup,
            dataButtonAppearance : item.dataButtonAppearance,
            dataCrowdedArea: item.dataCrowdedArea,
            dataOverCrowdedArea: item.dataOverCrowdedArea,
            dataPeopleIconArea : item.dataPeopleIconArea,
            dataSideStateIconArea : item.dataSideStateIconArea
        }
    }

正如我上面提到的,更多的按钮意味着延迟也会增加。就像 100 毫秒将一个按钮输入模型一样。在某处,提到在 Repeater 中使用 ListModel 来加载数据项。但我也试过了那个,似乎发生了同样的延迟。 我已经检查了我的目标设备的大部分延迟,导致仅执行此行 dataModel = button;我不能忽略这一行,因为,一旦复制到数据模型中,就只能看到所有按钮。还有其他方法可以减少这种延迟吗?

QT QML 中继器 数据 列表模型

评论

0赞 smr 10/20/2023
请添加一个工作示例,但正如 @nikita-shrama 也提到的那样,问题的根源是您的 for 循环。我建议使用 a 来实现更快的比较并提高时间复杂度。HashTable
0赞 JarMan 10/20/2023
阅读文档的这一部分。更改模型时,中继器必须重新创建委托的所有实例。如果可以将代码更改为 ListView 或 GridView,则只需实例化可见的委托。
0赞 GrecKo 10/21/2023
使用正确的QAbstractListModel,不要在QML中执行所有这些逻辑。

答:

0赞 Stephen Quan 10/27/2023 #1

您似乎正在使用 Javascript 数组作为模型。

    delegateModel.model = areaViewModel;

这意味着每次进行更改时,都必须将模型替换为包含更改的新 Javascript 数组。但是,必须重新绘制所有记录,即使是以前见过的记录。这种模式的复杂度是 O(N(N+1)/2) 或大致为 O(N^2)。从下表可以看出性能:Repeater

大小 总绘制时间
1 1
2 1 + 2
3 1 + 2 + 3
N N ( N + 1 ) / 2

这很糟糕,因为它是指数级延迟。

在某些平台上,您可能没有注意到这一点,因为 N 很低,或者您的处理器速度足够快。因此,你要做的是将其部署为该平台可接受的解决方案。但是,一段时间后,当该代码成为生产代码时,您的用户将增加 N 超出您的测试限制,并且您的用户会将其作为您需要解决的问题提出。

首选的方法是重构并用 (1) , (2) 或 (3) 新 Qt6.4+ 语法替换。这些模型运行良好,因为它们实现了更改检测,因此只需刷新较新的记录,而不必刷新所有记录。ListModelQAbstractListModellistRepeater

    // e.g.
    Repeater {
        model: ListModel {
            id: areaViewModel
        }
    }

简而言之,不要使用 Javascript 数组作为 .Repeater

评论

0赞 Mohan S 11/9/2023
感谢您的回答,正如您所说,我们相同的项目代码运行良好,这意味着在我们的高端处理器 (IMX 8) 和目标设备中使用的 Qt 6.4 中看不到此延迟问题。另一个问题,何时升级 Qt 版本 6.4,我们是否应该在不更改代码的情况下减少我的 IMX 537 处理器目标设备中的这种延迟。