Под мерой производительности UI будем понимать количество откликов на действия пользователя в единицу времени. А под откликом — запрашиваемую пользователем реакцию приложения.

Малым временем отклика можно объяснить ряд предпочтений пользователя:

1. Предпочтение аналоговых интерфейсов цифровым (когада возникает задержка на обработке цифрового ввода);
2. На заре Windows, — предпочтения пользователей работать с DOS программами в «текстовом режиме», а с не GUI аналогами в Windows (время отклика в текстовом режиме тогда было заметно меньше на сходной платформе);
3. Предпочтение реальных игровых консолей их эмуляторам (эмуляторы часто имеют время отклика отличное от времени отклика оригинальных консолей);
4. Предпочтение пользователей iOS и Android относительно WinCE и Symbian (среди прочего, например в iOS ставилась цель быстрого отклика и поддержки 60 FPS, Android хотя и не ставил таких целей был заметно отзывчивее WinCE и Symbian);
5. В автомобилях — неоднозначное отношение пользователей к автоматическим коробками передач, электронной педали газа и некоторым другим системам вносящим задержку между управляющим воздействием и реакцией на него (это относится к наименее продвинутым версиям этих решений).

Большое время отклика является по сути «обратной связью с запаздыванием», про которую более подробно можно прочитать тут: «Обратная связь с запаздыванием в кране с горячей водой, марсоходе и демографической пирамиде».

Сравнение производительности


Задержка реакции на ввод пользователя в цифровых системах естественна и неизбежна. Однако в UI для восприятия значение имеют только те задержки, которые пользователь в состоянии ощутить. Например, в восприятии визуальной информации среднестатистический пользователь не способен различить задержки менее 1/60 секунды, поэтому дальнейшее уменьшение времени визуального отклика вряд-ли будет оправдано.

Рассмотрим отзывчивость пользовательского интерфейса библиотек WPF, Qt, WinForms и FLTK. Трудоемко измерить отзывчивость всех контролов этих библиотек, и сложно в каждом случае измерить интервал между вводом пользователя и реакцией контрола на этот ввод. Поэтому, немного упростим задачу оценки. Тестировать будем один, но сложный, и в целом показательный контрол, присутствующий во всех библиотеках — DataGrid. Отзывчивость будем мерить по FPS в отклике на скроллинг содержимого этого контрола. Для того чтобы избежать подсчета неполных кадров, будем использовать двойную буферизацию.

Во время скроллинга грида, мы так или иначе мы проверим основные механизмы библиотеки отвечающие за реакцию на ввод, рендеринг контролов, текста, обработку визуального дерева контролов, и отчасти композицию. Это даст нам возможность оценить потенциал библиотеки для работы с нагруженным UI.

Для каждой библиотеки я подготовил по тестовому приложению, заполняющих грид данными одинакового вида. Эти данные — 5000 строк по 20 колонок с некой произвольной информацией (считаю такое количество строк и столбцов близким к реальным максимальным потребностям отображения сложных объектов). Я не оптимизировал заполнение грида, поэтому не стоит придавать значения тому, что для некоторых библиотек он заполняется медленно. Производительность будем мерить уже после того как грид заполнен.

Производительность я проверял только под Windows (хотя библиотеки, за исключением WPF кросплатформенны), для проверки был написан счетчик FPS (только для Windows и 32 битной глубины цвета) определяющий изменения кадра по верхней части основного экрана. Погрешность счетчика может быть около 1 кадра.

Методика измерения производительности:
1. Запускаем счетчик FPSCounter.exe.
2. Запускаем одно из тестовых приложений FltkGrid.exe, FormsGrid.exe, QtGrid.exe или WpfDatagridTest.exe и разворачиваем его на весь основной экран (это необходимо т.к. детектируется только изменение верхней части кадра на основном экране)
3. Двигаем бегунок вертикального скроллера грида вверх и вниз до упора, во время движения бегунка смотрим значение FPS в верхнем левом углу экрана или в окне счетчика. (для получения максимальных значений FPS двигать бегунок надо быстро, иначе мы упремся в собственную «производительность», а не в производительность UI)

Измерения FPS я производил на нескольких, оказавшихся под рукой платформах, почти все платформы на Windows 7 x64, другие приложения на время измерений закрывал.

Архив с FPSCounter.exe, FltkGrid.exe, FormsGrid.exe, QtGrid.exe и WpfDatagridTest.exe, а так же Qt библиотеками, необходимыми для тестового приложения на Qt (16 Mb)

Архив с FPSCounter.exe, FltkGrid.exe, FormsGrid.exe ( т.е. без Qt, зато очень маленький)

Приложения скомпилированы под х64 платформу. Кроме этого для запуска может потребоваться msvs runtime 2010

Результаты


Ниже приведена таблица с перечнем платформ, на которых запускались измерения и их результатами. В таблице также указана оценка однопоточной производительности платформы (Single Thread Performance) в соответствии с www.cpubenchmark.net.



В дополнение к таблице, хочу добавить что приложения Оffice и например браузер Chrome (на «хороших» страницах) показывают FPS примерно равный FLTK или чуть меньше.

Для более наглядной иллюстрации добавлю график зависимости FPS от оценки Single Thread производительности. График построен на основе данных приведенных в таблице.



График стоит немного прокомментировать. На платформе с оценкой 1000 использовалось низкое вертикальное разрешение экрана, что сильно уменьшило число видимых ячеек, тем самым существенно повысив FPS вертикального скроллирования. Хочу также добавить, что для компиляции FLTK примеров был отключен ряд оптимизаций, поэтому вполне возможно включив их можно несколько увеличить оценки примера FLTK на всех платформах (по этой же причине во время старта примера FLTK появляется окно консоли).

В целом же зависимость FPS от однопоточной производительности процессора достаточно линейна. Есть предположение что многопоточная производительность пока мало применима к UI библиотекам (например, превосходство i7 в многопоточной производительности мало влияет на FPS). Так же измерения показали слабую зависимость FPS в нашем тесте от видеокарты (зависимость безусловно есть, но похоже в данных тестах видеокарта не является узким местом) Еще одной интересной деталью явилось ограничение 30 FPS на ряде платформ. Не уверен связано ли это c драйвером видеокарты или какими-то ее настройками, однако в ряде случаев не удавалось получить более 30 FPS…

Исключительно на основании данных измерений я не могу рекомендовать FLTK, как универсальное средство повышения отзывчивости вашего UI, тем более что писать UI на FLTK весьма трудоемко (на написание FLTK примера у меня ушло около 20ти минут, хотя остальные примеры были написаны заметно быстрее). Однако результаты измерений заставляют задуматься о том, что по многие библиотеки не полностью раскрывают потенциал железа на котором выполняются, делая приложения менее привлекательными для пользователя(с точки зрения отзывчивости по крайней мере).

Буду рад, если подобным образом кто-то протестирует и другие UI библиотеки, предложит вариант оптимизации производительности тестовых примеров (для рассматриваемого случая скроллинга) или же просто расскажет о FPS в тестовых примерах на своей конфигурации.

Прилагаю исходники счетчика и тестов.

Как же получить высокий FPS?


Как видно из теста, стабильные 60 FPS, в случае тяжелого UI мы получим разве что на дорогом железе и наиболее затратной в разработке UI библиотеке (то есть дорогим будет и разработка и железо и потребление этого железа), наверное иногда это того стоит, но пока это скорее исключение. Однако, если не вдаваться в крайности и поставить себе целью получить хотя бы 20 FPS в нагруженных информацией интерфейсах. Чего нам это будет стоить?

