简体   繁体   English

如何降低我的(非正统的)基于 Qt5/QML 的软件的 memory 使用?

[英]How to lower the memory usage of my (unorthodox) Qt5/QML-based software?

CONTEXT:语境:

I am developing a research prototype for a novel interaction concept and computational desktop environment, I currently call Sketchable Interaction (SI).我正在为新颖的交互概念和计算桌面环境开发一个研究原型,我目前称之为 Sketchable Interaction (SI)。 Currently, SI works only on Debian-based linuxes.目前,SI 仅适用于基于 Debian 的 linux。 In a nutshell, SI allows users to draw interactive regions on their desktop which carry effects.简而言之,SI 允许用户在桌面上绘制带有效果的交互区域。

Once two or more regions overlap, regions which's effects are compatible to each other apply their effects to each other as well.一旦两个或多个区域重叠,效果相互兼容的区域也将它们的效果相互应用。 In this way, graphical representations and data of files, etc. can be set, modified or deleted.通过这种方式,可以设置、修改或删除文件等的图形表示和数据。

Here are some screenshots to give a visual example:以下是一些屏幕截图,以提供视觉示例:

Showing Desktop Environment:显示桌面环境:

显示桌面环境

Drawing region (blue one) for Opening Folders/Files:打开文件夹/文件的绘图区域(蓝色一个):

打开文件夹/文件的绘图区域(蓝色一个)

Finished drawing of blue region:蓝色区域完成图:

蓝色区域完成图

Opended Desktop-Folder by overlapping it with the blue region and drew a Preview File region:通过与蓝色区域重叠打开桌面文件夹并绘制预览文件区域:

通过与蓝色区域重叠打开桌面文件夹并绘制预览文件区域

Moved image file (png) with the cat out of the folder:将带有猫的图像文件(png)移出文件夹:

将带有猫的图像文件(png)移出文件夹

Overlapped image file with the cat with the green region to show a preview of the image:与带有绿色区域的猫重叠的图像文件以显示图像的预览:

与带有绿色区域的猫重叠的图像文件以显示图像的预览

TECHNICAL STATUS QUO OF SI SI技术现状

SI is written in C++ with the current Qt5 and QML versions. SI 使用 C++ 编写,当前版本为 Qt5 和 QML 版本。 SI-Plugins which represent the effects you saw in the screenshots, are written in python3.7+, with the use of Boost.Python and do not use PyQt5.代表您在屏幕截图中看到的效果的 SI 插件是用 python3.7+ 编写的,使用 Boost.Python 并且不使用 PyQt5。

SI opens a MainWindow and every region drawing (everything you see in the screenshots is a region, including the mouse cursor) is a QWidget which is a borderless child of that MainWindow. SI 打开一个 MainWindow 并且每个区域绘图(您在屏幕截图中看到的所有内容都是一个区域,包括鼠标光标)是一个 QWidget,它是该 MainWindow 的无边框子级。

In order to do any styling eg display textures, like the folder icon, SI uses QML files, represented as QQuickWidgets which is a borderless child of that QWidget (I am aware of the stacking order problem, but we can ignore that for this question!)为了进行任何样式设置,例如显示纹理,如文件夹图标,SI 使用 QML 文件,表示为 QQuickWidgets,它是 QWidget 的无边界子项(我知道堆叠顺序问题,但我们可以忽略这个问题! )

I am able to change QML styling from within SI-Python-Plugins at runtime.我可以在运行时从 SI-Python-Plugins 中更改 QML 样式。 This internally uses QMetaObject to pass a QMap<qstr, QVariant> to a function in the container component.这在内部使用 QMetaObject 将 QMap<qstr, QVariant> 传递给容器组件中的 function。

QMetaObject::invokeMethod(reinterpret_cast<QObject *>(d_view->rootObject()), "updateData", QGenericReturnArgument(), Q_ARG(QVariant, region->data()));

