Apakah == menyebabkan percabangan di GLSL?

27

Mencoba mencari tahu apa yang menyebabkan percabangan dan apa yang tidak ada dalam GLSL.

Saya sering melakukan ini di shader saya:

float(a==b)

Saya menggunakannya untuk mensimulasikan pernyataan if, tanpa percabangan bersyarat ... tetapi apakah ini efektif? Saya tidak memiliki pernyataan if di mana pun dalam program saya sekarang, juga tidak memiliki loop.

EDIT: Untuk memperjelas, saya melakukan hal-hal seperti ini dalam kode saya:

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: Saya tahu mengapa saya tidak ingin bercabang. Saya tahu apa itu percabangan. Saya senang Anda mengajari anak-anak tentang percabangan tapi saya ingin tahu sendiri tentang operator boolean (dan operasi bitwise tapi saya cukup yakin mereka baik-baik saja)

Tidak ada yang Mengagumkan
sumber

Jawaban:

42

Apa yang menyebabkan percabangan di GLSL tergantung pada model GPU dan versi driver OpenGL.

Sebagian besar GPU tampaknya memiliki bentuk operasi "pilih satu dari dua nilai" yang tidak memiliki biaya percabangan:

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

dan terkadang bahkan hal-hal seperti:

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

akan dikurangi menjadi beberapa operasi nilai pilih tanpa penalti cabang.

Beberapa GPU / Driver memiliki (memiliki?) Sedikit penalti pada operator perbandingan antara dua nilai tetapi operasi lebih cepat dibandingkan dengan nol.

Di mana mungkin lebih cepat untuk dilakukan:

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

daripada membandingkan (tmp1 != tmp2)secara langsung tetapi ini sangat tergantung GPU dan driver jadi kecuali Anda menargetkan GPU yang sangat spesifik dan tidak ada orang lain saya sarankan menggunakan operasi perbandingan dan meninggalkan pekerjaan yang mengoptimalkan ke driver OpenGL sebagai driver lain mungkin memiliki masalah dengan bentuk yang lebih panjang dan menjadi lebih cepat dengan cara yang lebih sederhana dan lebih mudah dibaca.

"Cabang" juga tidak selalu buruk. Misalnya pada GPU SGX530 yang digunakan dalam OpenPandora, scale2x shader ini (30 ms):

    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;
    }

Berakhir secara dramatis lebih cepat daripada shader yang setara ini (80 ms):

    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;

Anda tidak pernah tahu sebelumnya bagaimana kinerja kompiler GLSL tertentu atau GPU tertentu sampai Anda membuat benchmark.


Untuk menambahkan titik (meskipun saya tidak memiliki angka waktu aktual dan kode shader untuk menyajikan Anda untuk bagian ini) Saat ini saya menggunakan sebagai perangkat keras pengujian biasa:

  • Intel HD Graphics 3000
  • Intel HD 405 Graphics
  • nVidia GTX 560M
  • nVidia GTX 960
  • AMD Radeon R7 260X
  • nVidia GTX 1050

Sebagai beragam model GPU yang berbeda dan umum untuk diuji.

Menguji masing-masing dengan driver OpenGL & OpenCL Linux, proprietary, dan Linux open source.

Dan setiap kali saya mencoba mengoptimalkan mikro shader GLSL (seperti pada contoh SGX530 di atas) atau operasi OpenCL untuk satu kombo GPU / Driver tertentu, saya pada akhirnya sama-sama melukai kinerja pada lebih dari satu GPU / Driver lain.

Jadi, selain dengan jelas mengurangi kompleksitas matematika tingkat tinggi (misalnya: mengonversi 5 divisi identik menjadi satu timbal balik dan 5 perkalian sebagai gantinya) dan mengurangi pencarian tekstur / bandwidth, kemungkinan besar akan membuang-buang waktu Anda.

Setiap GPU terlalu berbeda dari yang lain.

Jika Anda akan bekerja secara khusus pada (a) konsol game dengan GPU tertentu, ini akan menjadi cerita yang berbeda.

Aspek lain (kurang signifikan untuk pengembang game kecil tetapi masih terkenal) dari ini adalah bahwa driver GPU komputer suatu hari nanti diam-diam mengganti shader Anda ( jika game Anda menjadi cukup populer ) dengan yang ditulis ulang kustom dioptimalkan untuk GPU tertentu. Melakukan itu semua bekerja untuk Anda.

Mereka akan melakukan ini untuk game populer yang sering digunakan sebagai tolok ukur.

Atau jika Anda memberi pemain Anda akses ke shader sehingga mereka dapat dengan mudah mengeditnya sendiri, beberapa dari mereka mungkin memeras beberapa FPS tambahan untuk keuntungan mereka sendiri.

Misalnya ada paket shader & tekstur buatan fan untuk Oblivion untuk secara dramatis meningkatkan frame rate pada perangkat keras yang nyaris tidak bisa dimainkan.