Для рассмотренных библиотек, вариантов, видимо, не так и много:
1. FLTK + почти самое дешевое железо. На разработку UI мы потратим заметно больше времени, но в текущих ценах сможем сэкономить ~100-200$ на железе на рабочее место пользователя.
2. Qt + среднее железо. На железе сэкономить особо не получится, но зато разработка UI будет дешевле чем в случае FLTK. Вероятно в ряде случаев вариант будет оптимальным.
3. WPF + дорогое железо т.е. дополнительные 200-300$ на рабочее место. Ведь если на i7-3770 мы получаем только 12 кадров, то нужно как минимум железо в полтора раза мощнее. Вероятно i7-5930K или возможно i7-4790K в паре с хорошей видеокартой справятся с задачей 20 FPS. Однако вряд ли это будет эффективное решение, да и справятся ли… к сожалению нет такого железа под рукой чтобы проверить, но если экстраполировать однопоточную производительность то ее оценка должна быть свыше 3000, для получения 20 FPS при 1280х1024… такого железа просто не существует, по крайней мере тут...
4. Облегчать UI, до тех пор пока не уложимся в 20 FPS. Например, если используя WPF, вместо 20ти колонок в гриде оставить только 6, то на i7-3770 мы получим стабильные 20 FPS, если же оставить всего 3-4 колонки, то получим 20 FPS и на бюджетном железе. Уменьшение размера самого грида также должно дать положительный эффект (правда на разных библиотеках он разный, и как ни странно для случая WPF эффект наименее выражен). Приемлемы ли такие решения? Применимы далеко не везде, но все-таки покрывают ряд задач, не фокусирующихся на представлении данных.

P.S.: Идея сравнить производительность гридов появилась после того, как я столкнулся с низкой производительностью грида WPF. В комментариях к моей предыдущей статье «Выбор между C++ и C#» я, в частности, разбирал эту проблему.
Далее мне стало интересно, как с задачей отображения грида справляются альтернативные библиотеки, так появились тестовые приложения и результаты изложенные в этой статье.
Какой минимальный FPS вы считаете комфортным для работы с приложением