I tested this with signals/slots as well, yet was unable to get it working as I intended, the above method does work as intended.我也使用信号/插槽对此进行了测试,但无法使其按预期工作,上述方法确实按预期工作。 Apparently, this is due to initializing exactly one QQmlEngine, instead of one per QQuickWidget.显然,这是由于只初始化了一个 QQmlEngine,而不是每个 QQuickWidget 一个。 This single QQmlEngine has CppOwnership.这个单一的 QQmlEngine 具有 CppOwnership。

engine = new QQmlEngine(this);
engine->setObjectOwnership(engine, QQmlEngine::CppOwnership);

THE PROBLEM问题

For testing purposes and performance benchmarking I intend to spawn thousands of regions: The following screenshots shows 1009 regions (1000 in the center).出于测试目的和性能基准测试,我打算生成数千个区域:以下屏幕截图显示了 1009 个区域(中心为 1000 个)。

This one is with all QML related code deactivated这个是与所有 QML 相关的代码停用

这个是所有与 QML 相关的代码都已停用

This yields, according to the tool htop, roughly 200 MB memory consumption.根据工具 htop,这会产生大约 200 MB memory 的消耗。

This one is with all QML related code activated这个是激活了所有 QML 相关代码

这个是激活了所有 QML 相关的代码

This yields roughly 4900 MB memory consumption.这产生大约 4900 MB memory 消耗。

The texture used in the yellow regions in the example with QML is a 64x64 px 32-bit RGBA image. QML 示例中黄色区域中使用的纹理是 64x64 像素 32 位 RGBA 图像。 This memory difference really strikes me as odd.这个 memory 差异真的让我觉得很奇怪。

The memory required for all images equals 1000 (number of regions) * 64 * 64 (number of pixels) * 4 (number of bytes if 4 channels with 8 bit) = 16,384,000 bytes which are ~16.5 MB.所有图像所需的 memory 等于 1000(区域数)* 64 * 64(像素数)* 4(如果 4 个通道为 8 位,则字节数)= 16,384,000 字节,约为 16.5 MB。 Of course there should be some further overhead per image, yet not 4.8 GB of overhead.当然,每个图像应该有一些额外的开销,但不是 4.8 GB 的开销。

I found out via other questions here or other sources that QML apparently needs a lot memory (some call it a memory hog).我通过此处的其他问题或其他来源发现 QML 显然需要很多 memory (有人称之为 memory 猪)。

Eg: QML memory usage on big grid例如: QML memory 在大电网上的使用

Yet, this high difference could stem from my unorthodox usage of Qt5 and QML.然而,这种巨大差异可能源于我对 Qt5 和 QML 的非正统用法。

QUESTION/S问题

Is there a way to lower this memory consumption, given the current state of the SI software?考虑到 SI 软件的当前 state,有没有办法降低这个 memory 消耗? Are their alternative approaches I did not come up with?他们的替代方法是我没有想出的吗? Is their a flag in Qt5/QML docs I missed which trivializes the problem?他们是我错过的 Qt5/QML 文档中的一个标志,它使问题变得微不足道吗?

Sorry for the lengthy post and thanks in advance for your help.很抱歉这篇冗长的帖子,并提前感谢您的帮助。

Edit: Typos, Addition of potential critical or suspected code as requested.编辑:错别字,根据要求添加潜在的关键或可疑代码。

Suspected Code: This is the constructor of a QWidget which contains a QQmlQuickWidget and represents a region可疑代码:这是一个 QWidget 的构造函数,其中包含一个 QQmlQuickWidget 并表示一个区域

