Чи викликає == розгалуження в GLSL?


27

Спробуємо з’ясувати, що саме спричиняє розгалуження, а що - ні в GLSL.

Я роблю це багато в своєму шейдері:

float(a==b)

Я використовую його для імітації, якщо заяви, без умовного розгалуження ... але чи ефективна вона? У мене зараз немає заяв ніде в моїй програмі, а також у мене немає циклів.

EDIT: Щоб уточнити, я роблю такі речі у своєму коді:

float isTint = float((renderflags & GK_TINT) > uint(0)); // 1 if true, 0 if false
    float isNotTint = 1-isTint;//swaps with the other value
    float isDarken = float((renderflags & GK_DARKEN) > uint(0));
    float isNotDarken = 1-isDarken;
    float isAverage = float((renderflags & GK_AVERAGE) > uint(0));
    float isNotAverage = 1-isAverage;
    //it is none of those if:
    //* More than one of them is true
    //* All of them are false
    float isNoneofThose = isTint * isDarken * isAverage + isNotTint * isAverage * isDarken + isTint * isNotAverage * isDarken + isTint * isAverage * isNotDarken + isNotTint * isNotAverage * isNotDarken;
    float isNotNoneofThose = 1-isNoneofThose;

    //Calc finalcolor;
    finalcolor = (primary_color + secondary_color) * isTint * isNotNoneofThose + (primary_color - secondary_color) * isDarken * isNotNoneofThose + vec3((primary_color.x + secondary_color.x)/2.0,(primary_color.y + secondary_color.y)/2.0,(primary_color.z + secondary_color.z)/2.0) * isAverage * isNotNoneofThose + primary_color * isNoneofThose;

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

Відповіді:


42

Що викликає розгалуження в GLSL, залежить від моделі GPU та версії драйвера OpenGL.

Здається, більшість графічних процесорів мають форму операції "вибрати одне з двох значень", яка не має вартості розгалуження:

n = (a==b) ? x : y;

а іноді навіть такі речі, як:

if(a==b) { 
   n = x;
   m = y;
} else {
   n = y;
   m = x;
}

буде зменшено до декількох операцій із вибраним значенням без штрафного розгалуження.

Деякі графічні процесори / драйвери мають (мали?) Трохи штрафу для оператора порівняння між двома значеннями, але більш швидкої роботи порівняння проти нуля.

Де це можна зробити швидше:

gl_FragColor.xyz = ((tmp1 - tmp2) != vec3(0.0)) ? E : tmp1;

замість того, щоб (tmp1 != tmp2)безпосередньо порівнювати, але це дуже залежить від GPU та драйвера, якщо ви не орієнтуєтесь на дуже конкретний GPU і ніхто інший, я рекомендую використовувати операцію порівняння і залишити цю оптимізацію завдання драйверу OpenGL, оскільки інший драйвер може мати проблеми з довшою формою і бути швидшим простішим, читабельнішим способом.

"Гілки" теж не завжди є поганою справою. Наприклад, на графічному процесорі SGX530, який використовується в OpenPandora, цей шейдер масштабу в два рази (30 мс):

    lowp vec3 E = texture2D(s_texture0, v_texCoord[0]).xyz;
    lowp vec3 D = texture2D(s_texture0, v_texCoord[1]).xyz;
    lowp vec3 F = texture2D(s_texture0, v_texCoord[2]).xyz;
    lowp vec3 H = texture2D(s_texture0, v_texCoord[3]).xyz;
    lowp vec3 B = texture2D(s_texture0, v_texCoord[4]).xyz;
    if ((D - F) * (H - B) == vec3(0.0)) {
            gl_FragColor.xyz = E;
    } else {
            lowp vec2 p = fract(pos);
            lowp vec3 tmp1 = p.x < 0.5 ? D : F;
            lowp vec3 tmp2 = p.y < 0.5 ? H : B;
            gl_FragColor.xyz = ((tmp1 - tmp2) != vec3(0.0)) ? E : tmp1;
    }

Закінчилося значно швидше, ніж цей еквівалентний шейдер (80 мс):

    lowp vec3 E = texture2D(s_texture0, v_texCoord[0]).xyz;
    lowp vec3 D = texture2D(s_texture0, v_texCoord[1]).xyz;
    lowp vec3 F = texture2D(s_texture0, v_texCoord[2]).xyz;
    lowp vec3 H = texture2D(s_texture0, v_texCoord[3]).xyz;
    lowp vec3 B = texture2D(s_texture0, v_texCoord[4]).xyz;
    lowp vec2 p = fract(pos);

    lowp vec3 tmp1 = p.x < 0.5 ? D : F;
    lowp vec3 tmp2 = p.y < 0.5 ? H : B;
    lowp vec3 tmp3 = D == F || H == B ? E : tmp1;
    gl_FragColor.xyz = tmp1 == tmp2 ? tmp3 : E;

