ตั้งแต่ Go 1.26 เราสนับสนุนโครงการทดลอง amd64 SIMD API ตอนนี้ใน Go 1.27 เรารองรับสถาปัตยกรรมเพิ่มเติมอีกสองสถาปัตยกรรม: arm64 และ wasm.
API ใหม่สามารถเข้าถึงได้โดยการกำหนดค่า GOEXPERIMENT=simd เมื่อสร้างโปรแกรม พวกมันถูกกำหนดไว้ใน simd/archsimd บรรจุุภัณฑ์.
archsimd เป็นโครงสร้างพื้นฐานระดับล่างนั่นเอง simd โดยพื้นฐานแล้วจะขึ้นอยู่กับเลเยอร์ที่แท้จริงในภาษาอื่น สำหรับการดำเนินการแปลกใหม่ที่มีอยู่ในสถาปัตยกรรมเฉพาะเท่านั้น ผู้ใช้สามารถเข้าถึงได้โดยการเปลี่ยนจาก simd ถึง archsimd (ผ่าน ToArch และ ). สำหรับรายละเอียด โปรดดูบล็อกโพสต์ของ simd
สำหรับผู้อ่านที่มีความรู้เกี่ยวกับ SIMD ในภาษาอื่นมาก่อน คุณอาจพบว่าของเรา archsimd API ไม่ได้สะท้อนถึงคำสั่งฮาร์ดแวร์ที่เกี่ยวข้องโดยตรง:
- เราตั้งชื่อที่สมเหตุสมผลให้กับ API ของเรา เช่น แทนที่จะตั้งชื่อ
_mm512_slli_epi64เราแค่เรียกมันว่าShiftAllLeft. - เราทิ้งรายละเอียดทางสถาปัตยกรรมบางส่วนไว้เพื่อเพิ่มประสิทธิภาพคอมไพเลอร์ แทนที่จะเปิดเผยใน API: เช่น แทน
_mm512_maskz_add_ps(m, x, y)เราเพิ่มประสิทธิภาพx.Add(y).Masked(m)ในคำสั่งเดียวกัน
การตัดสินใจในการออกแบบเหล่านี้ทำให้ API ของเรามีขนาดเล็กลงและเข้าถึงได้มากขึ้น และยังทำให้ simd การใช้งานที่ง่ายกว่ามาก
สำหรับผู้อ่านที่ไม่มีความรู้เกี่ยวกับ SIMD มาก่อน เราหวังว่า API ระดับต่ำของเราจะเป็นการเดินทางที่เป็นธรรมชาติและสนุกสนานสำหรับคุณ
กรุณาแบ่งปันความคิดเห็นและความคิดเห็นของคุณ ทุกอย่างเกี่ยวกับการใช้งาน ประสิทธิภาพ ฯลฯ ล้วนได้รับการชื่นชม ปัญหาหลักของโครงการนี้คือ #73787
SIMD คืออะไร
SIMD ย่อมาจาก Single Instruction, Multiple Data คำสั่ง SIMD ทำงานบนไวด์รีจิสเตอร์ (128 บิต, 256 บิต หรือมากกว่า) ที่มีองค์ประกอบ 8 ถึง 64 บิตหลายรายการ ดำเนินการกับองค์ประกอบทั้งหมดในแบบคู่ขนาน การลงทะเบียนที่กว้างขึ้นและการดำเนินการแบบขนานช่วยเพิ่มประสิทธิภาพที่สำคัญสำหรับอัลกอริธึมที่สามารถใช้ประโยชน์จากสิ่งเหล่านี้ได้
จนถึงขณะนี้ ใครก็ตามที่ต้องการใช้ SIMD ใน Go จะต้องอดทนกับอุปสรรคในการเขียนในภาษาแอสเซมบลี ใหม่ archsimd แพ็คเกจอยู่ที่นี่เพื่อเปลี่ยนแปลงสิ่งนั้น
เป้าหมาย
เป้าหมายคือเพื่อรองรับคำสั่ง SIMD ให้ได้มากที่สุดบนสถาปัตยกรรมที่แตกต่างกัน ปัจจุบันนี้เรามี amd64, arm64และ wasm มีอยู่. ของเรา amd64 การสนับสนุนมี API ที่ใหญ่ที่สุด ครอบคลุม AVX, AVX2 และส่วนขยาย AVX-512 จำนวนมาก ของเรา arm64 ขณะนี้การสนับสนุนครอบคลุมถึง NEON โดยมี SVE และ SVE2 บางส่วนอยู่ในระหว่างดำเนินการ ของเรา wasm การสนับสนุนเกือบจะเสร็จสมบูรณ์แล้วด้วยการออกแบบนามธรรม SIMD 128 บิตที่มีการกำหนดไว้อย่างดี
ผู้ใช้บางรายอาจสนใจส่วนขยายอาร์เรย์บนสถาปัตยกรรมบางอย่าง เช่น AMX และ SME เรายังไม่ได้ตัดสินใจว่าจะแสดงเมทริกซ์ใน Go อย่างมีประสิทธิภาพได้อย่างไร ดังนั้นจึงยังไม่รองรับ แต่เราอาจสนับสนุนเมทริกซ์เหล่านี้ในอนาคต
การออกแบบและการตั้งชื่อ API
ประเภท
SIMD API เฉพาะสถาปัตยกรรมถูกนำเข้ามาจาก simd/archsimd. archsimd ใช้โครงสร้างประเภทที่แตกต่างกัน เช่น Float32x4, Int32x8หรือ Uint8x16เพื่อส่งสัญญาณรูปร่างและประเภทขององค์ประกอบของแต่ละเวกเตอร์ และกำหนดการดำเนินการเป็นวิธีการในประเภทเหล่านั้น ตอนนี้, archsimd รองรับเฉพาะช่วงเวกเตอร์ที่มีความกว้างคงที่เท่านั้น ส่วนขยายเวกเตอร์ที่ปรับขนาดได้เช่น arm64 SVE และ RVV จะมีโครงสร้างที่แตกต่างกันเพื่อแสดงเวกเตอร์ ประเภทของมาสก์ยังมาพร้อมกับรูปร่างคล้ายเวกเตอร์ เช่น Mask32x4.
วิธีการ
ในความกว้างของเวกเตอร์ ประเภทองค์ประกอบ และสถาปัตยกรรมที่แตกต่างกัน เราใช้ชื่อวิธีการเดียวกันเมื่อใดก็ตามที่การดำเนินการมีความหมายเหมือนกัน:
- ไม่ว่าความกว้างของเวกเตอร์หรือสถาปัตยกรรมจะเป็นเช่นไร การบวกแบบองค์ประกอบต่อองค์ประกอบก็ทำได้ง่ายๆ
x.Add(y); ประเภทของxกำหนดคำสั่งที่จะออก แทนที่จะเพิ่มชื่อฟังก์ชันที่แตกต่างกัน 18 ชื่อ กลับมีชื่อเมธอดเพียงชื่อเดียวเท่านั้น - เมื่อคำแนะนำเกี่ยวกับสถาปัตยกรรมที่แตกต่างกันดูคล้ายกันตั้งแต่แรกเห็น แต่แตกต่างกันในลักษณะการทำงานของ Edge-Case เราจะตั้งชื่อให้ชัดเจน เพื่อที่ความแตกต่างจะได้ไม่รบกวนคุณ ตัวอย่างเช่น การค้นหาตารางขนาด 16 ไบต์
amd64(VPSHUFB) รีเซ็ตไบต์เอาต์พุตเมื่อดัชนีเป็นลบ (index < 0) และเกี่ยวข้องกับโมดูโล 16 ไม่เช่นนั้นเราจะเรียกมันว่าPermuteOrZero. บนarm64นีออน (VTBL) และwasm(i8x16.swizzle) ดัชนีใดๆ ที่อยู่นอกช่วง (index < 0 || index >= 16) รีเซ็ตไบต์เอาต์พุต ดังนั้นเราจึงเรียกมันว่าLookupOrZero. - บน
amd64คำสั่ง 256 บิตและ 512 บิตจำนวนมากทำงานอย่างเป็นอิสระบนเลน 128 บิต แทนที่จะทำงานบนรีจิสเตอร์ทั้งหมด เราลงท้ายวิธีการเหล่านี้อย่างชัดเจนด้วยGrouped(เช่นInterleaveLoGroupedหรือPermuteOrZeroGrouped) เพื่อทำให้ขีดจำกัดแทร็ก 128 บิตชัดเจน
วิธีการดังกล่าวต้องใช้ค่าตัวรับ ซึ่งใช้ไม่ได้กับการสร้างเวกเตอร์หน่วยความจำหรือสเกลาร์ สำหรับสิ่งเหล่านั้น archsimd จัดเตรียมฟังก์ชันระดับแพ็คเกจที่ตั้งชื่อด้วยประเภทเวกเตอร์เป้าหมาย:
- ชิ้น (ค่าเริ่มต้น): ใน Go 1.27 การโหลดและจัดเก็บสไลซ์ใช้ชื่อที่สั้นกว่า:
archsimd.LoadFloat32x4(s []float32) Float32x4และx.Store(s []float32). สำหรับองค์ประกอบสุดท้ายที่สามารถสั้นกว่าเวกเตอร์เต็มได้archsimd.LoadFloat32x4Part(s []float32) (Float32x4, int)เติมเบาะแสที่เหลือเป็นศูนย์และส่งกลับจำนวนองค์ประกอบที่โหลดซึ่งจับคู่กับx.StorePart(s []float32) int. - อาร์เรย์และการส่งสัญญาณ: พอยน์เตอร์อาร์เรย์ขนาดคงที่ใช้
Arrayคำต่อท้าย (archsimd.LoadFloat32x4Array(y *[4]float32)และx.StoreArray(y *[4]float32)), และarchsimd.BroadcastFloat32x4(v float32)ส่งสเกลาร์ไปยังแทร็กทั้งหมด
พิมพ์การตีความใหม่
การตีความซ้ำคือการแปลงประเภทโดยไม่มีค่าใช้จ่าย อัลกอริธึม SIMD มักจะตีความบิตใหม่เป็นประเภทองค์ประกอบและความกว้าง
ในการทดลอง Go 1.26 เราได้นำสิ่งนี้ไปใช้ As วิธีการ ซึ่งได้รับผลกระทบจากการระเบิดกำลังสองของคู่ประเภท และไม่สามารถสรุปกับเวกเตอร์ที่ไม่ขึ้นกับความกว้างได้ simd หรือ EVS
ใน Go 1.27 เราได้แทนที่ด้วยการแปลงที่เขียนได้แบบไม่มีค่าใช้จ่าย:
ToBits()ตีความเวกเตอร์จำนวนเต็มที่ลงนามหรือเวกเตอร์ลอยเป็นเวกเตอร์จำนวนเต็มที่ไม่ได้ลงนามด้วยความกว้างองค์ประกอบเดียวกัน (เช่นInt32x4.ToBits() -> Uint32x4), และBitsToInt32()/BitsToFloat32()แปลงกลับReshapeToUintในเวกเตอร์ที่ไม่ได้ลงนามจะเปลี่ยนความกว้างขององค์ประกอบภายในความกว้างเดียวกันกับบันทึก ตัวอย่างเช่น ถ้าs() xคือarchsimd.Uint8x16แล้วx.ReshapeToUint32s().BitsToFloat32()ตีความชิ้นส่วนของxเหมือนกarchsimd.Float32x4โดยมีค่าใช้จ่ายรันไทม์เป็นศูนย์
มาสก์
สถาปัตยกรรมที่แตกต่างกันเข้ารหัสมาสก์เวกเตอร์แตกต่างกันมาก เช่น k มาสก์ลงทะเบียนบน AVX-512, บิตมาสก์เวกเตอร์แบบเต็มบน AVX/AVX2, NEON และ wasmและบันทึกภาคแสดงใน EVS หากต้องการซ่อนรายละเอียดฮาร์ดแวร์นี้ archsimd ให้ความทึบแสง Mask ประเภท (เช่น Mask32x4) รวมรูปร่างเวกเตอร์แต่ละรูปเข้าด้วยกัน:
- การเปรียบเทียบเช่น
x.Greater(y)ผลิตMask. x.Masked(m)องค์ประกอบศูนย์อยู่ที่ไหนmเป็นเท็จและx.IfElse(m, y)(ซึ่งมาแทนที่.Mergeจาก Go 1.26) เลือกองค์ประกอบจากxที่ไหนmมันเป็นเรื่องจริงและyที่ไหนmมันเป็นเท็จ- การดำเนินการมาสก์ช่องมองคอมไพเลอร์พร้อมคำแนะนำโดยรอบ: ใน AVX-512
x.Add(y).Masked(m)หรือx.Add(y).IfElse(m, z)คอมไพล์เป็นมาสก์ศูนย์เดียวหรือรวมมาสก์VPADDคำแนะนำ; ใน AVX2, NEON หรือwasmมันลดลงเป็นระดับบิตที่เหมาะสมANDหรือคำสั่งผสมผสาน/บิตเลือก
ตัวอย่าง
อัลกอริธึมแบบพกพาเป็นผลิตภัณฑ์ภายในสามารถนำไปใช้งานได้อย่างง่ายดายโดยใช้อุปกรณ์พกพา simd แพ็คเกจตามที่แสดงในโพสต์บล็อก simd อย่างไรก็ตาม เรายังคงเปิดเผย archsimd เพื่อสำรวจคำสั่งเฉพาะทางสถาปัตยกรรมที่แปลกใหม่ซึ่งไม่เหมาะกับ API ทางแยกแบบพกพา หรือคำสั่งที่อาจทำสิ่งต่าง ๆ ได้เร็วกว่าที่คาดไว้ simd จัดเตรียมให้.
บน amd64ส่วนขยาย GFNI เปิดเผย GaloisFieldAffineTransform การดำเนินงาน นอกเหนือจากสิ่งที่สนาม Galois คืออะไร การดำเนินการเหล่านี้ยังทำสิ่งง่ายๆ อย่างหนึ่ง:
// Each element of A is interpreted as a matrix of 8 rows of vectors of 8 bits.
// Each element of x is interpreted as a vector of 8 bits.
// The b argument is likewise a vector of 8 bits.
// The result is z[i] = (A[i/8] * x[i]) + b.
//
// The matrix * and vector + follow the usual rules for linear algebra,
// but based on AND and XOR for scalar * and +
//
// Asm: VGF2P8AFFINEQB, CPU Feature: AVX512GFNI
func (x Uint8x16) GaloisFieldAffineTransform(A Uint64x2, b uint8) Uint8x16
แต่ละเมทริกซ์ 8×8 บิต A ถูกบีบอัดทีละบรรทัดให้เป็นหนึ่งเดียว uint64. เนื่องจากการรวมเชิงเส้นหรือการเรียงสับเปลี่ยนของ 8 บิตภายในไบต์สามารถเขียนเป็นเมทริกซ์ 8×8 บิตได้ คำสั่งเดียวนี้จึงเป็นมีดของ Swiss Army สำหรับการจัดการบิตระดับไบต์
ตัวอย่างเช่น การกลับลำดับบิตของแต่ละไบต์ (bits.Reverse8) โดยทั่วไปต้องใช้ตารางการค้นหาแบบ nibble มาสก์ กะ และ OR ใน SIMD กับ GaloisFieldAffineTransformเราสามารถคูณแต่ละไบต์ด้วยเมทริกซ์เอกลักษณ์ต้านเส้นทแยงมุม 8×8 (0x8040201008040201) เพื่อย้อนกลับทั้งหมด 8 บิตขนาด 64 ไบต์ในคำสั่งเดียว:
/*
Anti-diagonal 8x8 bit matrix (0x8040201008040201):
[ 0 0 0 0 0 0 0 1 ] [ x7 ] [ x0 ]
[ 0 0 0 0 0 0 1 0 ] [ x6 ] [ x1 ]
[ 0 0 0 0 0 1 0 0 ] [ x5 ] [ x2 ]
[ 0 0 0 0 1 0 0 0 ] * [ x4 ] = [ x3 ]
[ 0 0 0 1 0 0 0 0 ] [ x3 ] [ x4 ]
[ 0 0 1 0 0 0 0 0 ] [ x2 ] [ x5 ]
[ 0 1 0 0 0 0 0 0 ] [ x1 ] [ x6 ]
[ 1 0 0 0 0 0 0 0 ] [ x0 ] [ x7 ]
*/
func ReverseBits(dst, src []uint8) {
if !archsimd.X86.AVX512GFNI() {
// A slow fallback emulation impl, details omitted.
slowReverseBits(dst, src)
return
}
if len(src) > len(dst) {
// Make sure src and dst are the same size
src = src[:len(dst)]
}
revMatrix := archsimd.BroadcastUint64x8(0x8040201008040201)
var v archsimd.Uint8x64
var i int
for i = 0; i < len(src)-v.Len()+1; i += v.Len() {
v = archsimd.LoadUint8x64(src[i : i+v.Len()])
v.GaloisFieldAffineTransform(revMatrix, 0).Store(dst[i : i+v.Len()])
}
if i < len(src) {
v, _ = archsimd.LoadUint8x64Part(src[i:])
v.GaloisFieldAffineTransform(revMatrix, 0).StorePart(dst[i:])
}
}
อีกตัวอย่างหนึ่งคือการขนย้ายเมทริกซ์ บน amd64คำแนะนำในการเรียงสับเปลี่ยนมีรูปร่างในลักษณะที่ไม่สามารถพกพาได้ ดังนั้นจึงใช้ simd มันซับซ้อนและอาจมีการจำลอง การใช้งานที่มีประสิทธิภาพสามารถเขียนได้โดยตรงมากขึ้น archsimd. ตัวอย่างด้านล่างนี้จะย้ายเมทริกซ์ขนาด 8 x 8 ของจำนวนเต็ม 32 บิตไปเป็นรีจิสเตอร์ amd64 โดยใช้การสลับกลุ่มบนแทร็ก AVX2 128 บิต (InterleaveLoGrouped, InterleaveHiGrouped) และการเรียงสับเปลี่ยนระหว่างแทร็ก (ConcatPermuteScalarsGrouped, ConcatPermute128Scalars):
func Transpose8(a0, a1, a2, a3, a4, a5, a6, a7 archsimd.Int32x8) (
b0, b1, b2, b3, b4, b5, b6, b7 archsimd.Int32x8) {
if !archsimd.X86.AVX2() {
// A slow fallback emulation impl, details omitted.
return slowTranspose8(a0, a1, a2, a3, a4, a5, a6, a7)
}
/*
LOW HIGH
a0: abcd efgh
a1: ijkl mnop
a2: qrst uvwx
a3: 0123 4567
a4: ABCD EFGH
a5: IJKL MNOP
a6: QRST UVWX
a7: 89yz YZ$@
*/
t0 := a0.InterleaveLoGrouped(a1) // t0 = aibj emfn
t1 := a0.InterleaveHiGrouped(a1) // t1 = ckdl gohp
t2 := a2.InterleaveLoGrouped(a3) // t2 = q0r1 u4v5
t3 := a2.InterleaveHiGrouped(a3) // t3 = s2t3 w6x7
t4 := a4.InterleaveLoGrouped(a5) // t4 = AIBJ EMFN
t5 := a4.InterleaveHiGrouped(a5) // t5 = CKDL GOHP
t6 := a6.InterleaveLoGrouped(a7) // t6 = Q8R9 UYVZ
t7 := a6.InterleaveHiGrouped(a7) // t7 = SyTz W$X@
a0 = t0.ConcatPermuteScalarsGrouped(0, 1, 4, 5, t2) // a0 = aiq0 emu4
a1 = t0.ConcatPermuteScalarsGrouped(2, 3, 6, 7, t2) // a1 = bjr1 fnv5
a2 = t1.ConcatPermuteScalarsGrouped(0, 1, 4, 5, t3) // a2 = cks2 gow6
a3 = t1.ConcatPermuteScalarsGrouped(2, 3, 6, 7, t3) // a3 = dlt3 hpx7
a4 = t4.ConcatPermuteScalarsGrouped(0, 1, 4, 5, t6) // a4 = AIQ8 EMUY
a5 = t4.ConcatPermuteScalarsGrouped(2, 3, 6, 7, t6) // a5 = BJR9 FNVZ
a6 = t5.ConcatPermuteScalarsGrouped(0, 1, 4, 5, t7) // a6 = CKSy GOW$
a7 = t5.ConcatPermuteScalarsGrouped(2, 3, 6, 7, t7) // a7 = DLTz HPX@
b0 = a0.ConcatPermute128Scalars(0, 2, a4) // b0 = aiq0 AIQ8
b1 = a1.ConcatPermute128Scalars(0, 2, a5) // b1 = bjr1 BJR9
b2 = a2.ConcatPermute128Scalars(0, 2, a6) // b2 = cks2 CKSy
b3 = a3.ConcatPermute128Scalars(0, 2, a7) // b3 = dlt3 DLTz
b4 = a0.ConcatPermute128Scalars(1, 3, a4) // b4 = emu4 EMUY
b5 = a1.ConcatPermute128Scalars(1, 3, a5) // b5 = fnv5 FNVZ
b6 = a2.ConcatPermute128Scalars(1, 3, a6) // b6 = gow6 GOW$
b7 = a3.ConcatPermute128Scalars(1, 3, a7) // b7 = hpx7 HPX@
return
}
แนวปฏิบัติที่ดี
เมื่อคุณดูตัวอย่างข้างต้น คุณอาจมีคำถามสองสามข้อ:
- มันคืออะไร
archsimd.X86.AVX512GFNI()และฉันสามารถละเว้นได้ไหม? - เหตุใด strided loop จึงเขียนเป็น
i = 0; i < len(src)-v.Len()+1; i += v.Len()แทนi = 0; i + v.Len() <= len(src); i += v.Len()? - ทำไม
Transpose8ใช้พารามิเตอร์เวกเตอร์ 8 ตัวแทนโครงสร้างหรือ a[8]archsimd.Int32x8ความหลากหลาย?
เมื่อมองแวบแรก ตัวเลือกเหล่านี้อาจดูเหมือนเป็นการตั้งค่าสไตล์เล็กน้อย แต่ตัวเลือกแต่ละรายการได้รับเลือกด้วยเหตุผลด้านประสิทธิภาพที่สำคัญหรือความถูกต้อง:
-
การตรวจสอบทรัพยากร CPU (
archsimd.X86.*,archsimd.ARM64.*)บน
amd64เราจัดให้มีการตรวจสอบทรัพยากรรันไทม์เช่นarchsimd.X86.AVX(),AVX2(),AVX512()และการตรวจสอบส่วนขยายเช่นAVX512GFNI()หรือAVX512VNNI(); บนarm64NEON พร้อมให้บริการเสมอและเราให้การตรวจสอบส่วนขยายเพิ่มเติมเช่นarchsimd.ARM64.PMULL(),SVE()และSVE2(). คอมไพเลอร์ไม่ทราบล่วงหน้าว่าเครื่องที่ใช้ไบนารี่ของคุณรองรับส่วนขยาย SIMD เสริมหรือไม่ หากคุณไม่ปกป้องรหัส SIMD ของคุณด้วยการตรวจสอบทรัพยากรที่เหมาะสม โปรแกรมของคุณอาจขัดข้องด้วยSIGILLบนฮาร์ดแวร์ที่ไม่มีคำแนะนำการตรวจสอบทรัพยากร CPU ก็มีความสำคัญไม่แพ้กัน เคล็ดลับการเพิ่มประสิทธิภาพคอมไพเลอร์. ตัวอย่างเช่น AVX-512 รองรับการมาสก์การผสานฮาร์ดแวร์บนเอาต์พุตคำสั่ง คอมไพเลอร์ลดลงอย่างไร
x.Add(y).IfElse(m, z)ในเวกเตอร์ 128 หรือ 256 บิตขึ้นอยู่กับทรัพยากร CPU ที่มีอยู่ หากรหัสไม่ได้รับการบันทึกโดยarchsimd.X86.AVX512()คอมไพเลอร์จะต้องปล่อยเวกเตอร์อย่างระมัดระวังVPADDตามด้วยกVPBLENDคำแนะนำ. ภายในกif archsimd.X86.AVX512()บล็อกอย่างไรก็ตามคอมไพเลอร์รู้ว่า AVX-512 พร้อมใช้งานและรวมลำดับเป็น AVX-512 เดียวด้วยการผสานมาสก์VPADDคำแนะนำ. -
กำจัดการตรวจสอบขอบเขตในลูปแบบก้าว
วิธีที่คุณเขียนขีดจำกัดการวนซ้ำจะกำหนดว่าคอมไพเลอร์
provepass สามารถกำจัดการตรวจสอบขอบเขตของการแบ่งส่วนภายในลูปได้ ในi + v.Len() <= len(src)นอกจากนี้i + v.Len()ในทางทฤษฎีสามารถล้นไปสู่ค่าลบได้intถ้าlen(src)อยู่ใกล้แล้วmath.MaxIntซึ่งป้องกันไม่ให้คอมไพเลอร์พิสูจน์สิ่งนั้นiไม่เป็นลบเสมอและอยู่ในขอบเขต การเขียนi < len(src)-v.Len()+1หลีกเลี่ยงการโอเวอร์โฟลว์ที่อาจเกิดขึ้นนี้โดยอนุญาตให้คอมไพล์เนื้อหาลูปด้วยการตรวจสอบขอบเขตเป็นศูนย์ -
เก็บเวกเตอร์ไว้ในทะเบียน (หลีกเลี่ยงประเภทคอมโพสิตขนาดใหญ่)
Transpose8ส่งผ่านพารามิเตอร์เวกเตอร์ 8 ตัวแทนที่จะเป็นโครงสร้างฟิลด์ 8 หรือ a[8]archsimd.Int32x8array เนื่องจากปัจจุบันแบ็กเอนด์ ABI และ SSA ของ Go วางประเภทคอมโพสิตขนาดใหญ่ไว้ในหน่วยความจำมากกว่าในการลงทะเบียน (ปัญหาที่ทราบติดตามใน #24416) เนื่องจากเวกเตอร์ SIMD มีขนาด 16 ถึง 64 ไบต์ในแต่ละเวกเตอร์ การทิ้งเวกเตอร์เหล่านั้นลงบนสแต็กจะทำให้ประสิทธิภาพลดลงมากกว่าการกระจายค่าสเกลาร์ สิ่งนี้ใช้กับทั้งพารามิเตอร์ฟังก์ชันและตัวแปรเฉพาะที่ ดังนั้นควรหลีกเลี่ยงการรวมเวกเตอร์ SIMD ไว้ภายในเมทริกซ์หรือโครงสร้างขนาดใหญ่ในลูปร้อน มีความพยายามอย่างต่อเนื่องในการส่งเสริมการเข้าถึงหน่วยความจำคอมโพสิตกลับไปยังรีจิสเตอร์ ซึ่งยังไม่ถึง Go 1.27
การทดลอง
ใน Go 1.27 GOEXPERIMENT=simd รองรับ amd64 (AVX, AVX2 และ AVX-512) arm64 (นีออน) และ wasm (128-bit SIMD WebAssembly) นอกกรอบ ไม่ว่าคุณจะใช้เครื่อง Intel/AMD arm64 เซิร์ฟเวอร์หรือ Apple Silicon Mac (M1/M2/M3/M4) หรือกำหนดเป้าหมาย WebAssembly คุณสามารถลอง archsimd โดยกำเนิดทันที:
GOEXPERIMENT=simd go test simd/archsimd/...
คุณยังสามารถทดสอบข้ามได้ wasm (หรือ amd64 ผ่านการจำลอง Rosetta บน Apple Silicon) GOARCH: :
# Run wasm SIMD tests using a WASI runtime:
GOOS=wasip1 GOARCH=wasm GOEXPERIMENT=simd go test simd/archsimd/...
# Or run amd64 AVX2 tests on Apple Silicon via Rosetta:
GOARCH=amd64 GOEXPERIMENT=simd go test simd/archsimd/...
เราสนใจคำติชมทุกประเภท! ผู้ใช้ในช่วงแรกๆ ได้ช่วยเราตรวจจับจุดบกพร่อง (เช่น #77582) และระบุตำแหน่งที่สามารถปรับปรุงการสร้างโค้ดหรือการยศาสตร์ของ API ได้ เราพยายามเลือกชื่อที่ชัดเจนและสม่ำเสมอและครอบคลุมคำแนะนำที่มีประโยชน์ที่สุดแต่ archsimd เป็นพื้นผิว API ขนาดใหญ่และยังมีพื้นที่สำหรับการปรับปรุงอยู่เสมอ
งานในอนาคต
สำหรับ Go 1.28 และใหม่กว่า เรากำลังดำเนินการอย่างเต็มที่เพื่อให้เสร็จสมบูรณ์ arm64 รองรับ SVE และ SVE2 ใน archsimd (นำเสนอประเภทเวกเตอร์ที่ปรับขนาดได้โดยไม่ขึ้นกับความกว้าง เช่น archsimd.Float32s รองรับโดยตรงจากฮาร์ดแวร์ SVE รีจิสเตอร์และเพรดิเคต) และเสียบเข้ากับมัน simd. เรายังวางแผนที่จะขยาย archsimd สำหรับสถาปัตยกรรมเพิ่มเติม เช่น riscv64, ppc64, s390xและ loong64ปรับปรุงการส่งเสริมการขายการลงทะเบียนสำหรับประเภทคอมโพสิต และกรอกคำแนะนำและการเพิ่มประสิทธิภาพคอมไพเลอร์ต่อไปตามความคิดเห็นของชุมชน