RegionRepresentation::RegionRepresentation(QWidget *parent, QQmlEngine* engine, const std::shared_ptr<Region>& region):
    d_color(QColor(region->color().r, region->color().g, region->color().b, region->color().a)),
    d_qml_path(region->qml_path()),
    d_view(new QQuickWidget(engine, this)),
    d_type(region->type()),
    d_uuid(region->uuid()),
    d_name(region->name())
{
    if(!d_qml_path.empty())
        d_view->setSource(QUrl::fromLocalFile(QString(d_qml_path.c_str())));

    d_view->setGeometry(0, 0, region->aabb()[3].x - region->aabb()[0].x, region->aabb()[1].y - region->aabb()[0].y);
    d_view->setParent(this);
    d_view->setAttribute(Qt::WA_AlwaysStackOnTop);
    d_view->setAttribute(Qt::WA_NoSystemBackground);
    d_view->setClearColor(Qt::transparent);

    setParent(parent);
    setGeometry(region->aabb()[0].x, region->aabb()[0].y, region->aabb()[3].x - region->aabb()[0].x, region->aabb()[1].y - region->aabb()[0].y);

    if(region->effect()->has_data_changed())
        QMetaObject::invokeMethod(reinterpret_cast<QObject *>(d_view->rootObject()), "updateData", QGenericReturnArgument(), Q_ARG(QVariant, region->data()));

    d_fill.moveTo(region->contour()[0].x - region->aabb()[0].x, region->contour()[0].y - region->aabb()[0].y);

    std::for_each(region->contour().begin() + 1, region->contour().end(), [&](auto& point)
    {
        d_fill.lineTo(point.x - region->aabb()[0].x, point.y - region->aabb()[0].y);
    });

    show();
}

I can acess and set data in the QQmlQuickWidget from a plugin (python) in that way:我可以通过这种方式从插件(python)访问和设置 QQmlQuickWidget 中的数据:

self.set_QML_data(<key for QMap as str>, <value for key as QVariant>, <datatype constant>)

Every region has such a QMap and when it is updated in any way, this is called by RegionRepresentation:每个区域都有这样一个 QMap,当它以任何方式更新时,它由 RegionRepresentation 调用:

if(region->effect()->has_data_changed())
        QMetaObject::invokeMethod(reinterpret_cast<QObject *>(d_view->rootObject()), "updateData", QGenericReturnArgument(), Q_ARG(QVariant, region->data()));

Populating the QMap is done in this way:以这种方式填充 QMap:

  QVariant qv;

    switch (type)
    {
        case SI_DATA_TYPE_INT:
            d_data[QString(key.c_str())] = QVariant( bp::extract<int>(value))
            d_data_changed = true;
            break;

        case SI_DATA_TYPE_FLOAT:
            d_data[QString(key.c_str())] = QVariant(bp::extract<float>(value));
            d_data_changed = true;
            break;

        case SI_DATA_TYPE_STRING:
            d_data[QString(key.c_str())] = QVariant(QString(bp::extract<char*>(value)));
            d_data_changed = true;
            break;

        case SI_DATA_TYPE_BOOL:
            d_data[QString(key.c_str())] = QVariant(bp::extract<bool>(value));
            d_data_changed = true;
            break;

        case SI_DATA_TYPE_IMAGE_AS_BYTES:
            int img_width = bp::extract<int>(kwargs["width"]);
            int img_height = bp::extract<int>(kwargs["height"]);

            QImage img(img_width, img_height, QImage::Format::Format_RGBA8888);

            if(!value.is_none())
            {
                const bp::list& bytes = bp::list(value);

                int len = bp::len(bytes);
                uint8_t buf[len];

                for(int i = 0; i < len; ++i)
                    buf[i] = (uint8_t) bp::extract<int>(value[i]);

                img.fromData(buf, len, "PNG");

                d_data[QString(key.c_str())] = QVariant(img);
            }
            else
            {
                d_data[QString(key.c_str())] = QVariant(QImage());
            }
            d_data_changed = true;
            break;
    }

In QML this QMap is used that way:在 QML 中,这个 QMap 是这样使用的:

// data is QMap<QString, QVariant>
function updateData(data)
{
    // assume that data has key "width" assigned from python as shown in above code snippet
    qmlcomponent.width = data.width;
}

