== ทำให้เกิดการแตกสาขาใน GLSL หรือไม่


27

พยายามคิดให้ชัดเจนว่าอะไรทำให้เกิดการแตกแขนงและอะไรที่ไม่ได้อยู่ใน GLSL

ฉันทำสิ่งนี้มากมายใน shader ของฉัน:

float(a==b)

ฉันใช้มันเพื่อจำลองว่างบโดยไม่มีการแยกตามเงื่อนไข ... แต่มันมีประสิทธิภาพ? ฉันไม่มีคำสั่งใด ๆ ในโปรแกรมของฉันตอนนี้และฉันไม่มีลูป

แก้ไข: เพื่อชี้แจงฉันทำสิ่งนี้ในรหัสของฉัน:

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;

แก้ไข: ฉันรู้ว่าทำไมฉันไม่ต้องการแยกทาง ฉันรู้ว่าการแตกแขนงคืออะไร ฉันดีใจที่คุณสอนเด็ก ๆ เกี่ยวกับการแตกแขนง แต่ฉันอยากรู้จักตัวเองเกี่ยวกับโอเปอเรเตอร์บูลีน (และตัวเลือกบิตทรัพทร์ แต่ฉันค่อนข้างแน่ใจว่ามันโอเค)

คำตอบ:


42

สิ่งที่ทำให้การแตกสาขาใน GLSL ขึ้นอยู่กับรุ่นของ GPU และเวอร์ชั่นของไดรเวอร์ OpenGL

GPUs ส่วนใหญ่ดูเหมือนจะมีรูปแบบการทำงาน "เลือกหนึ่งในสองค่า" ที่ไม่มีค่าใช้จ่ายในการแยกสาขา:

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

และบางครั้งก็มีสิ่งที่ชอบ:

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

จะลดลงเป็นการดำเนินการเลือกค่าบางอย่างโดยไม่มีการลงโทษจากการแตกสาขา

GPU / ไดร์เวอร์บางตัวมีโทษเล็กน้อยสำหรับผู้ดำเนินการเปรียบเทียบระหว่างสองค่า แต่ทำงานได้เร็วกว่าเมื่อเปรียบเทียบกับศูนย์

มันอาจจะเร็วกว่าที่จะทำ:

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

แทนที่จะเปรียบเทียบ(tmp1 != tmp2)โดยตรงแต่นี่ขึ้นอยู่กับ GPU และไดรเวอร์ดังนั้นหากคุณไม่ได้กำหนดเป้าหมายเป็น GPU ที่เฉพาะเจาะจงมากและไม่มีใครแนะนำให้ใช้การดำเนินการเปรียบเทียบและปล่อยให้การเพิ่มประสิทธิภาพของงานกับไดรเวอร์ OpenGL เป็นไปได้ และรวดเร็วขึ้นด้วยวิธีที่ง่ายและอ่านง่ายขึ้น

"สาขา" ไม่ใช่สิ่งเลวร้ายเสมอไป ตัวอย่างเช่น GPU SGX530 ที่ใช้ใน OpenPandora เครื่องวัดขนาด 2x นี้ (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;
    }

สิ้นสุดเร็วกว่า shader ที่เทียบเท่านี้ (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;

คุณไม่มีทางรู้ล่วงหน้าว่าคอมไพเลอร์ GLSL หรือ GPU เฉพาะจะทำงานได้อย่างไรจนกว่าคุณจะทำการทดสอบ


หากต้องการเพิ่มจุด (ถึงแม้ว่าฉันยังไม่มีหมายเลขกำหนดเวลาจริงและรหัส shader ที่จะนำเสนอให้คุณในส่วนนี้) ปัจจุบันฉันใช้เป็นฮาร์ดแวร์ทดสอบปกติของฉัน:

  • กราฟิก Intel HD 3000
  • กราฟิก Intel HD 405
  • nVidia GTX 560M
  • nVidia GTX 960
  • AMD Radeon R7 260X
  • nVidia GTX 1050

ในฐานะที่เป็นรุ่นที่แตกต่างกันทั่วไป GPU เพื่อทดสอบ

ทดสอบแต่ละรายการด้วย Windows, Linux กรรมสิทธิ์และไดรเวอร์โอเพ่นซอร์ส OpenGL & OpenCL

และทุกครั้งที่ฉันพยายามที่จะเพิ่มประสิทธิภาพ GLSL shader แบบไมโคร (ตามตัวอย่าง SGX530 ด้านบน) หรือการดำเนินการ OpenCL สำหรับหนึ่งใน GPU / ไดรเวอร์คอมโบโดยเฉพาะ ฉันก็จบลงด้วยประสิทธิภาพที่มากกว่าหนึ่งใน GPU / ไดรเวอร์อื่น ๆ

ดังนั้นนอกเหนือจากการลดความซับซ้อนทางคณิตศาสตร์ในระดับสูงอย่างชัดเจน(เช่น: แปลงหน่วยงานที่เหมือนกัน 5 หน่วยให้เป็นส่วนกลับเดี่ยวและ 5 หน่วยแทน) และลดการค้นหาพื้นผิว / แบนด์วิดท์มันน่าจะเป็นการเสียเวลาของคุณ

GPU ทุกตัวนั้นแตกต่างจากตัวอื่น ๆ

หากคุณกำลังทำงานโดยเฉพาะกับ (a) เกมคอนโซลที่มี GPU เฉพาะนี่จะเป็นเรื่องที่แตกต่างออกไป

ด้านอื่น ๆ (มีความสำคัญน้อยกว่าสำหรับผู้พัฒนาเกมขนาดเล็ก แต่ยังคงเป็นที่น่าประทับใจ) ในแง่นี้ก็คือไดรเวอร์ GPU ของคอมพิวเตอร์หนึ่งวันอาจแทนที่ shaders ของคุณได้อย่างเงียบ ๆ ( หากเกมของคุณเป็นที่นิยมพอ ) การทำเช่นนั้นใช้ได้สำหรับคุณทั้งหมด

พวกเขาจะทำเช่นนี้สำหรับเกมยอดนิยมที่มักใช้เป็นเกณฑ์มาตรฐาน

หรือถ้าคุณให้ผู้เล่นของคุณเข้าถึง shaders เพื่อให้พวกเขาสามารถแก้ไขตัวเองได้อย่างง่ายดายบางคนอาจบีบ FPS พิเศษบางอย่างเพื่อผลประโยชน์ของตนเอง

ตัวอย่างเช่นมี shader & texture pack ที่ทำให้แฟน ๆ หลงลืมเพื่อเพิ่มอัตราเฟรมบนฮาร์ดแวร์ที่เล่นไม่ได้

และในที่สุดเมื่อ shader ของคุณซับซ้อนพอเกมของคุณเกือบจะเสร็จและคุณเริ่มการทดสอบกับฮาร์ดแวร์ที่แตกต่างกันคุณจะยุ่งพอเพียงแค่กำหนด shaders ของคุณให้ทำงานได้กับ GPU ทุกประเภทเนื่องจากข้อบกพร่องต่าง ๆ ที่คุณไม่เคยทำ มีเวลาในการปรับให้เหมาะสมกับระดับนั้น


"หรือถ้าคุณให้ผู้เล่นของคุณเข้าถึง shaders เพื่อให้พวกเขาสามารถแก้ไขได้อย่างง่ายดาย ... " เนื่องจากคุณได้กล่าวถึงสิ่งนี้สิ่งที่เป็นวิธีการของคุณในการ wallhack shaders และชอบ? ให้เกียรติระบบตรวจสอบรายงาน ... ? ฉันชอบความคิดของล็อบบี้ที่ถูก จำกัด ให้กับ shaders / สินทรัพย์เดียวกันไม่ว่าพวกเขาจะเป็นอะไรก็ตามเนื่องจากสถานการณ์เกี่ยวกับความสมจริงสูงสุด / นาที / ที่ปรับขนาดได้การหาประโยชน์และอื่น ๆ ควรนำผู้เล่นและผู้กลั่นกรองร่วมกัน เพื่อให้จำได้ว่านี่เป็นวิธีการทำงานของ Gary's Mod แต่ฉันทำได้ดี
John P

1
@JohnP Security ไม่ว่าอะไรก็ตามที่ถือว่าลูกค้าไม่ได้ผลก็ไม่สามารถใช้งานได้ แน่นอนว่าถ้าคุณไม่ต้องการให้คนอื่นแก้ไขเฉดสีของพวกเขามันก็ไม่มีประเด็นที่จะเปิดเผยพวกเขา แต่มันก็ไม่ได้ช่วยอะไรมากมายเกี่ยวกับความปลอดภัย กลยุทธ์ของคุณในการตรวจจับสิ่งต่าง ๆ เช่นวอลแฮ็คควรปฏิบัติกับฝั่งไคลเอ็นต์ที่ยุ่งเหยิงกับสิ่งต่าง ๆ ซึ่งเป็นสิ่งกีดขวางแรกและอาจมีประโยชน์ที่มากขึ้นในการอนุญาตให้ปรับแสงเช่นเดียวกับคำตอบนี้หากไม่นำไปสู่ .
คิว

8
@ JohnP หากคุณไม่ต้องการให้ผู้เล่นมองทะลุกำแพงอย่าปล่อยให้เซิร์ฟเวอร์ส่งข้อมูลใด ๆ เกี่ยวกับสิ่งที่อยู่หลังกำแพง
Polygnome

1
แค่นั้นแหละ - ฉันไม่ได้แฮ็คกำแพงระหว่างผู้เล่นที่ชอบมันไม่ว่าด้วยเหตุผลใด อย่างไรก็ตามในฐานะผู้เล่นฉันได้ละทิ้งชื่อ AAA หลายอย่างเพราะ - ด้วยเหตุผลอื่น - พวกเขาทำตัวอย่างของ modders ความงามในขณะที่เงิน / XP / ฯลฯ แฮกเกอร์ไปโดยไม่ได้รับบาดเจ็บ (ซึ่งทำเงินจริงจากผู้ที่ผิดหวังมากพอที่จะจ่ายให้) ทำระบบรายงานและการอุทธรณ์ที่ไม่เข้าใจและทำให้เป็นระบบอัตโนมัติ ฉันหวังว่าอาจมีวิธีการกระจายอำนาจมากกว่าทั้งผู้พัฒนาและผู้เล่น
John P

ไม่ฉันจะไม่ทำแบบอินไลน์หากมี ฉันเพิ่งลอย (คำสั่งบูลีน) * (บางอย่าง)
Geklmintendonna จาก Awesome

7

@Stephane Hockenhull มีคำตอบให้คุณในสิ่งที่คุณจำเป็นต้องรู้มันจะขึ้นอยู่กับฮาร์ดแวร์ทั้งหมด

แต่ให้ฉันให้คุณตัวอย่างบางส่วนของวิธีการที่จะสามารถขึ้นอยู่กับฮาร์ดแวร์และเหตุผลที่แตกแขนงก็เป็นปัญหาที่ทุกสิ่งที่ไม่ GPU ทำเบื้องหลังเมื่อแตกแขนงไม่ใช้สถานที่

โฟกัสของฉันคือ Nvidia เป็นหลักฉันมีประสบการณ์กับการเขียนโปรแกรม CUDA ระดับต่ำและฉันเห็นPTX ( IRสำหรับเคอร์เนล CUDA เช่นSPIR-Vแต่สร้างขึ้นสำหรับ Nvidia) และดูเกณฑ์มาตรฐานของการเปลี่ยนแปลงบางอย่าง

เหตุใดการแตกแขนงในสถาปัตยกรรม GPU จึงเป็นเรื่องใหญ่

ทำไมการแตกกิ่งตอนแรกจึงไม่ดี ทำไม GPU จึงพยายามหลีกเลี่ยงการแตกกิ่งในตอนแรก เพราะ GPUs มักจะใช้รูปแบบที่หัวข้อแบ่งปันเดียวกันชี้การเรียนการสอน GPUs ติดตามสถาปัตยกรรม SIMDโดยทั่วไปและในขณะที่ความละเอียดของสิ่งนั้นอาจเปลี่ยนแปลงได้ (เช่น 32 เธรดสำหรับ Nvidia, 64 สำหรับ AMD และอื่น ๆ ) ในบางระดับกลุ่มของเธรดจะใช้ตัวชี้คำสั่งเดียวกัน ซึ่งหมายความว่าเธรดเหล่านั้นจำเป็นต้องดูที่บรรทัดของรหัสเดียวกันเพื่อทำงานร่วมกันในปัญหาเดียวกัน คุณอาจถามว่าพวกเขาสามารถใช้รหัสบรรทัดเดียวกันและทำสิ่งต่าง ๆ ได้อย่างไร พวกเขาใช้ค่าที่แตกต่างกันในการลงทะเบียน แต่การลงทะเบียนเหล่านั้นยังคงใช้ในบรรทัดของรหัสเดียวกันทั่วทั้งกลุ่ม จะเกิดอะไรขึ้นถ้านั่นไม่เกิดขึ้น (IE a branch?) หากโปรแกรมไม่มีทางอยู่รอบ ๆ มันจะแยกกลุ่ม (Nvidia เช่นการรวมกลุ่มของ 32 เธรดเรียกว่าWarpสำหรับ AMD และสถาบันการคำนวณแบบขนานมันถูกเรียกว่าwavefront) ในกลุ่มที่แตกต่างกันสองกลุ่มขึ้นไป

หากมีโค้ดที่แตกต่างกันเพียงสองบรรทัดเท่านั้นคุณจะสิ้นสุดเธรดการทำงานจะถูกแบ่งออกเป็นสองกลุ่ม (จากที่นี่จะเรียกว่า warps) สมมติว่าสถาปัตยกรรม Nvidia ซึ่งขนาดวาร์ปเป็น 32 ถ้าครึ่งหนึ่งของเธรดเหล่านี้แตกต่างจากนั้นคุณจะมี 2 warps ที่มีเธรดที่ใช้งานอยู่ 32 เธรดซึ่งทำให้สิ่งต่าง ๆ มีประสิทธิภาพครึ่งหนึ่งจากการคำนวณจนถึงจุดจบ ในสถาปัตยกรรมจำนวนมาก GPU จะพยายามแก้ไขสิ่งนี้โดยการรวมเธรดกลับเป็นวิปริตเดี่ยวเมื่อพวกเขาไปถึงสาขาโพสต์คำสั่งเดียวกันหรือคอมไพเลอร์จะวางจุดประสานอย่างชัดเจนซึ่งบอกให้ GPU รวมเธรดกลับมาหรือพยายาม

ตัวอย่างเช่น:

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

เธรดมีศักยภาพที่แข็งแกร่งในการเบี่ยงเบน (เส้นทางการสอนที่แตกต่างกัน) ดังนั้นในกรณีเช่นนี้คุณอาจมีคอนเวอร์เจนซ์เกิดขึ้นในr += t;ที่ที่ตัวชี้คำสั่งจะเหมือนเดิมอีกครั้ง ความแตกต่างยังสามารถเกิดขึ้นได้กับมากกว่าสองสาขาส่งผลให้การใช้วิปริตลดลงสี่สาขาหมายถึง 32 กระทู้ได้แบ่งออกเป็น 4 warps, 25% throughput การใช้งาน อย่างไรก็ตามการรวมเข้าด้วยกันสามารถซ่อนปัญหาเหล่านี้บางส่วนได้เนื่องจาก 25% ไม่ทำให้ปริมาณงานผ่านตลอดทั้งโปรแกรม

สำหรับ GPU ที่ซับซ้อนน้อยกว่าปัญหาอื่น ๆ สามารถเกิดขึ้นได้ แทนที่จะแยกพวกเขาเพียงคำนวณทุกสาขาแล้วเลือกเอาท์พุทในตอนท้าย สิ่งนี้อาจปรากฏเหมือนกับ divergence (ทั้งคู่มีการใช้ throughput 1 / n) แต่มีปัญหาสำคัญสองสามประการด้วยวิธีการทำซ้ำ

หนึ่งคือการใช้พลังงานคุณกำลังใช้พลังงานมากขึ้นเมื่อมีสาขาเกิดขึ้นซึ่งจะไม่ดีสำหรับ gpus มือถือ ประการที่สองคือความแตกต่างเกิดขึ้นเฉพาะกับ Nvidia gpus เมื่อเธรดของวาร์ปเดียวกันใช้เส้นทางที่แตกต่างกันดังนั้นจึงมีตัวชี้คำสั่งที่แตกต่างกัน ดังนั้นคุณยังสามารถแยกย่อยและไม่มีปัญหาปริมาณงานใน GPU ของ Nvidia หากเกิดขึ้นในหลาย ๆ 32 หรือเกิดขึ้นในวิปริตเดียวออกหลายสิบ หากมีแนวโน้มว่าสาขาจะเกิดขึ้นก็จะมีแนวโน้มลดจำนวนเธรดที่น้อยลงและคุณจะไม่มีปัญหาการแตกสาขา

อีกปัญหาที่เล็กกว่าคือเมื่อคุณเปรียบเทียบ GPU กับซีพียูพวกเขามักจะไม่มีกลไกการทำนายและกลไกสาขาที่แข็งแกร่งอื่น ๆ เนื่องจากฮาร์ดแวร์เหล่านั้นใช้กลไกได้มากแค่ไหนคุณมักจะเห็นว่าไม่มี op-opสำหรับ GPU สมัยใหม่เนื่องจากสิ่งนี้

ตัวอย่างความแตกต่างทางสถาปัตยกรรมของ GPU ในทางปฏิบัติ

ตอนนี้ลองมาเป็นตัวอย่างของสเตฟานีแล้วดูว่าชุดประกอบจะมีลักษณะอย่างไรสำหรับการแก้ปัญหาแบบไร้สาขาบนสถาปัตยกรรมเชิงทฤษฎีทั้งสอง

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

เช่นเดียวกับ Stephane กล่าวเมื่อคอมไพเลอร์อุปกรณ์พบสาขามันอาจตัดสินใจใช้คำสั่งเพื่อ "เลือก" องค์ประกอบซึ่งจะจบลงโดยไม่มีการลงโทษสาขา นี่หมายถึงในอุปกรณ์บางอย่างที่จะถูกรวบรวมเป็นอย่างเช่น

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

ซึ่งเป็นสาขาน้อยและเทียบเท่า แต่ใช้วิธีคำแนะนำเพิ่มเติม เนื่องจากตัวอย่างของ Stephanes จะถูกรวบรวมไว้ในระบบของตนดังนั้นจึงไม่เหมาะสมที่จะลองคิดเลขด้วยตนเองเพื่อลบการแตกแขนงของเราเองเนื่องจากคอมไพเลอร์ตัวแรกของสถาปัตยกรรมอาจตัดสินใจรวบรวมเป็นรูปแบบที่สองแทน แบบฟอร์มที่เร็วขึ้น


5

ฉันเห็นด้วยกับทุกอย่างที่กล่าวไว้ในคำตอบของ @Stephane Hockenhull หากต้องการขยายในจุดสุดท้าย:

คุณไม่มีทางรู้ล่วงหน้าว่าคอมไพเลอร์ GLSL หรือ GPU เฉพาะจะทำงานได้อย่างไรจนกว่าคุณจะทำการทดสอบ

จริงที่สุด. นอกจากนี้ฉันเห็นคำถามประเภทนี้เกิดขึ้นค่อนข้างบ่อย แต่ในทางปฏิบัติฉันไม่ค่อยได้เห็นชิ้นส่วนที่เป็นแหล่งของปัญหาเรื่องประสิทธิภาพ มันเป็นเรื่องธรรมดามากที่ปัจจัยอื่น ๆ ทำให้เกิดปัญหาเช่นการอ่านสถานะจาก GPU มากเกินไปการสลับบัฟเฟอร์มากเกินไปทำงานมากเกินไปในการดึงสายเดียว ฯลฯ

กล่าวอีกนัยหนึ่งก่อนที่คุณจะกังวลเกี่ยวกับการปรับแต่ง shader แบบไมโครให้เพิ่มประสิทธิภาพโปรไฟล์แอปทั้งหมดของคุณ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.