Tránh nếu các câu lệnh trong DirectX 10 shader?


14

Tôi đã nghe nói rằng nếu các câu lệnh nên tránh trong shader, bởi vì cả hai phần của câu lệnh sẽ được thực thi, và hơn là sai sẽ bị loại bỏ (điều này gây hại cho hiệu suất).

Nó vẫn là một vấn đề trong DirectX 10? Ai đó nói với tôi, rằng trong đó chỉ có nhánh bên phải sẽ được thực thi.

Để minh họa tôi có mã:

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

Có cách nào khác để làm cho nó nhanh hơn?

Nếu vậy, làm thế nào?

Cả hai nhánh trông giống nhau, sự khác biệt duy nhất là các giá trị của "hằng số" ( y1, y2, b1, b2giống nhau cho tất cả các pixel trong Pixel Shader).


1
Thành thật mà nói, đó là tối ưu hóa rất sớm, chỉ cần không thay đổi chúng cho đến khi bạn điểm chuẩn mã của mình và 100% rằng trình đổ bóng là một nút cổ chai.
pwny

Câu trả lời:


17

Nhiều quy tắc cho các shader tối ưu hóa vi mô cũng giống như đối với các CPU truyền thống có phần mở rộng vector. Dưới đây là một vài gợi ý:

  • có các hàm kiểm tra tích hợp ( test, lerp/ mix)
  • thêm hai vectơ có cùng chi phí như thêm hai phao
  • swizzling là miễn phí

Đúng là các chi nhánh rẻ hơn trên phần cứng hiện đại so với trước đây, nhưng vẫn tốt hơn để tránh chúng nếu có thể. Bằng cách sử dụng chức năng kiểm tra và làm mờ, bạn có thể viết lại trình đổ bóng của mình mà không cần kiểm tra:

/* 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));

Sử dụng steplerplà một thành ngữ rất phổ biến để lựa chọn giữa hai giá trị.


6

Nói chung là ổn. Các shader sẽ thực thi theo nhóm các đỉnh hoặc pixel (các nhà cung cấp khác nhau có các thuật ngữ khác nhau vì vậy tôi sẽ tránh xa điều đó) và nếu tất cả các đỉnh hoặc pixel trong một nhóm đi theo cùng một đường thì chi phí phân nhánh là không đáng kể.

Bạn cũng cần tin tưởng vào trình biên dịch shader. Mã HLSL mà bạn viết không nên được xem như là một đại diện trực tiếp của mã byte hoặc thậm chí là tập hợp mà nó sẽ biên dịch thành và trình biên dịch hoàn toàn tự do để chuyển đổi nó thành một cái gì đó tương đương nhưng tránh được nhánh (đôi khi có thể là lerp một chuyển đổi ưa thích). Mặt khác, nếu trình biên dịch xác định rằng việc thực hiện một nhánh thực sự là đường dẫn nhanh hơn, nó sẽ biên dịch nó xuống một nhánh. Xem lắp ráp được tạo trong PIX hoặc một công cụ tương tự có thể rất hữu ích ở đây.

Cuối cùng, sự khôn ngoan cũ vẫn còn ở đây - hồ sơ nó, xác định xem đó có thực sự là vấn đề hiệu suất đối với bạn không, và giải quyết nó sau đó, không phải trước đó. Giả sử rằng một cái gì đó có thể là một vấn đề hiệu suất và hành động theo giả định đó sẽ chỉ có rủi ro lớn về các vấn đề lớn hơn sau này.


4

Trích dẫn từ liên kết / bài viết được đăng bởi Robert Rouhani:

"Mã điều kiện (tiền định) được sử dụng trong các kiến ​​trúc cũ để mô phỏng phân nhánh thực. Các câu lệnh if-then được biên dịch cho các kiến ​​trúc này phải đánh giá cả các lệnh được lấy và không được lấy trên tất cả các đoạn. Điều kiện nhánh được ước tính và mã điều kiện được đặt. hướng dẫn trong mỗi phần của nhánh phải kiểm tra giá trị của mã điều kiện trước khi ghi kết quả của chúng vào các thanh ghi. Do đó, chỉ các hướng dẫn trong các nhánh được ghi lại đầu ra của chúng. Do đó, trong các kiến ​​trúc này, tất cả các nhánh đều có giá bằng cả hai phần của nhánh, cộng với chi phí đánh giá điều kiện nhánh. Phân nhánh nên được sử dụng một cách tiết kiệm trên các kiến ​​trúc như vậy. GPU NVIDIA GeForce FX Series sử dụng mô phỏng nhánh mã điều kiện trong bộ xử lý phân đoạn của chúng. "

Như mh01 đã đề xuất ("Xem lắp ráp được tạo trong PIX hoặc một công cụ tương tự có thể rất hữu ích ở đây."), Bạn nên sử dụng một công cụ biên dịch để kiểm tra đầu ra. Theo kinh nghiệm của tôi, công cụ Cg của nVidia (Cg vẫn được sử dụng rộng rãi ngày nay vì khả năng đa nền tảng của nó) đã cho một minh họa hoàn hảo về hành vi được đề cập trong đoạn mã điều kiện (dự đoán) của GPU gem . Do đó, bất kể giá trị kích hoạt là gì, cả hai nhánh được đánh giá trên cơ sở từng mảnh và chỉ ở cuối, một nhánh phải được đặt trong sổ đăng ký đầu ra. Tuy nhiên, thời gian tính toán đã bị lãng phí. Trước đó, tôi nghĩ rằng việc phân nhánh sẽ giúp hiệu suất, đặc biệt là vì tất cảcác mảnh trong shader đó dựa vào một giá trị đồng nhất để quyết định nhánh phải - điều đó không xảy ra như dự định. Vì vậy, một cảnh báo lớn ở đây (ví dụ tránh ubershaders - có thể là nguồn địa ngục phân nhánh lớn nhất).


2

Nếu bạn chưa có vấn đề về hiệu suất, điều này là tốt. Chi phí để so sánh với một hằng số vẫn cực kỳ rẻ. Đây là một bài đọc tốt về phân nhánh GPU: http://http.developer.nvidia.com/GPUGems2/gpugems2_ch CHƯƠNG34.html

Bất kể, đây là một đoạn mã sẽ có hiệu lực kém hơn nhiều so với câu lệnh if (và ít có thể đọc / duy trì hơn nhiều), nhưng vẫn bị loại bỏ:

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

x = 10 * y + b;

Lưu ý rằng tôi đang đưa ra giả định rằng x bị giới hạn trong phạm vi [0, 1] . Điều này sẽ không hoạt động nếu x> = 2 hoặc x <0.

Những gì snipped làm là chuyển đổi x thành một 0hoặc 1nhân số sai với 0 và số khác bằng 1.


Vì thử nghiệm ban đầu là if(x<0.5)giá trị fxnên round(x)hoặc floor(x + 0.5).
sam hocevar

1

Có nhiều hướng dẫn có thể làm điều kiện mà không cần phân nhánh;

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

Cộng với một số toán tử logic;

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

nguồn: http://theorangeduck.com/page/avoiding-shader-cond điều kiện

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.