Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: QgraphicsView + QgraphicsScene - требует очень много памяти
Форум на CrossPlatform.RU > Библиотеки > Qt > Qt Система рисования. Печать
Lokis
Добрый день. Потребовалось на qt написать небольшое приложение для отображения графов. Пример нашёл быстро, изменил под себя. Узлы и связи рисуются и работают. Но есть ОГРОМНАЯ проблема. В специфических случаях библиотека начинает потреблять огромное количество оперативной памяти(гигабайты). Причём это 100% не утечка. Qt требует память для своих внутренних нужд. Подозреваю, что память идёт на просчёт пересечений. Соответственно вопрос - как уменьшить количество памяти, которое требуется qt (отключить лишние расчёты). Ниже код и картинка случая, в котором память будет потребляться в больших количествах, если увеличивать число узлов и связей. Детализация узлов и связей в зависимости от масштабов проведена.
В архиве файлы проекта (узлы, связи, сцена с отображением. Класс заполнитель сцены и main). Также прикреплена картинка, которая иллюстрирует в каком случае будет расти память при увеличении чила узлов.

Под катом метод в котором создаётся тестовый граф. Это в файле GraphWidget.cpp
Раскрывающийся текст

void GraphWidget::DoTestGraph()
{
    QSharedPointer<GraphStruct> testGraph;
    testGraph = QSharedPointer<GraphStruct>(new GraphStruct);
    for (size_t ind = 0; ind < 10; ++ind)  // Создаём узлы (горизонтальное колитчество узлов)
    {
        for (size_t jnd = 0; jnd < 10; ++jnd) // Вертикальное количество узлов
        {    
            GraphStruct::GraphVertex testvertex(QPoint(50*ind, 50*jnd), "node", "node_descr");
            testGraph->vertexList.push_back(testvertex);
        }
    }
    for (size_t ind = 0; ind < testGraph->vertexList.size() - 3; ++ind) // Создаём связи
    {
        GraphStruct::GraphEdge testedge("link", "link_desc", 0, ind + 1); // Завязываем все связи на 1 узел. Достаточено 3600 - улов и связей чтобы увидеть как начинает расти выделение памяти
        testGraph->edgeList.push_back(testedge);
    }
    graphView->CreateGraph(testGraph);
}



А вот тут начинает кушаться память (файл main)

Раскрывающийся текст

#include "..\..\QueryClient\NewGUI\QT_graph\GraphWidget.h"
#include <QtWidgets/QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    ST::User::GraphWidget gw;
    gw.show();

    return a.exec(); // Тут происходит выделение огромного количества памяти
}

Алексей1153
на 99% - проблема не в Qt, а в кривом алгоритме/классах. Всё, что на скрине показано, для сцены - это фигня

файл pro не вижу, зато вижу stdafx.h - в студии делаешь ? Файл проекта какой ?

и главные вопросы -
1) как определил, что жрётся память ?
2) это всё как-то сказывается на производительности машины или программы ?
ViGOur
Все дело как сказал Алексей1153 - в неправильном алгоритме, смотри, что у тебя происходит.
Как я понимаю, метод DoTestGraph дергатеся каждый раз при перерисовке экрана. Каждый раз при рисовании у тебя создаются временные переменные графов, причем как я понимаю в большом количестве и для них еще выделяется память в куче (которая к тому же освобождается, когда есть возможность, а не сразу после использования) .

Что тебе мешает создать их сразу, и менять их расположение по мере необходимости, и/или так как у тебя большие ообъемы, то как вариант создать Патерн Flyweight для рисования и работы твоими с графами.
Lokis
Цитата(Алексей1153 @ 15.10.2013, 11:08) *
на 99% - проблема не в Qt, а в кривом алгоритме/классах. Всё, что на скрине показано, для сцены - это фигня

файл pro не вижу, зато вижу stdafx.h - в студии делаешь ? Файл проекта какой ?

и главные вопросы -
1) как определил, что жрётся память ?
2) это всё как-то сказывается на производительности машины или программы ?



То что на скрине конечно фигня. Там всего пара десятков узлов. Проблемы начинаются при тысячах. Как я писал - от 3600 тысяч уже проблемы начинаются. Проблема именно в qt. Проект генерится в 2012 студии. Все файлы необходимые для проекта в архиве, но у всех разные среды сборки, так что удобнее выложить файлы, а не проект, который является частью другого проекта. Если проити отладчиков в файле main, то до этой строчки
return a.exec(); // Тут происходит выделение огромного количества памяти