Dan terakhir, setelah shader Anda menjadi cukup kompleks, game Anda hampir selesai, dan Anda mulai menguji pada perangkat keras yang berbeda, Anda akan cukup sibuk hanya memperbaiki shader Anda untuk bekerja sama sekali pada berbagai GPU karena itu disebabkan oleh berbagai bug yang Anda tidak inginkan. punya waktu untuk mengoptimalkan mereka ke tingkat itu.

Stephane Hockenhull
sumber
"Atau jika kamu memberi pemainmu akses ke shader sehingga mereka dapat dengan mudah mengeditnya sendiri ..." Karena kamu sudah menyebutkan ini, apa yang mungkin menjadi pendekatanmu terhadap wallhack shaders dan sejenisnya? Sistem kehormatan, terverifikasi, laporan ...? Saya suka ide lobi terbatas pada shader / aset yang sama, apa pun itu, karena sikap pada realisme maks / mnt / skalabel, eksploitasi, dan sebagainya harus menyatukan para pemain dan modder untuk mendorong ulasan, kolaborasi, dll. untuk mengingat ini adalah cara Mod Gary bekerja, tapi aku tidak mengerti.
John P
1
@ JohnP Keamanan bijaksana apa pun yang mengasumsikan klien tidak dikompromikan tidak berhasil. Tentu saja jika Anda tidak ingin orang mengedit shader mereka, tidak ada gunanya mengekspos mereka, tetapi itu tidak terlalu membantu keamanan. Strategi Anda untuk mendeteksi hal-hal seperti wallhacks harus memperlakukan sisi klien mengacaukan hal-hal sebagai penghalang pertama yang rendah, dan mungkin ada manfaat yang lebih besar untuk memungkinkan modding cahaya seperti dalam jawaban ini jika itu tidak mengarah pada keuntungan tidak adil yang dapat terdeteksi bagi pemain. .
Cubic
8
@JohnP Jika Anda tidak ingin pemain melihat menembus dinding, jangan biarkan server mengirimi mereka informasi apa pun tentang apa yang ada di balik tembok itu.
Polygnome
1
Hanya itu - saya tidak menentang peretasan di dinding antara pemain yang menyukainya karena alasan apa pun. Sebagai pemain, saya telah meninggalkan beberapa gelar AAA karena - di antara alasan-alasan lain - mereka membuat contoh pemodel estetika sementara uang / XP / dll. peretas pergi tanpa cedera (yang menghasilkan uang nyata dari mereka yang cukup frustrasi untuk membayar), kekurangan tenaga dan mengotomatiskan sistem laporan & banding mereka, dan memastikan bahwa permainan itu hidup dan mati oleh jumlah server yang mereka pelihara agar tetap hidup. Saya berharap mungkin ada pendekatan yang lebih terdesentralisasi sebagai dev dan pemain.
John P
Tidak, saya tidak melakukan inline jika ada. Saya hanya melakukan float (pernyataan boolean) * (sesuatu)
Geklmintendon't dari Awesome
7

@Stephane Jawaban Hockenhull cukup banyak memberi Anda apa yang perlu Anda ketahui, itu akan sepenuhnya bergantung pada perangkat keras.

Tapi izinkan saya memberi Anda beberapa contoh bagaimana hal itu dapat tergantung hardware, dan mengapa bercabang bahkan masalah sama sekali, apa GPU melakukan balik layar ketika bercabang tidak tempat take.

Fokus saya terutama dengan Nvidia, saya memiliki pengalaman dengan pemrograman CUDA tingkat rendah, dan saya melihat apa PTX ( IR untuk kernel CUDA , seperti SPIR-V tetapi hanya untuk Nvidia) dihasilkan dan melihat tolok ukur membuat perubahan tertentu.

Mengapa Bercabang di Arsitektur GPU merupakan masalah besar?

Mengapa cabang buruk? Mengapa GPU mencoba menghindari percabangan? Karena GPU biasanya menggunakan skema di mana utas berbagi penunjuk instruksi yang sama . GPU mengikuti arsitektur SIMDbiasanya, dan meskipun rinciannya mungkin berubah (yaitu 32 utas untuk Nvidia, 64 utas untuk AMD dan lainnya), pada tingkat tertentu sekelompok utas berbagi penunjuk instruksi yang sama. Ini berarti bahwa utas-utas tersebut harus melihat baris kode yang sama agar dapat bekerja sama dalam masalah yang sama. Anda mungkin bertanya bagaimana mereka dapat menggunakan baris kode yang sama dan melakukan hal yang berbeda? Mereka menggunakan nilai yang berbeda dalam register, tetapi register tersebut masih digunakan dalam baris kode yang sama di seluruh grup. Apa yang terjadi ketika itu berhenti menjadi masalahnya? (Yaitu cabang?) Jika program benar-benar tidak memiliki jalan lain, ia membagi kelompok (Nvidia bundel 32 thread seperti itu disebut Warp , untuk AMD dan akademisi komputasi paralel, itu disebut sebagai muka gelombang.) ke dua atau lebih grup yang berbeda.

