АК
Александр Кузьминых21 листопада 2017 р. 03:10

Поліпшення використання ЦП у Qt 3D

Багато покращень було внесено до Qt 3D з моменту випуску Qt 5.6, нашої попередньої версії довгострокової підтримки (LTS). Інженери з KDAB і The Qt Company завзято працювали, щоб привнести нові функції в Qt 5.9 LTS, багато з яких перераховані в Що нового Qt 3D з Qt 5,9 у посту блогу Шон Хармера з KDAB. Незважаючи на те, що безліч можливостей ще в розробці (наприклад, Vulkan backend), основна увага в останніх випусках змістилася у бік продуктивності та стабільності. Ефективність значно покращилася порівняно з Qt 5.6, особливо для складних сцен та сцен з великою кількістю графів.

Сцени з багатьма вікнами перегляду зазвичай призводять до великої кількості кадрових графів, оскільки кожне вікно перегляду відповідає листовому вузлу. Якщо ви не знайомі з концепцією кадрового графа в Qt 3D і про те, наскільки це потужно, вам слід прочитати повідомлення з блогу Пола Лемарі на kdab.com . Нижче розташовано знімок екрана одного з наших внутрішніх тестів; досить проста (і барвиста) сцена з 28 вікнами перегляду:


Використання ЦП у цьому тесті значно скоротилося в Qt 5.9.2 у порівнянні з Qt 5.6.2, і компанія Qt працює разом з інженерами KDAB над рядом змін, які, як ми очікуємо, зменшать навантаження на ЦП ще більше в Qt 5.11:

Багато покращень продуктивності було перенесено на порт Qt 3D Studio, заснований на Qt 3D. Незважаючи на те, що середовище виконання заплановане на випуск наступного року, ми вже зараз додаємо покращення продуктивності до поточної серії Qt 5.9.x LTS. Ось деякі результати тестів наших внутрішніх прикладів Qt3D Studio:

Поліпшення продуктивності додані у багатьох частинах Qt 3D. Наприклад, ми додали підтримку ефективних форматів файлів, таких як glTF2. У цьому пості ми докладно розглянемо деякі зміни, які ми робимо зменшення використання ЦП, а пізнішому повідомленні ми обговоримо скорочення споживання пам'яті.

Поліпшення вирішувача залежностей завдань

Одне з покращень продуктивності, яке ми зробили – це вирішувач залежностей завдань Qt 3D. Qt 3D ділить роботу, яка має виконуватися кожен кадр на окремі, дрібніші завдання, які можуть виконуватися паралельно. Завдання є частиною гнучкої архітектури backend/frontend Qt 3D, яка відокремлює інтерфейс в основному потоці від бекенда, який складається з аспектів, що виконують обробку рендерингу, введення та анімацію (докладніше про це у документації Qt 3D Overview ).

Бекенд запускає завдання з різних аспектів пулу потоків, і кожне завдання може визначати залежність від інших завдань, які мають виконуватися перед ним. Ці залежності необхідно вирішувати ефективно, тому що завдання часто змінюються від одного кадру до іншого. Хоча це просто, коли кількість завдань невелика, це стає більш трудомістким для складних сцен з великими кадрами.

Профілюючи наші приклади за допомогою Callgrind , ми виявили вузькі місця продуктивності у певних частинах вирішувача залежностей завдань. Зокрема, великий QVector всіх залежностей буде змінюватися щоразу, коли завдання буде завершено, і відповідні залежності можна видалити зі списку. Це різко понизило продуктивність.

Ми розпочали роботу над рішенням, в якому ми повністю позбавимося QVector і зберігатимемо два списки пов'язаних із завданням: один список складається з того, від чого завдання залежить, і інший з того, що від цього завдання залежить.

class AspectTaskRunnable {
    // ... other definitions
    QVector m_dependencies;
    QVector m_dependers;
};

За допомогою цього рішення, коли завдання завершиться, воно може пройти через список m_dependers і видалити себе зі списку m_dependencies в кожному з m_dependers. Якщо список m_dependers порожній, це завдання можна запустити. Однак тепер у нас стало багато маленьких QVectors, які змінюються весь час. Хоча це краще, ніж зміна розміру одного великого QVector, це ще не оптимально.

Нарешті, ми зрозуміли, що оскільки залежності не можуть змінюватися під час виконання завдання, немає необхідності відстежувати, що залежить від завдання і від чого це завдання. Кожному завданню достатньо знати, які завдання залежать від нього і від якої кількості завдань залежить воно саме.

class AspectTaskRunnable {
    // ... other definitions
    int m_dependencyCount = 0;
    QVector<AspectTaskRunnable*> m_dependers;
};

Щоразу, коли завдання завершується, ми переглядаємо список завдань залежно від нього та віднімаємо в них кількість залежностей на одиницю. Останній код виглядає приблизно так (безсоромно спрощений для зручності читання):

void QThreadPooler::taskFinished(AspectTaskRunnable *job)
{
    const auto &dependers = job->m_dependers;
    for (auto &depender : dependers) {
        depender->m_dependencyCount--;
        if (depender->m_dependencyCount == 0) {
            m_threadPool.start(depender);
        }
    }
}

Впроваджуючи цю зміну, вирішувач залежностей завдань став незначним внеском у використанні ЦП, і ми змогли зосередитись на інших вузьких місцях.

Покращення продуктивності QThreadPool

Інші частини Qt також мають можливості оптимізації, які можна знайти в наших тестах. Наприклад, Qt 3D використовує QThreadPool від Qt Core для автоматичного керування завданнями та розподілу їх для різних потоків. Однак, як і в попередньому випадку, QThreadPool використовувався для зберігання завдань у QVector, який змінював свій розмір при кожному завершенні завдання. Це не велика проблема, коли йдеться про невелику кількість завдань, але це раптово стало вузьким місцем для складних 3D сцен Qt з великою кількістю завдань.