памяти выделяется совсем чуть чуть, а как отдаю управление qt - начинают выделяться гигабайты.
Диспетчер задач также указывает на данную программу. В коде DoTestGraph() - показано как создать большое количество узлов.

Цитата(ViGOur @ 15.10.2013, 11:23) *
Все дело как сказал Алексей1153 - в неправильном алгоритме, смотри, что у тебя происходит.
Как я понимаю, метод DoTestGraph дергатеся каждый раз при перерисовке экрана. Каждый раз при рисовании у тебя создаются временные переменные графов, причем как я понимаю в большом количестве и для них еще выделяется память в куче (которая к тому же освобождается, когда есть возможность, а не сразу после использования) .

Что тебе мешает создать их сразу, и менять их расположение по мере необходимости, и/или так как у тебя большие ообъемы, то как вариант создать Патерн Flyweight для рисования и работы твоими с графами.


Алгоритм правильный. DoTestGraph - вызывается 1 раз. На сцену добавляются элементы - всё отлично, память не кушается. Я могу добавит тысячи элементов, без пересений - ПАМЯТЬ НЕ БУДЕТ ТРАТИТСЯ! Если я добавлю те же эле енты, но связи будут пересекть узлы - выделяются гигабайты паямти.
Алексей1153
Lokis, для студийного проекта (VS>6) файл *.vcproj прикладывают :) Там может быть прописана куча настроек, которые будут нужны любой среде VS



попробовал в QtCreator создать проект. При компиляции слишком много костылей, не стал вникать )

кстати, вместо nullptr удобнее писать 0
Lokis
Цитата(Алексей1153 @ 15.10.2013, 12:01) *
Lokis, для студийного проекта (VS>6) файл *.vcproj прикладывают :) Там может быть прописана куча настроек, которые будут нужны любой среде VS



попробовал в QtCreator создать проект. При компиляции слишком много костылей, не стал вникать )

кстати, вместо nullptr удобнее писать 0



Да в общем нашли подобие решения, котрое подходит. У связей метод QPainterPath shape() const {QPainterPath path; return path;}; возвращает пустоту и дикое количество памяти сразу перестало требоваться. Конечно перерисовка теперь происходит только при специфических событий для связей. например, выделить другое окно, а потмо вернуться обратно. Но это решить проще - найти сигналы смены фокуса например. Так что как я и утвержал - qt на расчёт пересечений требовала бешенное количество памяти.
ViGOur
Проект, который ты выложил без пересечений, дай код с пересечением, чтобы самому не вникать и не писать...
Lokis
Цитата(ViGOur @ 15.10.2013, 12:11) *
Проект, который ты выложил без пересечений, дай код с пересечением, чтобы самому не вникать и не писать...


Замените эту функцию и будет 10000 узлов с пересечением.
void GraphWidget::DoTestGraph()
{
    QSharedPointer<GraphStruct> testGraph;
    testGraph = QSharedPointer<GraphStruct>(new GraphStruct);
    for (size_t ind = 0; ind < 100; ++ind)  // Создаём узлы (горизонтальное колитчество узлов)
    {
        for (size_t jnd = 0; jnd < 100; ++jnd) // Вертикальное количество узлов
        {    
            GraphStruct::GraphVertex testvertex(QPoint(50*ind, 50*jnd), "node", "node_descr");
            testGraph->vertexList.push_back(testvertex);
        }
    }
    for (size_t ind = 0; ind < testGraph->vertexList.size() - 3; ++ind) // Создаём связи
    {
        GraphStruct::GraphEdge testedge("link", "link_desc", 0, ind + 1); // Завязываем все связи на 1 узел. Достаточено 3600 - улов и связей чтобы увидеть как начинает расти выделение памяти (пересечения тут)
        testGraph->edgeList.push_back(testedge);
    }
    graphView->CreateGraph(testGraph);
}
ViGOur
Цитата(Lokis @ 15.10.2013, 12:06) *
Так что как я и утвержал - qt на расчёт пересечений требовала бешенное количество памяти.
Мда, есть такое дело. Причем жрет и правда немеренно в случае пересечений!
Хотя ничего удивительного:
Цитата
Функция boundingRect() имеет много различных целей. QGraphicsScene основывает свой индекс элементов на boundingRect(), а QGraphicsView использует их обе ( boundingRect и paint ) для отбрасывания невидимых элементов и для определения площади, которая должна быть переделана при отрисовке перекрывающихся элементов. В дополнение механизм определения коллизий QGraphicsItem использует boundingRect() для предоставления эффективного обрезания. Более точный алгоритм столкновений в collidesWithItem() использует функцию shape(), которая возвращает точный контур фигуры элемента в виде QPainterPath.
Iron Bug
Цитата(Алексей1153 @ 15.10.2013, 14:01) *
кстати, вместо nullptr удобнее писать 0