Ви ніколи не знаєте заздалегідь, як буде працювати конкретний компілятор GLSL або конкретний графічний процесор, поки ви не порівняєте його.


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

  • Intel HD Graphics 3000
  • Графіка Intel HD 405
  • nVidia GTX 560M
  • nVidia GTX 960
  • AMD Radeon R7 260X
  • nVidia GTX 1050

Як широкий спектр різних, поширених, GPU моделей, з якими можна перевірити.

Тестування кожного за допомогою власників Windows, Linux та Linux з відкритим кодом OpenGL та OpenCL.

І кожен раз, коли я намагаюся мікро-оптимізувати шейдер GLSL (як у прикладі SGX530 вище) або операції OpenCL для одного конкретного комбінованого GPU / драйвера, я в кінцевому підсумку пошкоджую продуктивність на більш ніж одному з інших графічних процесорів / драйверів.

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

Кожен графічний процесор занадто відрізняється від інших.

Якщо ви працюєте спеціально над (а) ігровими консолями (-ми) з певним графічним процесором, це була б інша історія.

Інший (менш важливий для невеликих розробників ігор, але все-таки помітний) полягає в тому, що драйвери комп'ютерних графічних процесорів можуть одного дня мовчки замінити ваші шейдери ( якщо ваша гра стане досить популярною ) на власні переписані, оптимізовані для цього конкретного GPU. Це все для вас працює.

Вони будуть робити це для популярних ігор, які часто використовуються як орієнтири.

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

Наприклад, існують фан-шейдери та фактурні пакети для Oblivion, щоб різко збільшити частоту кадрів на іншому ледь відтворюваному обладнання.

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


"Або якщо ви надаєте вашим гравцям доступ до шейдерів, щоб вони могли легко редагувати їх самих ..." Оскільки ви вже згадали про це, яким може бути ваш підхід до шейдерів на стінах і тому подібного? Система честі, перевірена, звіти ...? Мені подобається, що ідеї лобі обмежуються одними і тими ж шейдерами / активами, якими б вони не були, оскільки позиції на максимальний / хв / масштабований реалізм, подвиги і так далі повинні зближувати гравців і модерів для заохочення до перегляду, співпраці тощо. пам'ятати, що це так, як працював Мод Гарі, але я добре вийшов з циклу.
Джон П

1
@JohnP Security мудре, все, що передбачає, що клієнт не порушений, все одно не працює. Звичайно, якщо ви не хочете, щоб люди редагували свої шейдери, немає сенсу їх виставляти, але це не дуже допомагає безпеці. Ваша стратегія виявлення таких речей, як wallhacks, повинна ставитися до того, що сторона клієнта возиться з речами як низький перший бар'єр, і, мабуть, може бути більша користь, щоб дозволити легке моделювання, як у цьому відповіді, якщо це не призведе до виявлення несправедливої ​​переваги для гравця .
Кубік

8
@JohnP Якщо ви не хочете, щоб гравці теж бачили крізь стіни, не дозволяйте серверу надсилати їм інформацію про те, що знаходиться за стіною.
Полігном

1
Це просто так - я не проти хакерства на стінах між гравцями, яким це подобається з будь-якої причини. Однак, як гравець, я відмовився від кількох назв AAA, тому що - серед інших причин - вони зробили приклади естетичних модерів, коли гроші / XP / тощо. хакери залишилися непошкодженими (які заробляли реальні гроші з тих, хто досить розчарувався, щоб заплатити), занурили та автоматизували свою систему звітів та оскаржень, і переконалися, що ігри живуть і помирають через кількість серверів, яких вони дбали, щоб зберегти життя. Я сподівався, що може бути децентралізованіший підхід як як розробник, так і як гравець.
Джон П

Ні, я не роблю Inline, якщо де. Я просто плаваю (булева заява) * (щось)
Geklmintendon't of Awesome

7

@Stephane Hockenhull відповідь в значній мірі дає вам те, що вам потрібно знати, це буде повністю залежно від обладнання.

Але дозвольте мені дати вам кілька прикладів того , як це може бути залежать від апаратного забезпечення, і тому розгалуження навіть проблема взагалі, то , що робить GPU робить за лаштунками , коли розгалуження робить місце.

Моя увага зосереджена насамперед на Nvidia, у мене є досвід роботи з програмуванням CUDA низького рівня, і я бачу, що створюється PTX ( ІЧ для ядер CUDA , як SPIR-V, але тільки для Nvidia) і бачу орієнтири внесення певних змін.