Here is the typical layout of QML files which are used for styling regions/effects:以下是用于设置区域/效果样式的 QML 文件的典型布局:

Item
{
    function updateData(data)
    {
        texture.width = data.icon_width;
        texture.height = data.icon_height;
        texture.source = data.img_path;
    }

    id: container
    visible: true

   Item {
       id: iconcontainer
       visible: true

       Image {
           id: texture
           anchors.left: parent.left
           anchors.top: parent.top

           visible: true
       }
    }
}

One of the central ideas is, that users of the system can create custom styling for regions and effect and address that styling dynamically at runtime via the associated plugins.中心思想之一是,系统用户可以为区域和效果创建自定义样式,并通过相关插件在运行时动态处理该样式。

While this is not an answer to your question, i think it might be a valuable info for you, and since i do not have enough reputation points to comment, i'm posting it as an answer.虽然这不是您问题的答案,但我认为这对您来说可能是有价值的信息,并且由于我没有足够的声誉点来发表评论,因此我将其发布为答案。

The memory issue you are seeing looks like a bug and not Qt/QML related.您看到的 memory 问题看起来像一个错误,与 Qt/QML 无关。 Below is a simple example of how to display a bunch of images in QML, and what to expect regarding memory consumption.下面是一个简单示例,说明如何在 QML 中显示一堆图像,以及对 memory 消耗的预期。

Code below displaying 1040 images with QML consumes under 30 MB of memory (with 64x64 px 32-bit RGBA source image, but it doesn't change much when using larger images).下面的代码使用 QML 显示 1040 张图像,消耗的 memory 不到 30 MB(使用 64x64 像素的 32 位 RGBA 源图像,但在使用更大的图像时变化不大)。 The displayed images are scaled down to 20x20 px, but even if you had enough of screen real estate to show them as 64x64 px and in worst case scenario if the memory consumption would increase linearly, it should be around 10x increase and nowhere near 4.8 GB.显示的图像缩小到 20x20 像素,但即使您有足够的屏幕空间将它们显示为 64x64 像素,并且在最坏的情况下,如果 memory 消耗会线性增加,它应该会增加大约 10 倍,并且远不及 4.8 GB . I hope this helps, and this is the code i've used:我希望这会有所帮助,这是我使用的代码:

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 1.3


Window {
    visible: true
    width: 1200
    height: 1000
    color: "#00000000"

    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 20
        Repeater {
            model: 26
            RowLayout {
                Repeater {
                    model: 40
                    Image {
                        Layout.preferredWidth: 20
                        Layout.preferredHeight: 20
                        source: "qrc:/tile.png"
                    }
                }
            }
        }
    }
}

And the memory consumption:和 memory 消耗: 在此处输入图像描述

First of all, I would suggest to not ask StackOverflow, but ask a profiler what is using our memory.首先,我建议不要问 StackOverflow,而是问分析器什么在使用我们的 memory。 Try heaptrack for instance.例如尝试堆跟踪。

However, I can tell you that the way you are using QQuickWidget is not as it is designed to use.但是,我可以告诉您,您使用 QQuickWidget 的方式与设计使用的方式不同。 It seems likely that this is where your memory is being used.这似乎是您的 memory 正在使用的地方。 I would suggest you change your design to use a single QQuickWidget or even use a QGraphicsArea instead of instantiating a new QQuickWidget per item.我建议您更改设计以使用单个 QQuickWidget 甚至使用 QGraphicsArea 而不是为每个项目实例化一个新的 QQuickWidget。

Then on the use of QMetaObject::invokeMethod: please don't.然后关于使用 QMetaObject::invokeMethod: 请不要。 It's an anti-pattern to try to poke into your QML.尝试插入您的 QML 是一种反模式。 Instead, expose whatever you want to get into QML as a property or a QAbstractItemModel, and bind to that from your QML.相反,将您想要进入 QML 的任何内容作为属性或 QAbstractItemModel 公开,并从您的 QML 绑定到它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM