Hindari jika pernyataan dalam DirectX 10 shaders?

14

Saya telah mendengar bahwa jika pernyataan harus dihindari dalam bayangan, karena kedua bagian dari pernyataan akan dieksekusi, dan daripada yang salah akan dijatuhkan (yang merusak kinerja).

Masih menjadi masalah di DirectX 10? Seseorang mengatakan kepada saya, bahwa di dalamnya hanya cabang yang tepat yang akan dieksekusi.

Sebagai ilustrasi saya punya kode:

float y1 = 5; float y2 = 6; float b1 = 2; float b2 = 3;

if(x>0.5){
    x = 10 * y1 + b1;
}else{
    x = 10 * y2 + b2;
}

Apakah ada cara lain untuk membuatnya lebih cepat?

Jika demikian, bagaimana caranya?

Kedua cabang terlihat serupa, satu-satunya perbedaan adalah nilai-nilai "konstanta" ( y1, y2, b1, b2sama untuk semua piksel dalam Pixel Shader).

PolGraphic
sumber
1
Jujur, itu optimasi yang sangat prematur, hanya jangan mengubahnya sampai Anda membuat benchmark kode Anda dan 100% bahwa shader adalah hambatan.
pwny

Jawaban:

17

Banyak aturan untuk shader yang mengoptimalkan mikro sama dengan CPU tradisional dengan ekstensi vektor. Berikut ini beberapa petunjuk:

  • ada fungsi tes bawaan ( test, lerp/ mix)
  • menambahkan dua vektor memiliki biaya yang sama dengan menambahkan dua pelampung
  • swizzling gratis

Memang benar bahwa cabang lebih murah pada perangkat keras modern daripada sebelumnya, tetapi masih lebih baik untuk menghindarinya jika memungkinkan. Dengan menggunakan fungsi swizzling dan tes Anda dapat menulis ulang shader Anda tanpa tes:

/* y1, y2, b1, b2 */
float4 constants = float4(5, 6, 2, 3);

float2 tmp = 10 * constants.xy + constants.zw;
x = lerp(tmp[1], tmp[0], step(x, 0.5));

Menggunakan stepdan lerpmerupakan ungkapan yang sangat umum untuk memilih antara dua nilai.

sam hocevar
sumber
6

Secara umum tidak masalah. Shader akan dieksekusi dalam kelompok simpul atau piksel (vendor yang berbeda memiliki terminologi berbeda untuk ini jadi saya menjauhi itu) dan jika semua simpul atau piksel dalam grup mengambil jalur yang sama maka biaya percabangan diabaikan.

Anda juga perlu mempercayai kompiler shader. Kode HLSL yang Anda tulis tidak boleh dilihat sebagai representasi langsung dari bytecode atau bahkan rakitan yang akan dikompilasi, dan kompiler bebas untuk mengubahnya menjadi sesuatu yang setara tetapi menghindari cabang (misalnya lerp kadang-kadang mungkin konversi yang disukai). Di sisi lain, jika kompiler menentukan bahwa melakukan cabang sebenarnya adalah jalur yang lebih cepat, ia akan mengkompilasinya ke cabang. Melihat perakitan yang dihasilkan di PIX atau alat serupa bisa sangat membantu di sini.

Akhirnya, kearifan lama masih berlaku di sini - profil itu, tentukan apakah itu benar-benar masalah kinerja untuk Anda, dan atasi itu, bukan sebelumnya. Dengan asumsi bahwa sesuatu mungkin merupakan masalah kinerja dan bertindak sesuai dengan asumsi itu hanya akan menimbulkan risiko besar masalah yang lebih besar di kemudian hari.

Maximus Minimus
sumber
4

Kutipan dari tautan / artikel yang diposting oleh Robert Rouhani:

"Kode kondisi (predikasi) digunakan dalam arsitektur yang lebih tua untuk meniru percabangan sejati. Jika-maka pernyataan yang dikompilasi untuk arsitektur ini harus mengevaluasi instruksi cabang yang diambil dan tidak diambil pada semua fragmen. Kondisi cabang dievaluasi dan kode kondisi ditetapkan. instruksi di setiap bagian cabang harus memeriksa nilai kode kondisi sebelum menulis hasilnya ke register. Akibatnya, hanya instruksi di cabang yang diambil yang menuliskan outputnya. Dengan demikian, dalam arsitektur ini semua cabang memerlukan biaya sebanyak kedua bagian dari cabang, ditambah biaya mengevaluasi kondisi cabang. Percabangan harus digunakan hemat pada arsitektur seperti itu. NVIDIA GeForce FX Series GPU menggunakan emulasi cabang kode kondisi dalam prosesor fragmen mereka. "

Seperti yang disarankan mh01 ("Melihat rakitan yang dihasilkan dalam PIX atau alat serupa bisa sangat membantu di sini."), Anda harus menggunakan alat kompiler untuk memeriksa hasilnya. Dalam pengalaman saya, alat CV nVidia (Cg masih banyak digunakan saat ini karena kemampuan lintas platformnya) memberikan ilustrasi sempurna tentang perilaku yang disebutkan dalam paragraf kode kondisi permata (predikasi) paragraf GPU . Dengan demikian, terlepas dari nilai pemicu, kedua cabang dievaluasi berdasarkan per fragmen, dan hanya pada akhirnya, yang tepat ditempatkan dalam registri keluaran. Namun demikian, waktu perhitungan terbuang sia-sia. Saat itu, saya berpikir bahwa percabangan akan membantu kinerja, terutama karena semuanyafragmen dalam shader itu bergantung pada nilai yang seragam untuk memutuskan cabang yang tepat - yang tidak terjadi seperti yang dimaksudkan. Jadi, peringatan utama di sini (misalnya, hindari ubershaders - mungkin sumber neraka percabangan terbesar).

teko teh
sumber
2

Jika Anda belum mengalami masalah kinerja, ini tidak masalah. Biaya perbandingan dengan konstanta masih sangat murah. Berikut ini bacaan yang bagus tentang percabangan GPU: http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter34.html

Bagaimanapun juga, ini adalah potongan kode yang akan terbentuk lebih buruk daripada pernyataan if (dan jauh lebih mudah dibaca / dikelola), tetapi masih menghilangkannya:

int fx = floor(x);
int y = (fx * y2) + ((1- fx) * y1);
int b = (fx * b2) + ((1 -fx) * b1);

x = 10 * y + b;

Perhatikan bahwa saya membuat asumsi bahwa x terbatas pada rentang [0, 1]. Ini tidak akan berfungsi jika x> = 2 atau x <0.

Apa yang dilakukan potongan adalah mengonversi x menjadi salah satu 0dan 1dan mengalikan yang salah dengan 0 dan yang lainnya dengan 1.

Robert Rouhani
sumber
Karena tes asli adalah if(x<0.5)nilai fxseharusnya round(x)atau floor(x + 0.5).
sam hocevar
1

Ada banyak instruksi yang dapat melakukan kondisi tanpa bercabang;

vec4 when_eq(vec4 x, vec4 y) {
  return 1.0 - abs(sign(x - y));
}

vec4 when_neq(vec4 x, vec4 y) {
  return abs(sign(x - y));
}

vec4 when_gt(vec4 x, vec4 y) {
  return max(sign(x - y), 0.0);
}

vec4 when_lt(vec4 x, vec4 y) {
  return max(sign(y - x), 0.0);
}

vec4 when_ge(vec4 x, vec4 y) {
  return 1.0 - when_lt(x, y);
}

vec4 when_le(vec4 x, vec4 y) {
  return 1.0 - when_gt(x, y);
}

Ditambah beberapa operator logis;

vec4 and(vec4 a, vec4 b) {
  return a * b;
}

vec4 or(vec4 a, vec4 b) {
  return min(a + b, 1.0);
}

vec4 xor(vec4 a, vec4 b) {
  return (a + b) % 2.0;
}

vec4 not(vec4 a) {
  return 1.0 - a;
}

sumber: http://theorangeduck.com/page/avoiding-shader-conditionals

Alexis Paques
sumber