Ми вирішили змінити реалізацію QThreadPool, щоб використовувати більші «сторінки черги» та помістити вказівники на ці сторінки в QVector. На кожній сторінці ми відстежуємо індекс першого завдання у черзі та індекс останнього завдання у черзі:

class QueuePage {
    enum {
        MaxPageSize = 256;
    }; 

    // ... helper functions, etc.

    int m_firstIndex = 0;
    int m_lastIndex = -1;
    QRunnable *m_entries[MaxPageSize];
};

Тепер все, що нам потрібно зробити, - це збільшити перший індекс щоразу, коли завдання завершується, і збільшити останній індекс при додаванні завдання. Якщо немає місця на сторінці, ми виділяємо нову. Це проста та низькорівнева реалізація, але це ефективно.

Кешування результатів конкретних завдань

Потім ми виявили, що певні завдання виділяються дуже вимогливі до процесора. Деякі з цих завдань, такі як QMaterialParameterGathererJob, виконували багато роботи в кожному кадрі, навіть якщо результати попередніх кадрів були однаковими. Це була ясна можливість для кешування результатів підвищення продуктивності. По-перше, давайте подивимося, що робить QMaterialParameterGathererJob.

У Qt 3D ви можете перевизначити значення кожного параметра, визначеного QRenderPass, встановивши його на QTechnique, QEffect або QMaterial, який використовує цей прохід рендерингу. Кожен параметр, своєю чергою, використовується визначення однорідного значення у фінальній програмі шейдерів. Цей код показує приклад QML, де параметр "колір" встановлений на всіх рівнях:

Material {
    parameters: [
        Parameter { name: "color"; value: "red"}
    ]
    effect: Effect {
        parameters: [
            Parameter { name: "color"; value: "blue"}
        ]
        techniques: Technique {
              // ... graphics API filter, filter keys, etc.

              parameters: [
                  Parameter { name: "color"; value: "green"}
              ]
              renderPasses: RenderPass {
                  parameters: [
                      Parameter { name: "color"; value: "purple"}
                  ]
                  shaderProgram: ShaderProgram {
                      // vertex shader code, etc.

                      fragmentShaderCode: "
                          #version 130
                          uniform vec4 color;
                          out vec4 fragColor;
                          void main() {
                              fragColor = color;
                          }
                      "
                  }
              }
          }
    }
}

Щоб з'ясувати кінцеве значення параметра, що використовується в програмі шейдерів, QMaterialParameterGathererJob переглядає всі матеріали в сцені та знаходить відповідні ефекти, методи та проходи рендерингу. Потім, визначаючи пріоритети параметрів, заданих QMaterial, QEffect, QTechnique і QRenderPass, ми визначаємо остаточне значення параметра.В цьому випадку значення «червоне», оскільки параметри QMaterial мають найвищий пріоритет.

Збір всіх параметрів досить трудомісткий у великих сценах з багатьма матеріалами і виявився вузьким місцем для деяких прикладів Qt 3D Studio. Тому ми вирішили кешувати параметри, знайдені QMaterialParameterGathererJob, але швидко зрозуміли, що кеш завжди буде недійсним, якщо значення змінюються кожен кадр. Це звичайний випадок, якщо параметри анімовані. Натомість ми вирішили кешувати покажчики на об'єкти QParameter, а не їх значення. Значення потім зберігаються поза кешем і виймаються лише за необхідності. Кешування результатів призвело до величезного збільшення продуктивності в сценах з багатьма параметрами, оскільки нам потрібно було виконувати цю роботу лише за великих змін сцени, наприклад, додавання матеріалів.

Ми працювали з багатьма подібними випадками, де ми брали кілька наших великих прикладів, профілювали їх, виявляли вузькі місця у конкретних завданнях, і працювали, щоб знайти способи покращення продуктивності або кешування результатів. На щастя, система на основі завдань Qt 3D спрощує оптимізацію або кешування певних завдань незалежно, тому ви можете очікувати, що в майбутні випуски Qt 3D з'являться додаткові покращення.

Стаття написана: Svenn-Arne Dragly | Четвер, Листопад 16, 2017р.

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Стабільний хостинг, на якому розміщується соціальна мережа EVILEG. Для проектів на Django радимо VDS хостинг.

Вам це подобається? Поділіться в соціальних мережах!

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
AD

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:50бали,
  • Рейтинг балів-4
m
  • molni99
  • 26 жовтня 2024 р. 08:37

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80бали,
  • Рейтинг балів4
m
  • molni99
  • 26 жовтня 2024 р. 08:29

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:20бали,
  • Рейтинг балів-10
Останні коментарі
ИМ
Игорь Максимов22 листопада 2024 р. 19:51
Django - Підручник 017. Налаштуйте сторінку входу до Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 жовтня 2024 р. 21:37
Django - Урок 064. Як написати розширення для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 жовтня 2024 р. 15:19
Читалка файлів fb3 на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов05 жовтня 2024 р. 14:51
Django - Урок 064. Як написати розширення для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas505 липня 2024 р. 18:02
QML - Урок 016. База даних SQLite та робота з нею в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Тепер обговоріть на форумі
Evgenii Legotckoi
Evgenii Legotckoi24 червня 2024 р. 22:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 листопада 2024 р. 14:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject04 червня 2022 р. 10:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9Anonim25 жовтня 2024 р. 16:10
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

Слідкуйте за нами в соціальних мережах