คำถาม:
ฉันทามติของอุตสาหกรรมซอฟต์แวร์คือรหัสที่สะอาดและเรียบง่ายเป็นพื้นฐานของความมีชีวิตในระยะยาวของฐานรหัสและองค์กรที่เป็นเจ้าของ คุณสมบัติเหล่านี้นำไปสู่การลดค่าใช้จ่ายในการบำรุงรักษาและเพิ่มโอกาสในการสร้างรหัสฐานอย่างต่อเนื่อง
อย่างไรก็ตามรหัส SIMD นั้นแตกต่างจากรหัสแอปพลิเคชันทั่วไปและฉันต้องการทราบว่ามีฉันทามติที่คล้ายกันเกี่ยวกับรหัสที่สะอาดและใช้งานง่ายที่ใช้เฉพาะกับรหัส SIMD
พื้นหลังคำถามของฉัน
ฉันเขียนโค้ด SIMD (คำสั่งคำสั่งเดียวหลายข้อมูล) มากมายสำหรับการประมวลผลภาพและการวิเคราะห์ที่หลากหลาย เมื่อเร็ว ๆ นี้ฉันยังต้องพอร์ตฟังก์ชั่นเหล่านี้จำนวนเล็กน้อยจากสถาปัตยกรรมหนึ่ง (SSE2) ไปยังอีก (ARM NEON)
รหัสนี้เขียนขึ้นสำหรับซอฟต์แวร์ที่มีการย่อขนาดดังนั้นจึงไม่สามารถขึ้นอยู่กับภาษาที่เป็นกรรมสิทธิ์หากไม่มีสิทธิ์การแจกจ่ายซ้ำเช่น MATLAB
ตัวอย่างของโครงสร้างรหัสทั่วไป:
- ใช้ประเภทเมทริกซ์ของOpenCV (
Mat
)สำหรับการจัดการหน่วยความจำบัฟเฟอร์และอายุการใช้งานทั้งหมด - หลังจากตรวจสอบขนาด (ขนาด) ของอาร์กิวเมนต์อินพุตพอยน์เตอร์ไปยังที่อยู่เริ่มต้นของแต่ละแถวของพิกเซลจะถูกนำมาใช้
- จำนวนพิกเซลและที่อยู่เริ่มต้นของพิกเซลแต่ละแถวจากเมทริกซ์อินพุตแต่ละอันจะถูกส่งผ่านไปยังฟังก์ชัน C ++ ระดับต่ำ
- ฟังก์ชัน C ++ ระดับต่ำเหล่านี้ใช้ SIMD ภายใน (สำหรับสถาปัตยกรรม IntelและARM NEON ) โหลดจากและบันทึกไปยังที่อยู่ตัวชี้แบบดิบ
- ลักษณะของฟังก์ชั่น C ++ ระดับต่ำเหล่านี้:
- สิทธิพิเศษหนึ่งมิติ (ต่อเนื่องกันในหน่วยความจำ)
- ไม่จัดการกับการจัดสรรหน่วยความจำ
(การจัดสรรทุกครั้งรวมถึงเรื่องชั่วคราวได้รับการจัดการโดยรหัสภายนอกโดยใช้เครื่องมืออำนวยความสะดวก OpenCV) - ช่วงของความยาวชื่อของสัญลักษณ์ (ภายในชื่อตัวแปร ฯลฯ ) มีความยาวประมาณ 10 - 20 อักขระซึ่งค่อนข้างมาก
(อ่านอย่าง techno-babble) - การใช้ตัวแปร SIMD ซ้ำนั้นไม่ได้รับผลกระทบเนื่องจากคอมไพเลอร์ค่อนข้างใช้งานได้อย่างถูกต้องในการวิเคราะห์โค้ดที่ไม่ได้เขียนในรูปแบบการเข้ารหัส "การมอบหมายงานเดี่ยว"
(ฉันได้ยื่นรายงานข้อผิดพลาดคอมไพเลอร์หลายฉบับ)
การเขียนโปรแกรม SIMD ด้านใดที่จะทำให้การสนทนาแตกต่างจากกรณีทั่วไป? หรือทำไม SIMD ถึงแตกต่าง?
ในแง่ของต้นทุนการพัฒนาเริ่มต้น
- เป็นที่ทราบกันดีว่าค่าใช้จ่ายในการพัฒนาเริ่มต้นของรหัส C ++ SIMD ที่มีประสิทธิภาพดีอยู่ที่ประมาณ 10x - 100x (โดยมีระยะห่างกว้าง) เมื่อเทียบกับรหัส C ++ ที่เขียนขึ้นแบบไม่ตั้งใจ
- ตามที่ระบุไว้ในคำตอบของการเลือกระหว่างประสิทธิภาพเทียบกับอ่าน / ทำความสะอาดรหัส? รหัส (รวมถึงรหัสตั้งใจเขียนและรหัส SIMD) เป็นครั้งแรกไม่สะอาดมิได้อย่างรวดเร็ว
- การปรับปรุงวิวัฒนาการของประสิทธิภาพการทำงานของรหัส (ทั้ง scalar และ SIMD code) นั้นไม่ได้รับการสนับสนุน (เพราะถูกมองว่าเป็นการทำงานซ้ำของซอฟต์แวร์ ) และไม่มีการติดตามต้นทุนและผลประโยชน์
ในแง่ของความเอนเอียง
(เช่นหลักการ Pareto, aka กฎ 80-20 )
- แม้ว่าการประมวลผลภาพจะมีเพียง 20% ของระบบซอฟต์แวร์ (ทั้งในขนาดรหัสและฟังก์ชันการทำงาน) การประมวลผลภาพจะค่อนข้างช้า (เมื่อดูเป็นเปอร์เซ็นต์ของเวลา CPU ที่ใช้) ใช้เวลามากกว่า 80%
- นี่คือสาเหตุที่ผลขนาดข้อมูล: ขนาดภาพทั่วไปวัดเป็นเมกะไบต์ในขณะที่ขนาดทั่วไปของข้อมูลที่ไม่ใช่ภาพวัดเป็นกิโลไบต์
- ภายในรหัสการประมวลผลภาพโปรแกรมเมอร์ SIMD ได้รับการฝึกฝนให้จดจำรหัส 20% ที่ประกอบไปด้วยฮอตสปอตโดยอัตโนมัติโดยระบุโครงสร้างลูปในรหัส C ++ ดังนั้นจากมุมมองของโปรแกรมเมอร์ SIMD 100% ของ "รหัสที่มีความสำคัญ" จึงเป็นปัญหาคอขวดของประสิทธิภาพการทำงาน
- บ่อยครั้งในระบบการประมวลผลภาพฮอตสปอตหลายจุดมีอยู่และใช้เวลาในสัดส่วนที่ใกล้เคียงกัน ตัวอย่างเช่นอาจมีฮอตสปอต 5 แห่งแต่ละแห่งใช้เวลา (20%, 18%, 16%, 14%, 12%) ของเวลาทั้งหมด เพื่อให้ได้ประสิทธิภาพสูงฮอตสปอตทั้งหมดจำเป็นต้องเขียนใหม่ใน SIMD
- ซึ่งสรุปได้ว่าเป็นกฎการตอกบอลลูน: บอลลูนไม่สามารถตอกสองครั้งได้
- สมมติว่ามีบอลลูนบางส่วนพูด 5 ของพวกเขา วิธีเดียวที่จะฆ่าพวกมันได้คือการทำให้พวกมันทีละตัว
- เมื่อบอลลูนแรกถูกตอกขึ้นมาบอลลูนที่เหลืออีก 4 ลูกจะประกอบด้วยเปอร์เซ็นต์ที่สูงขึ้นของเวลาดำเนินการทั้งหมด
- เพื่อให้ได้กำไรเพิ่มขึ้นหนึ่งจะต้องปรากฏขึ้นอีกบอลลูน
(นี่เป็นการฝ่าฝืนกฎการเพิ่มประสิทธิภาพ 80-20: ผลลัพธ์ทางเศรษฐกิจที่ดีสามารถทำได้หลังจากที่เก็บผลไม้ต่ำสุด 20%)
ในแง่ของการอ่านและการบำรุงรักษา
รหัส SIMD อ่านยาก
- สิ่งนี้เป็นจริงแม้ว่าหนึ่งในนั้นจะเป็นไปตามหลักปฏิบัติทางวิศวกรรมซอฟต์แวร์ที่ดีที่สุดเช่นการตั้งชื่อการห่อหุ้มความถูกต้องของ const (และทำให้ผลข้างเคียงชัดเจน) การสลายตัวของฟังก์ชัน ฯลฯ
- สิ่งนี้เป็นจริงแม้สำหรับโปรแกรมเมอร์ SIMD ที่มีประสบการณ์
รหัส SIMD ที่เหมาะสมที่สุดมีการบิดเบี้ยวมาก(ดูหมายเหตุ)เปรียบเทียบกับรหัสต้นแบบ C ++ ที่เทียบเท่ากัน
- มีหลายวิธีในการเปลี่ยนรหัส SIMD แต่เพียง 1 ใน 10 ของความพยายามดังกล่าวจะได้ผลลัพธ์ที่รวดเร็ว
- (นั่นคือในเพลงของการเพิ่มประสิทธิภาพ 4x-10x เพื่อแสดงให้เห็นถึงต้นทุนการพัฒนาที่สูงขึ้นแม้จะได้รับการปฏิบัติที่สูงขึ้นในทางปฏิบัติ)
(หมายเหตุ)
นี่คือวิทยานิพนธ์หลักของโครงการ MIT Halideโดยอ้างถึงหัวข้อคำต่อท้ายกระดาษ:
"การแยกอัลกอริธึมจากการกำหนดตารางเวลาเพื่อให้การปรับระบบท่อประมวลผลภาพทำได้ง่าย"
ในแง่ของการบังคับใช้ล่วงหน้า
- รหัส SIMD เชื่อมโยงกับสถาปัตยกรรมเดียวอย่างเคร่งครัด สถาปัตยกรรมใหม่แต่ละอัน (หรือการลงทะเบียน SIMD ที่กว้างขึ้น) ต้องมีการเขียนใหม่
- ต่างจากการพัฒนาซอฟต์แวร์ส่วนใหญ่โดยทั่วไปแล้วรหัส SIMD แต่ละชิ้นจะถูกเขียนเพื่อจุดประสงค์เดียวที่ไม่เคยเปลี่ยนแปลง
(ยกเว้นการย้ายไปยังสถาปัตยกรรมอื่น) - สถาปัตยกรรมบางรุ่นรักษาความเข้ากันได้แบบย้อนกลับได้อย่างสมบูรณ์แบบ (Intel) บางคนขาดตลาดเล็กน้อย (ARM AArch64, แทนที่
vtbl
ด้วยvtblq
) แต่ก็เพียงพอที่จะทำให้โค้ดบางตัวไม่สามารถคอมไพล์ได้
ทั้งในด้านทักษะและการฝึกอบรม
- ยังไม่ชัดเจนว่าสิ่งที่จำเป็นต้องมีความรู้จะต้องฝึกอบรมโปรแกรมเมอร์ใหม่อย่างถูกต้องในการเขียนและรักษารหัส SIMD
- ผู้สำเร็จการศึกษาระดับวิทยาลัยที่เรียนรู้การเขียนโปรแกรม SIMD ในโรงเรียนดูเหมือนจะดูถูกและปฏิเสธว่าเป็นเส้นทางอาชีพที่ไม่เหมาะสม
- การแยกส่วนการอ่านและการทำโปรไฟล์ระดับต่ำถูกอ้างถึงว่าเป็นทักษะพื้นฐานสองประการสำหรับการเขียนโค้ด SIMD ประสิทธิภาพสูง อย่างไรก็ตามยังไม่มีความชัดเจนว่าจะฝึกอบรมโปรแกรมเมอร์อย่างเป็นระบบในทักษะทั้งสองนี้อย่างไร
- สถาปัตยกรรม CPU สมัยใหม่ (ซึ่งแตกต่างอย่างมากจากสิ่งที่สอนในตำรา) ทำให้การฝึกอบรมยากขึ้น
ในแง่ของความถูกต้องและต้นทุนที่เกี่ยวข้องกับข้อบกพร่อง
- ฟังก์ชั่นการประมวลผล SIMD เดียวมีความจริงเพียงพอที่สามารถสร้างความถูกต้องได้โดย:
- ใช้วิธีการที่เป็นทางการ(ด้วยปากกาและกระดาษ)และ
- การตรวจสอบการส่งออกจำนวนเต็มช่วง(มีรหัสต้นแบบและดำเนินการนอกเวลาทำงาน)
- อย่างไรก็ตามกระบวนการตรวจสอบนั้นมีค่าใช้จ่ายสูงมาก (ใช้เวลา 100% ในการตรวจสอบโค้ดและใช้เวลา 100% ในการตรวจสอบรูปแบบต้นแบบ) ซึ่งเพิ่มค่าใช้จ่ายในการพัฒนาของ SIMD เป็นสามเท่า
- หากมีข้อบกพร่องอย่างใดอย่างหนึ่งจัดการที่จะลื่นผ่านกระบวนการตรวจสอบนี้มันเกือบเป็นไปไม่ได้ที่จะ "ซ่อมแซม" (แก้ไข) ยกเว้นการแทนที่ (เขียนใหม่) ฟังก์ชั่นที่น่าสงสัยที่มีข้อบกพร่อง
- รหัส SIMD ทนทุกข์ทรมานจากความผิดพลาดของข้อบกพร่องในคอมไพเลอร์ C ++ (การปรับตัวสร้างโค้ดให้เหมาะสม)
- รหัส SIMD ที่สร้างขึ้นโดยใช้เท็มเพลตนิพจน์ C ++ ยังทนทุกข์ทรมานจากข้อบกพร่องของคอมไพเลอร์อย่างมาก
ในแง่ของนวัตกรรมก่อกวน
มีการนำเสนอโซลูชั่นจำนวนมากจากสถาบันการศึกษา แต่มีเพียงไม่กี่รายที่เห็นการใช้งานเชิงพาณิชย์อย่างแพร่หลาย
- MIT Halide
- Stanford Darkroom
- NT2 (กล่องเครื่องมือเท็มเพลตตัวเลข) และ Boost.SIMD ที่เกี่ยวข้อง
ดูเหมือนว่าไลบรารีที่มีการใช้งานเชิงพาณิชย์อย่างแพร่หลายดูเหมือนจะเปิดใช้งาน SIMD ได้ยาก
- ไลบรารีโอเพนซอร์สดูเหมือนจะอุ่น ๆ กับ SIMD
- เมื่อเร็ว ๆ นี้ฉันได้สังเกตสิ่งนี้โดยตรงหลังจากรวบรวมฟังก์ชั่น OpenCV API จำนวนมากซึ่งเป็นรุ่น 2.4.9
- ไลบรารีการประมวลผลรูปภาพอื่น ๆ ที่ฉันทำไว้ยังไม่ได้ใช้งาน SIMD อย่างหนักหรือพวกมันจะพลาดจุดฮอตสปอตที่แท้จริง
- ห้องสมุดพาณิชย์ดูเหมือนจะหลีกเลี่ยง SIMD ทั้งหมด
- ในบางกรณีฉันเคยเห็นไลบรารีการประมวลผลรูปภาพที่แปลงค่ารหัส SIMD ที่ปรับปรุงแล้วในรุ่นก่อนหน้าเป็นรหัสที่ไม่ใช่ SIMD ในรุ่นก่อนหน้าซึ่งส่งผลให้ประสิทธิภาพลดลงอย่างมาก
(การตอบสนองของผู้ขายคือจำเป็นต้องหลีกเลี่ยงข้อบกพร่องของคอมไพเลอร์)
- ในบางกรณีฉันเคยเห็นไลบรารีการประมวลผลรูปภาพที่แปลงค่ารหัส SIMD ที่ปรับปรุงแล้วในรุ่นก่อนหน้าเป็นรหัสที่ไม่ใช่ SIMD ในรุ่นก่อนหน้าซึ่งส่งผลให้ประสิทธิภาพลดลงอย่างมาก
- ไลบรารีโอเพนซอร์สดูเหมือนจะอุ่น ๆ กับ SIMD
คำถามของโปรแกรมเมอร์นี้: บางครั้งรหัสแฝงต่ำต้องเป็น "น่าเกลียด" หรือไม่? มีความเกี่ยวข้องและก่อนหน้านี้ฉันเขียนคำตอบสำหรับคำถามนั้นเพื่ออธิบายมุมมองของฉันเมื่อไม่กี่ปีที่ผ่านมา
อย่างไรก็ตามคำตอบนั้นเป็น "จุดจบ" ในมุมมอง "การปรับให้เหมาะสมก่อนวัยอันควร" ซึ่งก็คือมุมมองที่:
- การปรับให้เหมาะสมทั้งหมดมาก่อนกำหนดตามคำจำกัดความ (หรือระยะสั้นตามลักษณะ ) และ
- การเพิ่มประสิทธิภาพเพียงอย่างเดียวที่มีผลประโยชน์ระยะยาวคือความเรียบง่าย
แต่มุมมองดังกล่าวจะเข้าร่วมประกวดในครั้งนี้บทความ ACM
ทั้งหมดนี้ทำให้ฉันถาม:
รหัส SIMD แตกต่างจากรหัสแอปพลิเคชันทั่วไปและฉันต้องการทราบว่ามีฉันทามติอุตสาหกรรมที่คล้ายกันเกี่ยวกับมูลค่าของรหัสที่สะอาดและเรียบง่ายสำหรับรหัส SIMD หรือไม่