а вот такие советы давать не надо. nullptr - это именно указатель. и принципиальное отличие от нуля у него есть. начиная с компиляторов, поддерживающих C++09 для указателей лучше использовать nullptr.

а по проблеме жрачки памяти, я подозреваю, что отчасти она может быть из-за puch_back. все динамические структуры имеют резервирование памяти и push_back обычно позволяет добавлять столько элементов, сколько зарезервировано. а потом начинает выделять новые буферы и копировать всю память. и старые куски памяти реально освобождаются далеко не сразу.
про реализацию контейнеров в Qt читаем тут.
для резервирования памяти под данные (чтобы не копировать) у QVector есть метод reserve. а чтобы копирование было динамическим, в Qt для частных классов (см. указатель на статью выше) есть макрос Q_DECLARE_TYPEINFO() с типом Q_MOVABLE_TYPE.
ViGOur
Хотя из выше описанного выходит, что даже если collidesWithItem возвращает false, всеравно требуется возврат точной фигуры...
Алексей1153
Iron Bug, ноль - он всегда ноль, как не обзови :) И убеждать не надо
Lokis
Цитата(ViGOur @ 15.10.2013, 13:08) *
Хотя из выше описанного выходит, что даже если collidesWithItem возвращает false, всеравно требуется возврат точной фигуры...


Именно так. С collidesWithItem и collidesWithPath - тоже кодую. Даёт прирост в скорости, но количество памяти не уменьшало. Возможно не предполагали создатели такого случая, который оказался нужен...
Алексей1153
Lokis, значит, у тебя именно такой случай, когда эти пересечения нужно считать самому :D

почему бы не разместить блоки на переднем слое, а связи - на заднем. И для связей отдельно у себя посчитать пересечения и нарисовать элементы-узлы ?
Lokis
Цитата(Алексей1153 @ 15.10.2013, 13:40) *
Lokis, значит, у тебя именно такой случай, когда эти пересечения нужно считать самому :D


Использвание компонет qt для отрисовки объектов на сцене терят при этом всякий смысл.
Алексей1153
Lokis, да я бы не сказал, что теряет
Iron Bug
Цитата(Алексей1153 @ 15.10.2013, 15:09) *
ноль - он всегда ноль, как не обзови :) И убеждать не надо

никто не убеждает. это факт из стандартов. nullptr - типизированная константа, а не числовой ноль. и разница есть.
не надо переть против комитета по стандартизации, надо читать новые правила и соответствовать стандартам ;)
Алексей1153
Iron Bug, да я знаю и понимаю разницу. Но я ленивый :)
ViGOur
В C++11 для обнуления указателей появилось специальное ключевое слово nullptr. В более ранних стандартах, официально использовалась запись:
Допустим, у нас есть некоторый контейнер состоящий из указателей. И мы хотим его обнулить. Вспоминая чудесные алгоритмы из STL, мы не раздумывая применим std::fill.
std::vector<Foo*> foos;
// ...
std::fill(foos.begin(), foos.end(), 0);

На первый взгляд — все просто отлично! Но ошибка есть, и она такая же как и в предыдущем варианте. std::fill является шаблоном, и увидев 0, шаблон примет его за int и, конечно же, из-за несоответствия типов мы получим очень страшное сообщение об ошибке от компилятора. Выход — кастовать 0 к указателю (static_cast<const Foo*>(0)), что уж явно не повышает читабельность.

Именно поэтому, было принято новое ключевое слово, и имя ему nullptr. Используя это ключевое слово, мы избавимся от вышеописанной проблемы, так как nullptr имеет свой собственный тип — std::nullptr_t — и компилятор не спутает его ни с чем другим.

Lokis,
Цитата(Lokis @ 15.10.2013, 13:43) *
Использвание компонет qt для отрисовки объектов на сцене терят при этом всякий смысл.
Согласен.
Попробую набросать дургой пример, по правилам сцены и вида, посмотрим как там себя поведет...
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Форум IP.Board © 2001-2024 IPS, Inc.