Jika hanya ada dua baris kode yang berbeda yang akan Anda gunakan, maka utas kerja dibagi menjadi dua kelompok (dari sini saya akan menyebutnya warps). Mari kita asumsikan arsitektur Nvidia, di mana ukuran warp adalah 32, jika setengah dari thread ini berbeda maka Anda akan memiliki 2 warps ditempati oleh 32 thread aktif, yang membuat semuanya menjadi setengah efisien dari komputasi melalui put end. Pada banyak arsitektur GPU akan mencoba untuk memperbaiki hal ini dengan konvergen benang kembali ke dalam warp tunggal setelah mereka mencapai cabang instruksi pos yang sama, atau compiler eksplisit akan menempatkan titik sinkronisasi yang menceritakan GPU untuk benang konvergen kembali, atau mencoba untuk.

sebagai contoh:

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

Utas memiliki potensi kuat untuk menyimpang (jalur instruksi berbeda) sehingga dalam kasus seperti itu Anda mungkin mengalami konvergensi di r += t;mana petunjuk instruksi akan sama lagi. Divergensi juga dapat terjadi dengan lebih dari dua cabang, yang menghasilkan utilisasi warp yang lebih rendah, empat cabang berarti 32 thread dapat dipecah menjadi 4 warps, utilisasi throughput 25%. Namun konvergensi dapat menyembunyikan beberapa masalah ini, karena 25% tidak tinggal throughput seluruh program.

Pada GPU yang kurang canggih, masalah lain dapat terjadi. Alih-alih menyimpang mereka hanya menghitung semua cabang kemudian pilih output di akhir. Ini mungkin tampak sama dengan divergensi (keduanya memiliki pemanfaatan throughput 1 / n), tetapi ada beberapa masalah utama dengan pendekatan duplikasi.

Salah satunya adalah penggunaan daya, Anda menggunakan lebih banyak daya ketika cabang terjadi, ini akan buruk untuk GPU ponsel. Kedua adalah bahwa divergensi hanya terjadi pada Nvidia gpus ketika utas dari warp yang sama mengambil jalur yang berbeda dan dengan demikian memiliki penunjuk instruksi yang berbeda (yang digunakan pada pascal). Jadi Anda masih dapat memiliki percabangan dan tidak memiliki masalah throughput pada GPU Nvidia jika mereka terjadi dalam kelipatan 32 atau hanya terjadi dalam satu lusinan dari lusinan. jika suatu cabang kemungkinan terjadi, maka semakin besar kemungkinan utas akan menyimpang dan Anda tidak akan memiliki masalah percabangan.

Masalah lain yang lebih kecil adalah ketika Anda membandingkan GPU dengan CPU, mereka seringkali tidak memiliki mekanisme prediksi dan mekanisme cabang kuat lainnya karena seberapa banyak perangkat keras yang digunakan oleh mekanisme tersebut, Anda sering dapat melihat isi tanpa-op pada GPU modern karena hal ini.

Contoh Perbedaan Arsitektur Arsitektur Praktis

Sekarang mari kita ambil contoh Stephanes dan lihat seperti apa rakitan itu untuk solusi tanpa cabang pada dua arsitektur teoretis.

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

Seperti yang dikatakan Stephane, ketika kompiler perangkat menemukan cabang, ia mungkin memutuskan untuk menggunakan instruksi untuk "memilih" elemen yang pada akhirnya tidak memiliki penalti cabang. Ini berarti pada beberapa perangkat ini akan dikompilasi menjadi seperti

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

pada orang lain tanpa instruksi pilih, mungkin dikompilasi

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

yang mungkin terlihat seperti:

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

yang kurang cabang dan setara, tetapi membutuhkan lebih banyak instruksi. Karena contoh Stephanes kemungkinan akan dikompilasi baik pada sistem mereka masing-masing, tidak masuk akal untuk mencoba menghitung matematika secara manual untuk menghapus percabangan itu sendiri, karena kompiler arsitektur pertama dapat memutuskan untuk mengkompilasi ke bentuk kedua alih-alih bentuk yang lebih cepat.

wih
sumber
5

Saya setuju dengan semua yang dikatakan dalam jawaban @Stephane Hockenhull. Untuk memperluas pada poin terakhir:

Anda tidak pernah tahu sebelumnya bagaimana kinerja kompiler GLSL tertentu atau GPU tertentu sampai Anda membuat benchmark.

Sepenuhnya benar. Selain itu, saya melihat pertanyaan semacam ini cukup sering muncul. Namun dalam praktiknya saya jarang melihat shader fragmen menjadi sumber masalah kinerja. Jauh lebih umum bahwa faktor-faktor lain yang menyebabkan masalah seperti terlalu banyak membaca status dari GPU, menukar terlalu banyak buffer, terlalu banyak pekerjaan dalam satu panggilan draw, dll.

Dengan kata lain, sebelum Anda khawatir tentang mengoptimalkan mikro shader, buat profil seluruh aplikasi Anda dan pastikan shader adalah penyebab perlambatan Anda.

pengguna1118321
sumber