пятница, 23 июля 2010 г.

Оптимизация

Сегодня по работе оптимизировал одну функцию в движке, длиной строк в 20. Всякие if-ы, array compaction, вызов glUniform*()... Беда в том, что эта функция вызывается много тысяч раз (> 50k), и это создаёт нагрузку на CPU и драйвер.

Надо было оптимизировать. Пораскинув мозгами, я вдруг понял, что можно применить принцип "лучше день потерять, зато потом за пять минут долететь". Осознав, что конкретно для этой функции можно сделать precompute данных, что можно реплицировать входные константы в несколько массивов и таким образом избежать "неизбежного" array compaction, что можно не делать switch по типу, а хранить адрес нужной функции, то переписав код функции и сделав рефакторинг уложился в одну строчку - тот самый вызов glUniform*() по адресу с предрассчитанными аргументами.

Ещё одним "открытием" стало то, что program object кэширует свои юниформы, так что данные в них выживают после смены программ. Благодаря этому оказалось возможным сделать проверку на предмет изменения данных и не телипать лишний раз API/драйвер если данные не изменились. Замер показал, что кол-во необходимых вызовов снижается до 5-6k, что при 400 DIP-ах является вполне приемлемым.

4 комментария:

  1. А ты по времени глянул на сколько ты разгрузил хотя-бы CPU до оптимизации и после?

    ОтветитьУдалить
  2. ~10 ms для всего кадра. Но это debug билд енджина.

    ОтветитьУдалить
  3. Проводил когда-то опыты, идеальным оказался memcmp максимум для двух векторов при передаче данных в шейдер. В OGL 3.0 появились "межшейдерные" юниформы, по типу констант в DX, тоже бывает полезно :)

    ОтветитьУдалить
  4. Я сделал memcmp()/memcpy() максимум для одного float4x4, и для vec4 проверку ручками чтобы не дёргать функцию. Bindable uniforms заюзал для констант с большим fetch/set ratio, но пока на Win от них только падение производительности :(

    ОтветитьУдалить