Có == gây phân nhánh trong GLSL không?


27

Cố gắng tìm ra chính xác nguyên nhân gây ra sự phân nhánh và những gì không có trong GLSL.

Tôi đang làm điều này rất nhiều trong shader của tôi:

float(a==b)

Tôi sử dụng nó để mô phỏng nếu các câu lệnh, không phân nhánh có điều kiện ... nhưng nó có hiệu quả không? Tôi không có câu lệnh nếu bất cứ nơi nào trong chương trình của tôi bây giờ, tôi cũng không có bất kỳ vòng lặp nào.

EDIT: Để làm rõ, tôi làm những thứ như thế này trong mã của mình:

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: Tôi biết tại sao tôi không muốn phân nhánh. Tôi biết phân nhánh là gì. Tôi rất vui vì bạn đang dạy bọn trẻ về việc phân nhánh nhưng tôi muốn biết bản thân mình về các toán tử boolean (và bitwise ops nhưng tôi khá chắc chắn rằng chúng vẫn ổn)

Câu trả lời:


42

Điều gì gây ra sự phân nhánh trong GLSL phụ thuộc vào kiểu máy GPU và phiên bản trình điều khiển OpenGL.

Hầu hết các GPU dường như có một hình thức hoạt động "chọn một trong hai giá trị" không có chi phí phân nhánh:

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

và đôi khi cả những thứ như:

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

sẽ được giảm xuống một vài thao tác chọn giá trị mà không bị phạt phân nhánh.

Một số GPU / Trình điều khiển đã (có?) Một chút hình phạt đối với toán tử so sánh giữa hai giá trị nhưng hoạt động nhanh hơn so với không.

Nơi nào có thể nhanh hơn để làm:

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

thay vì so sánh (tmp1 != tmp2)trực tiếp nhưng điều này phụ thuộc rất nhiều vào GPU và trình điều khiển , trừ khi bạn đang nhắm mục tiêu một GPU rất cụ thể và không có ai khác tôi khuyên bạn nên sử dụng thao tác so sánh và để công việc tối ưu hóa đó cho trình điều khiển OpenGL vì trình điều khiển khác có thể có vấn đề với dạng dài hơn và nhanh hơn với cách đơn giản hơn, dễ đọc hơn.

"Chi nhánh" không phải lúc nào cũng là một điều xấu. Ví dụ: trên GPU SGX530 được sử dụng trong OpenPandora, shader scale2x này (30ms):

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

Đã kết thúc nhanh hơn đáng kể so với shader tương đương này (80ms):

    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;

Bạn không bao giờ biết trước trình biên dịch GLSL cụ thể hoặc GPU cụ thể sẽ hoạt động như thế nào cho đến khi bạn đánh giá nó.


Để thêm điểm (ngay cả tho tôi không có số thời gian thực và mã shader để trình bày cho bạn về phần này) Tôi hiện đang sử dụng làm phần cứng kiểm tra thông thường của mình:

  • Đồ họa HD HD 3000
  • Đồ họa Intel HD 405
  • nVidia GTX 560M
  • nVidia GTX 960
  • AMD Radeon R7 260X
  • nVidia GTX 1050

Là một loạt các mô hình GPU khác nhau, phổ biến, để thử nghiệm.

Kiểm tra từng trình điều khiển với Windows, Linux độc quyền và trình điều khiển OpenGL & OpenCL nguồn mở của Linux.

Và mỗi khi tôi cố gắng tối ưu hóa micro shader GLSL (như trong ví dụ SGX530 ở trên) hoặc các hoạt động OpenCL cho một kết hợp GPU / Trình điều khiển cụ thể, tôi sẽ làm tổn hại đến hiệu suất của hơn một trong các GPU / Trình điều khiển khác.

Vì vậy, ngoài việc giảm rõ ràng độ phức tạp toán học cấp cao (ví dụ: chuyển đổi 5 phép chia giống nhau thành một phép đối ứng và 5 phép nhân thay thế) và giảm tra cứu / băng thông kết cấu, rất có thể sẽ lãng phí thời gian của bạn.

Mỗi GPU là quá khác nhau từ những người khác.

Nếu bạn đang làm việc cụ thể trên (a) máy chơi trò chơi với GPU cụ thể thì đây sẽ là một câu chuyện khác.

Một khía cạnh khác (ít quan trọng hơn đối với các nhà phát triển trò chơi nhỏ nhưng vẫn đáng chú ý) ở đây là trình điều khiển GPU máy tính có thể một ngày âm thầm thay thế các shader của bạn ( nếu trò chơi của bạn đủ phổ biến ) với các tùy chỉnh được viết lại được tối ưu hóa cho GPU cụ thể đó. Làm điều đó tất cả làm việc cho bạn.

Họ sẽ làm điều này cho các trò chơi phổ biến thường được sử dụng làm điểm chuẩn.

Hoặc nếu bạn cung cấp cho người chơi của mình quyền truy cập vào các shader để họ có thể dễ dàng tự chỉnh sửa chúng, một số trong số họ có thể ép thêm một vài FPS vì lợi ích riêng của họ.

Ví dụ, có các gói đổ bóng và kết cấu do quạt tạo ra cho Oblivion để tăng đáng kể tốc độ khung hình trên phần cứng hầu như không thể phát.

Và cuối cùng, khi trình tạo bóng của bạn đủ phức tạp, trò chơi của bạn gần như đã hoàn thành và bạn bắt đầu thử nghiệm trên các phần cứng khác nhau, bạn sẽ bận rộn đủ để sửa các trình tạo bóng của bạn hoạt động trên nhiều loại GPU khác nhau do các lỗi khác nhau mà bạn không gặp phải. có thời gian để tối ưu hóa chúng đến mức độ đó.


"Hoặc nếu bạn cung cấp cho người chơi của bạn quyền truy cập vào các shader để họ có thể dễ dàng tự chỉnh sửa chúng ..." Vì bạn đã đề cập đến điều này, cách tiếp cận của bạn với shader wallhack và tương tự là gì? Hệ thống danh dự, được xác minh, báo cáo ...? Tôi thích ý tưởng về các hành lang được giới hạn trong cùng một shader / tài sản, bất kể chúng có thể là gì, vì các quan điểm về chủ nghĩa hiện thực tối đa / tối thiểu / có thể mở rộng, v.v. nên kết hợp người chơi và người điều hành để khuyến khích đánh giá, cộng tác, v.v. để nhớ rằng đây là cách mà Mod của Gary hoạt động, nhưng tôi không thể thực hiện được.
John P

1
@JohnP Bảo mật bất cứ điều gì giả sử khách hàng không bị xâm phạm dù sao cũng không hoạt động. Tất nhiên, nếu bạn không muốn mọi người chỉnh sửa shader của họ thì không có điểm nào để lộ chúng, nhưng nó không thực sự giúp ích nhiều cho bảo mật. Chiến lược phát hiện những thứ như wallhacks của bạn nên coi phía khách hàng rối tung mọi thứ như một rào cản đầu tiên thấp và có thể có một lợi ích lớn hơn khi cho phép điều chỉnh ánh sáng như trong câu trả lời này nếu nó không dẫn đến lợi thế không công bằng cho người chơi .
Khối

8
@JohnP Nếu bạn không muốn người chơi nhìn xuyên tường, đừng để máy chủ gửi cho họ bất kỳ thông tin nào về những gì đằng sau bức tường.
Polygnome

1
Chỉ vậy thôi - Tôi không chống lại việc hack tường giữa những người chơi thích nó vì bất kỳ lý do gì. Tuy nhiên, với tư cách là một người chơi, tôi đã từ bỏ một số tựa game AAA vì - trong số các lý do khác - họ đã tạo ra các ví dụ về các bộ điều chỉnh thẩm mỹ trong khi tiền / XP / v.v. tin tặc đã trở nên vô đạo đức (những người kiếm được tiền thật từ những người nản lòng đủ để trả tiền), bảo vệ và tự động hóa hệ thống báo cáo và kháng cáo của họ, và chắc chắn rằng các trò chơi đã sống và chết bởi số lượng máy chủ mà họ quan tâm để duy trì sự sống. Tôi đã hy vọng có thể có một cách tiếp cận phi tập trung hơn khi cả dev và player.
John P

Không tôi không làm nội tuyến nếu bất cứ nơi nào. Tôi chỉ thực hiện float (tuyên bố boolean) * (một cái gì đó)
Geklmintend Do not Awesome

7

Câu trả lời của @Stephane Hockenhull cung cấp cho bạn những gì bạn cần biết, nó sẽ hoàn toàn phụ thuộc vào phần cứng.

Nhưng hãy để tôi cung cấp cho bạn một số ví dụ về cách nó có thể được phần cứng phụ thuộc, và tại sao nhánh thậm chí là một vấn đề ở tất cả, những gì GPU làm đằng sau hậu trường khi phân nhánh làm nơi cất.

Trọng tâm của tôi chủ yếu là với Nvidia, tôi có một số kinh nghiệm với lập trình CUDA cấp thấp và tôi thấy những gì PTX ( IR cho hạt nhân CUDA , như SPIR-V nhưng chỉ dành cho Nvidia) được tạo ra và xem các tiêu chuẩn thực hiện một số thay đổi nhất định.

Tại sao Chi nhánh trong Kiến trúc GPU lại là một vấn đề lớn như vậy?

Tại sao nó là xấu để chi nhánh ở nơi đầu tiên? Tại sao GPU cố gắng tránh phân nhánh ở nơi đầu tiên? Bởi vì GPU thường sử dụng sơ đồ trong đó các luồng chia sẻ cùng một con trỏ lệnh . GPU đi theo kiến trúc SIMDthông thường, và trong khi độ chi tiết của điều đó có thể thay đổi (ví dụ 32 luồng cho Nvidia, 64 cho AMD và các luồng khác), ở một mức độ nào đó, một nhóm các luồng chia sẻ cùng một con trỏ lệnh. Điều này có nghĩa là các luồng đó cần được xem xét cùng một dòng mã để có thể làm việc cùng nhau trên cùng một vấn đề. Bạn có thể hỏi làm thế nào họ có thể sử dụng cùng một dòng mã và làm những việc khác nhau? Chúng sử dụng các giá trị khác nhau trong các thanh ghi, nhưng các thanh ghi đó vẫn được sử dụng trong cùng một dòng mã trên toàn bộ nhóm. Điều gì xảy ra khi điều đó dừng lại là trường hợp? (IE có phải là chi nhánh không?) Nếu chương trình thực sự không có cách nào khác, nó sẽ tách nhóm (Nvidia như vậy gồm 32 luồng được gọi là Warp , đối với AMD và học viện điện toán song song, nó được gọi là mặt sóng) trong hai hoặc nhiều nhóm khác nhau.

Nếu chỉ có hai dòng mã khác nhau mà bạn sẽ kết thúc, thì các luồng làm việc được phân chia giữa hai nhóm (từ đây, tôi sẽ gọi chúng là các sợi dọc). Giả sử kiến ​​trúc Nvidia, trong đó kích thước sợi dọc là 32, nếu một nửa số luồng này phân kỳ thì bạn sẽ có 2 sợi dọc chiếm 32 luồng hoạt động, điều này làm cho mọi thứ hiệu quả bằng một nửa tính toán từ đầu đến cuối. Trên nhiều kiến ​​trúc, GPU sẽ cố gắng khắc phục điều này bằng cách hội tụ các luồng trở lại thành một sợi dọc khi chúng đến cùng một nhánh bài hướng dẫn, hoặc trình biên dịch sẽ đặt một điểm đồng bộ hóa rõ ràng để GPU hội tụ lại các luồng hoặc cố gắng.

ví dụ:

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

Các luồng có tiềm năng mạnh để phân kỳ (các đường dẫn lệnh không giống nhau) vì vậy trong trường hợp như vậy, bạn có thể có sự hội tụ xảy ra trong r += t;đó các con trỏ lệnh sẽ lại giống nhau. Sự phân kỳ cũng có thể xảy ra với hơn hai nhánh, dẫn đến việc sử dụng sợi dọc thậm chí thấp hơn, bốn nhánh có nghĩa là 32 luồng được chia thành 4 sợi dọc, sử dụng 25% thông lượng. Tuy nhiên, sự hội tụ có thể che giấu một số vấn đề này, vì 25% không duy trì được thông lượng trong toàn bộ chương trình.

Trên các GPU kém tinh vi, các vấn đề khác có thể xảy ra. Thay vì chuyển hướng, họ chỉ tính toán tất cả các nhánh sau đó chọn đầu ra ở cuối. Điều này có thể xuất hiện giống như phân kỳ (cả hai đều có mức sử dụng thông lượng 1 / n), nhưng có một vài vấn đề lớn với phương pháp sao chép.

Một là sử dụng năng lượng, bạn đang sử dụng nhiều năng lượng hơn khi có một nhánh xảy ra, điều này sẽ không tốt cho gpus di động. Thứ hai là sự phân kỳ chỉ xảy ra trên Nvidia gpus khi các luồng của cùng một sợi dọc có các đường dẫn khác nhau và do đó có một con trỏ lệnh khác nhau (được chia sẻ như của pascal). Vì vậy, bạn vẫn có thể phân nhánh và không gặp sự cố thông lượng trên GPU Nvidia nếu chúng xảy ra trong bội số 32 hoặc chỉ xảy ra trong một lần vênh trong số hàng chục. nếu một nhánh có khả năng xảy ra thì nhiều khả năng sẽ có ít luồng hơn và bạn sẽ không gặp vấn đề rẽ nhánh.

Một vấn đề nhỏ hơn là khi bạn so sánh GPU với CPU, chúng thường không có cơ chế dự đoán và cơ chế nhánh mạnh mẽ khác vì cơ chế đó chiếm bao nhiêu phần cứng, bạn thường có thể thấy no-op trên GPU hiện đại vì điều này.

Ví dụ về sự khác biệt kiến ​​trúc GPU thực tế

Bây giờ hãy lấy ví dụ của Stephanes và xem lắp ráp sẽ như thế nào đối với các giải pháp không có chi nhánh trên hai kiến ​​trúc lý thuyết.

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

Giống như Stephane đã nói, khi trình biên dịch thiết bị gặp phải một nhánh, nó có thể quyết định sử dụng một lệnh để "chọn" phần tử mà cuối cùng sẽ không bị phạt nhánh. Điều này có nghĩa là trên một số thiết bị, nó sẽ được biên dịch thành một cái gì đó như

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

trên những người khác không có hướng dẫn chọn, nó có thể được biên dịch thành

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

trông giống như:

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

không có chi nhánh và tương đương, nhưng có nhiều hướng dẫn hơn. Bởi vì ví dụ của Stephanes có thể sẽ được biên dịch theo hệ thống tương ứng của họ, nên sẽ không có ý nghĩa gì khi cố gắng tự tìm ra toán học để loại bỏ nhánh, vì trình biên dịch của kiến ​​trúc đầu tiên có thể quyết định biên dịch sang dạng thứ hai thay vì hình thức nhanh hơn.


5

Tôi đồng tình với mọi thứ được nói trong câu trả lời của @Stephane Hockenhull. Để mở rộng về điểm cuối cùng:

Bạn không bao giờ biết trước trình biên dịch GLSL cụ thể hoặc GPU cụ thể sẽ hoạt động như thế nào cho đến khi bạn đánh giá nó.

Hoàn toàn đúng. Hơn nữa, tôi thấy loại câu hỏi này xuất hiện khá thường xuyên. Nhưng trong thực tế, tôi hiếm khi thấy một shader mảnh là nguồn gốc của vấn đề hiệu năng. Điều phổ biến hơn là các yếu tố khác đang gây ra các vấn đề như quá nhiều lần đọc trạng thái từ GPU, hoán đổi quá nhiều bộ đệm, quá nhiều công việc trong một cuộc gọi rút thăm, v.v.

Nói cách khác, trước khi bạn lo lắng về việc tối ưu hóa vi mô của trình đổ bóng, hãy lập hồ sơ cho toàn bộ ứng dụng của bạn và đảm bảo rằng các trình đổ bóng là thứ gây ra sự chậm chạp của bạ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.