Проголосовало 322 человека. Воздержалось 168 человек.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Комментарии (39)


  1. kekekeks
    02.08.2015 18:54
    +3

    Штатный WPF-ный грид умеет кучу совершенно ненужных в типовом приложении вещей, оттого не быстр. Рекомендую прогнать тесты с использованием:
    fastwpfgrid.codeplex.com
    tableview.codeplex.com
    xceed.com/Grid_WPF_Intro.html (бесплатную версию брать тут)


    1. kekekeks
      02.08.2015 19:16
      +1

      А, да, к последнему надо дописать:

                   <tk:DataGrid x:Name="dataGrid" 
                   ItemsSource="{Binding Path=Bookings}" 
                   AutoGenerateColumns="False" 
                   EnableRowVirtualization="True" 
                   EnableColumnVirtualization="True"
                   VirtualizingStackPanel.IsVirtualizing="True">
      


      1. X_OSL
        02.08.2015 19:26

        Спасибо.
        Я попробовал грид из Xceed. Он показал в моем тесте в 3 раза больший FPS чем стандартный грид WPF.

        Правда с ним есть проблема: во время скроллинга он не отрисовывает свое содержимое, и получается что скролируется пустой грид.

        Как можно заставить этот грид отображать содержимое во время скроллинга? (иначе сложно адекватно сравнить его с остальными)


        1. kekekeks
          02.08.2015 19:31

          Не оно?


          1. X_OSL
            02.08.2015 19:40

            Не помогает. Всеравно не отрисовывает содержимое до остановки скроллинга…


        1. kekekeks
          02.08.2015 19:35

          И вообще, если хотите быстро-быстро, то берите fastwpfgrid.codeplex.com, он не создаёт Visual Tree, а рисует сам. Насколько я понимаю, примерно так же делает FTLK.


          1. X_OSL
            02.08.2015 20:15

            К сожалению сейчас не могу проверить. Грид не совместим с vs2010 и .net framework 4.0
            (даже не получается перестроить грид из исходников без более свежей студии и 4.5.1, а на этой машине не могу его сносить 4.0 так на ней иногда как требуется строить приложения совместимые с XP, для которой нет 4.5.1)

            Попробую проверить на более свежем framework позже. По исходникам могу сказать что в реализации грида много unmanaged кода, и приложеный пример (который в бинарном виде запускается даже на 4.0) работает в два раза быстрее стандартного грида WPF, однако же в примере 100 колонок и 1000 строк, что отличается от тестовой конфигурации.

            Кроме того опасения вызывает то, что контрол fastgrid достаточно «молодой», появился только в Mar 24, 2015, есть риск столкнуться с багами при использованиии в продакшине…

            Я попробую его чуть позже, когда будет доступна ситема, где нет необходимости в 4.0 фреймворке.


            1. kekekeks
              02.08.2015 20:19

              много unmanaged кода
              Где? Всё на шарпе и компилится в msil.


              1. X_OSL
                02.08.2015 20:22
                +1

                Да, я не правильно выразился. не unmanaged, а unsafe в проекте WriteableBitmapEx.


                1. kekekeks
                  02.08.2015 20:25
                  +1

                  Это достаточно древняя и широко используемая библиотека. 35К загрузок с NuGet.


                  1. X_OSL
                    02.08.2015 20:30
                    +1

                    Конкретно этой библиотеке похоже уже 3 года, и вполне возможно, что она достаточно «провереная». Хотя конечно код вроде этого, немного пугает с точки зрения использования в С#
                    // Win32 memory copy function
                    //[DllImport(«ntdll.dll»)]
                    [DllImport(«msvcrt.dll», EntryPoint = «memcpy», CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
                    public static extern unsafe void* memcpy(
                    void* dst,
                    void* src,
                    int count);

                    В любом случае грид заметно быстрее стандартного. Надо его проверить с тестовыми данными.


                    1. X_OSL
                      03.08.2015 09:08

                      Попробовал использовать FastGridControl в своем примере вместо стандартного грида. Я просто подставил другой грид в свой тест и грид на системе с 4.5.1 все заработало.

                      Производительность была ровно такой-же как и и обычного грида WPF. (да похоже это и был обычный WPF грид)
                      Видимо в приложенных примерах с гридом делается еще что-то, причем со стороны приложения, что его использует.

                      В примере, которые лежит в репозитории видно что еще создаются дополнительные модели и некий свой TextRenderPanel контрол (очень странно почему все это делается не внутри грида, а снаружи)

                      Похоже повозиться тут надо не меньше чем с FLTK. Но все еще не теряю надежды запустить данный грид, так, чтобы он работал быстро, однако похоже для этого надо будет потратить немало времени.


                      1. X_OSL
                        03.08.2015 13:04

                        Я наконец таки его запустил так, чтобы он работал быстро.

                        Возиться пришлось не меньше чем с FLTK: создать модель представления и использовать кастомный рендер текста и фиксированный размер грида. Кроме того пришлось отказаться от биндингов данных.

                        Но все же fastwpfgrid показал производительность примерно равную производительности грида FLTK.

                        Конечно это очень хороший результат, хотя грид и не дешев в использовании.
                        Жалко что грид пока слишком молодой (4 месяца и 231 скачка) для использования в продакшине.
                        Еще более жалко что стандартный грид WPF не дает и половины производительности этого грида…


                        1. HomoLuden
                          05.08.2015 15:17

                          Попробуйте из описанных фиксов оставить только фиксированные размеры грида. Остальное — семечки, по сравнению с UpdateLayout().
                          Можеие в dotTrace поглядеть на чем просадка производительности в случае с DataGrid. Там при ресайзе ужасно много UpdateLayout вызывается, хотя визуально разницы может быть почти не видно.


                          1. X_OSL
                            06.08.2015 08:48

                            Ранее я пробовал ставить фиксированные размеры WPF гридам. Эффекта от этого не было. Грид вызывает измерение размера ячеек даже тогда, когда причины для измерений нет… то есть нет ни ресайза, ни изменения колонок — только скроллинг.


                            1. HomoLuden
                              06.08.2015 10:20

                              Под фиксированным размером грида я подразумевал прежде всего ширины колонок. Специально писал плагин к нему, который рулит этими ширинами. Те же резиновые колонки получались, но обновлялись ширины с нужной мне частотой (с задержкой).
                              Если вообще задать фиксированные ширины колонкам и не трогать их, то производительность сильно поднимается.


                              1. X_OSL
                                06.08.2015 10:38

                                Удалось ли вам запретить «лишние» вызовы стандартного MeasureCore во время скроллинга грида? Если да, то как?


    1. VioletGiraffe
      02.08.2015 19:46

      >Штатный WPF-ный грид умеет кучу совершенно ненужных в типовом приложении вещей
      Что не делает ему чести, если это нельзя быстро и легко отключить.


      1. HomoLuden
        05.08.2015 15:07

        Стоковый грид только на старте, только с дефолтными типами колонок (не TemplateColumn) и только с виртуализацией.
        Его проблема в очень сложном визуальном дереве, что вместе с адаптивным дизайном (подгонка ширины колонок под контент) убивает производительность напроч.
        Я профайлил стартовый этам и даже на глаз заметно, что при первом рендеринге экрана колонки несколько раз меняют свой размер. При каждом ресайзе вызывается огромное количество Measure() для вычисления размеров всех элементов в дереве.

        Не думаю что от стокового грида вряд ли кто-то ожидал хорошей работы. Да, он содержит в себе чрезмерное количество фич, которые реализованы криво и зачастую не нужны.


        1. X_OSL
          06.08.2015 08:31

          В статье я рассматривал этап уже не стартовый этап, и даже не менял размеры колонок.
          Я просто скролил контент.

          И кстати ранее тоже профайлил WPF грид во время скролла (в комментах к предыдущей статье я это разбирал). Вот например мой скриншот профайлинга во время скролла
          http://s30.postimg.org/4lvgfhrnl/Measure.png

          Большинство времени тратится на MeasureCore, даже тогда когда никакие размеры ни грида ни колонок не меняются, виртуализация проблему не лечит.

          Выглядит такая логика не эффективной конечно т.е. можно сказать что проблема в реализации грида.


          1. HomoLuden
            06.08.2015 10:33

            Именно в реализации и конкретно в поведении лэйаута и глубины визуального дерева. Это один из самых тормозных стоковых компонентов.


  1. barkalov
    02.08.2015 19:08

    А что по поводу виртуализации? Без нее WPF (про остальные UI не в курсле) неподъёмен, известное дело.


    1. kekekeks
      02.08.2015 19:18

      По дефолту DataGrid её использует. Можно включить реиспользование через VirtualizingStackPanel.VirtualizationMode="Recycling"


      1. X_OSL
        02.08.2015 19:35

        VirtualizingStackPanel.VirtualizationMode=«Recycling» — добавил для примера WPF. Изменений в результате не видно…


      1. HomoLuden
        05.08.2015 15:09

        У DataGrid и с виртуализацией проблемы производительности серьезные (адаптивный лэйаутинг). Но в кейсе описанном в статье колонки, если не ошибаюсь, не изменяют свой размер.
        Вот если бы сравнили с ListView + GridView. Да еще бы в кейсе перезаполнения источника данных, тогда было бы интереснее.
        DataGrid — Угрюмое Гуано. Нехорошая кандидатура для сравнения.


        1. X_OSL
          06.08.2015 08:57

          Как понимаю, основной недостаток ListView + GridView в невозможности полноценного inline edit.

          Был бы рад, если бы кто-нибудь написал подобное сравнение для контролов ListView в разных библиотеках и возможно других стандартных контролов. Гриды же были выбраны мной, как наиболее универсальные контролы представления (и редактирования) табличных данных.


          1. HomoLuden
            06.08.2015 10:30

            Ответил ниже
            Отличием DataGrid от ListView является то, что у него предоставлено API для редактирования. Т.е. вы у него снаружи прописываете два шаблона ячейки: шаблон для чтения и шаблон для редактирования.
            В моей практике по требованию от UX и дизайнера часто приходится реализовывать хитрое поведение ячеек. Например могут потребовать чтоб все ячейки одновременно переходили в режим редактирования. Потому встроенное API от DataGrid часто «идет лесом».
            Тогда проще всего использовать TemplateColumn и в нем прописывать триггеры, включающие инпуты редактирования, напр., когда строка выделена.
            А работа с TemplateColumn что в DataGrid, что в ListView практически одинакова.

            Так что мне кажется более уместным было в сравнение добавить именно ListView.


            1. X_OSL
              06.08.2015 10:44

              Получается достаточно дорого для разработки в ListView поддерживать редактирование шаблонами…
              Хотя если в гриде все колонки шаблонные, то разница по разработке не сильно большая.

              Надо пробовать, на сколько это может поднять производительность.


              1. X_OSL
                06.08.2015 11:21

                Я попробовал вместо грида в тесте использовать с ListView с GridView в нем. Без дополнительных темплейтов редактирования. Написать пример получилось быстро.

                Но вот работал он ничуть не быстрее обычного грида WPF, FPS на скроллинге был ровно такой же…


    1. X_OSL
      02.08.2015 19:21

      В примере для WPF были включены EnableRowVirtualization=«True» EnableColumnVirtualization=«True»
      Можете попробовать запустить примеры и счетчик, если отпишетесь тут с результатами и названием вашего процессора — будет полезно. Если сможете заставить их работать быстрее — будет еще полезнее.


  1. tzlom
    02.08.2015 19:40
    -3

    Почему Qt такой древний? И почему бы не попробовать то-же самое на QML?


    1. X_OSL
      02.08.2015 19:46

      Qt использовался тот который оказался под рукой (5.2.1 вышел в 2014 году).
      Вполне возможно более свежие Qt покажут результат лучше.
      Если выложите пример на QML или результаты этого же теста собранного под более свежим Qt — будет очень полезно.


  1. RPG18
    03.08.2015 00:32

    Как я понимаю FLTK гонит через OpenGL?


    1. X_OSL
      03.08.2015 09:40

      В FLTK неплохо интегрирована поддержка OpenGL, и некоторые контролы с префиксом Fl_Gl_ рендерятся через OpenGL, однако на сколько я понял, многие другие контролы, рендерятся стандартными средствами системы (Windows, Linux или MacOS)


  1. Athari
    03.08.2015 05:47
    +1

    А где MFC? Без него сравнение не будет полным! %)


    1. X_OSL
      03.08.2015 09:46

      Проблема в том, что на сколько мне известно в MFC нету контрола грид. (Например CListView это никак не грид)
      Сравнивать же сторонние, и зачастую платные, контролы для MFC — наряду со стандартными контролами для других библиотек выглядит очень неравноценным сравнением.


      1. HomoLuden
        05.08.2015 15:13

        Если там есть колонки, то очень даже уместно (по крайней мере в кейсе с неиспользуемой сортировкой и фильтрацией), которые в WPF, например, лучше выносить наружу.


        1. X_OSL
          06.08.2015 08:41

          В CListView не хватает возможности стандартного редактирования ячеек, там, на сколько мне известно, можно редактировать только первую колонку, это существенно сужает его применение для задач представления данных, где данные нужно еще и редактировать.
          Да и в целом CListView плохо умеет работать с ячейками отдельно т.е. для него есть «первая» ячейка, а есть остальные. Конечно бывает этого и хватает.
          Иными словами, на гриде можно решать практический любую задачу представления и редактирования табличных данных, а на ListView только часть этих задач, из-за меньшей универсальности CListView был выбран именно грид для тестов.

          ( и конечно если сравнивать листы, то их надо сравнивать уже с листами, а это совсем другое сравнение)


          1. HomoLuden
            06.08.2015 10:24

            Понятно… Тогда да.
            А в WPF ListView можно снабдить колонками с помощью GridView и для редактирования данных можно использовать TemplatedColumn. С DataGrid часто приходится дизайнить ячейки, потому его встроенная фича редактирования часто оказывается не востребованной.