Чому філія в GPU Architecture така велика справа?

Чому погано гілкуватися в першу чергу? Чому GPU намагаються уникати філій в першу чергу? Оскільки графічні процесори, як правило, використовують схему, де потоки поділяють один і той же вказівник інструкції . Графічні процесори відповідають архітектурі SIMDяк правило, і хоча деталізація цього може змінюватися (тобто 32 потоки для Nvidia, 64 для AMD та інших), на деякому рівні група потоків поділяє один і той же покажчик інструкції. Це означає, що ці нитки повинні шукати один і той же рядок коду, щоб спільно працювати над однією проблемою. Ви можете запитати, як вони здатні використовувати однакові рядки коду та робити різні речі? Вони використовують різні значення в реєстрах, але ці регістри все ще використовуються в одних і тих же рядках коду для всієї групи. Що відбувається, коли це перестає бути таким? (IE гілка?) Якщо програма справді не обійдеться, вона розбиває групу (Nvidia такі пучки з 32 потоків називають Warp , для AMD і паралельних обчислювальних академій це називається фронтом хвилі) у дві або більше різних груп.

Якщо у вас є лише два різних рядки коду, то робочі потоки розділяються між двома групами (звідси одна я називатиму їх основою). Давайте припустимо архітектуру Nvidia, де розмір основи становить 32, якщо половина цих ниток розходиться, то у вас буде 2 основи, зайняті 32 активними нитками, що робить речі наполовину ефективнішими від обчислювальної роботи через закінчення. У багатьох архітектурах GPU намагатиметься виправити це шляхом конвертування потоків назад в єдину основу, коли вони дістаються до тієї ж гілки повідомлення інструкції, або компілятор явно поставить точку синхронізації, яка повідомляє GPU конвергувати потоки назад або намагатися.

наприклад:

if(a)
    x += z * w;
    q >>= p;
else if(c)
    y -= 3;
r += t;

Потік має сильний потенціал для розбіжності (не відрізняються контури інструкцій), тому в такому випадку у вас може виникнути конвергенція, r += t;коли вказівники інструкцій знову будуть однаковими. Розбіжність може трапитися і з більш ніж двома гілками, в результаті чого ще більш низьке використання основи, чотири гілки означають, що 32 нитки розбиваються на 4 основи, 25% пропускна здатність. Однак конвергенція може приховати деякі з цих питань, оскільки 25% не залишається пропускною здатністю через всю програму.

На менш складних графічних процесорах можуть виникати й інші проблеми. Замість розбіжностей вони просто обчислюють усі гілки, а потім вибирають вихід у кінці. Це може виглядати так само, як і розбіжність (обидва мають 1 / n використання пропускної здатності), але є кілька основних проблем із підходом дублювання.

Одне - це енергоспоживання, ви використовуєте набагато більше енергії, коли колись філія трапляється, це буде погано для мобільних gpus. По-друге, розбіжність відбувається лише на gvid Nvidia, коли нитки одного і того ж основи проходять різні шляхи і, таким чином, мають інший вказівник інструкції (який поділяється як на Pascal). Таким чином, ви все ще можете мати розгалуження і не мати проблем з пропускною спроможністю на графічних процесорах Nvidia, якщо вони зустрічаються у кратних 32 або відбуваються лише в одну основу з десятків. якщо вірогідність трапиться, швидше за все, буде зменшуватися менше ниток, і все одно у вас не виникне проблеми з розгалуженням.

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

Приклад відмінності архітектурних графічних процесорів

Тепер давайте на прикладі Стефана і подивимось, як виглядатиме збірка для рішень без галузей двох теоретичних архітектур.

n = (a==b) ? x : y;

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

cmpeq rega, regb
// implicit setting of comparison bit used in next part
choose regn, regx, regy

для інших, які мають інструкцію вибору, вона може бути складена

n = ((a==b))* x + (!(a==b))* y

який може виглядати так:

cmpeq rega regb
// implicit setting of comparison bit used in next part
mul regn regcmp regx
xor regcmp regcmp 1
mul regresult regcmp regy
mul regn regn regresult

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


5

Я погоджуюся з усім, що сказано у відповіді @Stephane Hockenhull. Щоб розгорнути останній пункт:

Ви ніколи не знаєте заздалегідь, як буде працювати конкретний компілятор GLSL або конкретний графічний процесор, поки ви не порівняєте його.

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

Іншими словами, перш ніж зайнятися мікро-оптимізацією шейдера, профіліруйте всю програму та переконайтесь, що шейдери - це те, що викликає ваше сповільнення.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.