สำหรับ RISC-V คุณอาจใช้ GCC / เสียงดังกราว
ความจริงแล้วสนุก: GCC รู้เทคนิค SWAR Bithack เหล่านี้บางส่วน (แสดงในคำตอบอื่น ๆ ) และสามารถใช้สำหรับคุณเมื่อรวบรวมรหัสกับเวกเตอร์ดั้งเดิมของ GNU Cสำหรับเป้าหมายโดยไม่มีคำแนะนำฮาร์ดแวร์ SIMD (แต่เสียงดังสำหรับ RISC-V จะเป็นการปลดการใช้งานแบบสเกลาร์ดังนั้นคุณต้องทำด้วยตัวเองถ้าคุณต้องการประสิทธิภาพที่ดีในคอมไพเลอร์)
ข้อดีอย่างหนึ่งของไวยากรณ์เวกเตอร์ดั้งเดิมคือเมื่อกำหนดเป้าหมายเครื่องด้วยฮาร์ดแวร์ SIMD จะใช้สิ่งนั้นแทนการปรับเวกเตอร์อัตโนมัติของคุณหรือสิ่งที่น่ากลัวอย่างนั้น
มันทำให้การเขียนง่ายvector -= scalar
ขึ้น ไวยากรณ์เพียงแค่ใช้งานโดยปริยายการกระจายสัญญาณหรือที่รู้จักก็คือการกระจายสเกลาร์ให้คุณ
โปรดทราบด้วยว่าการuint64_t*
โหลดจาก a uint8_t array[]
นั้นเป็น UB ที่ใช้นามแฝงอย่างเข้มงวดดังนั้นโปรดระมัดระวังด้วย (ดูเพิ่มเติมทำไม strlen ของ glibc จำเป็นต้องมีความซับซ้อนมากในการทำงานอย่างรวดเร็วหรือไม่ re: การทำให้ SWAR bithacks aliasing ที่เข้มงวดปลอดภัยใน C บริสุทธิ์) คุณอาจต้องการบางสิ่งเช่นนี้เพื่อประกาศuint64_t
ว่าคุณสามารถใช้พอยน์คาสต์เพื่อเข้าถึงออบเจ็กต์อื่น ๆ เช่นวิธีการchar*
ทำงานใน ISO C / C ++
ใช้สิ่งเหล่านี้เพื่อรับข้อมูล uint8_t เป็น uint64_t เพื่อใช้กับคำตอบอื่น ๆ :
// GNU C: gcc/clang/ICC but not MSVC
typedef uint64_t aliasing_u64 __attribute__((may_alias)); // still requires alignment
typedef uint64_t aliasing_unaligned_u64 __attribute__((may_alias, aligned(1)));
อีกวิธีหนึ่งในการทำโหลดที่ปลอดภัยในนามแฝงนั้นมีmemcpy
อยู่ใน a uint64_t
ซึ่งจะลบalignof(uint64_t
ข้อกำหนดการจัดตำแหน่ง) ด้วย แต่สำหรับ ISAs ที่ไม่มีการโหลดที่ไม่ได้จัดแนวอย่างมีประสิทธิภาพ gcc / clang จะไม่อินไลน์และปรับให้เหมาะสมmemcpy
เมื่อไม่สามารถพิสูจน์ได้ว่าตัวชี้นั้นอยู่ในแนวเดียวกันซึ่งจะส่งผลร้ายต่อประสิทธิภาพการทำงาน
TL: DR: ทางออกที่ดีที่สุดของคุณคือการประกาศข้อมูลเป็นuint64_t array[...]
หรือจัดสรรแบบไดนามิกuint64_t
, หรือโดยเฉพาะอย่างยิ่งalignas(16) uint64_t array[];
ที่ช่วยให้การจัดตำแหน่งอย่างน้อย 8 ไบต์หรือ 16 alignas
หากคุณระบุ
เนื่องจากuint8_t
เกือบจะแน่นอนunsigned char*
จึงปลอดภัยในการเข้าถึงไบต์ของuint64_t
ผ่านuint8_t*
(แต่ไม่กลับกันสำหรับอาร์เรย์ uint8_t) ดังนั้นสำหรับกรณีพิเศษนี้ที่มีประเภทองค์ประกอบที่แคบunsigned char
คุณสามารถหลีกเลี่ยงปัญหานามแฝงที่เข้มงวดเนื่องจากchar
เป็นสิ่งพิเศษ
ตัวอย่างไวยากรณ์แบบดั้งเดิมของ GNU C:
GNU C เวกเตอร์พื้นเมืองที่ได้รับอนุญาตเสมอเพื่อนามแฝงที่มีประเภทพื้นฐานของพวกเขา (เช่นint __attribute__((vector_size(16)))
สามารถได้อย่างปลอดภัยนามแฝงint
แต่ไม่float
หรือuint8_t
หรือสิ่งอื่นใด
#include <stdint.h>
#include <stddef.h>
// assumes array is 16-byte aligned
void dec_mem_gnu(uint8_t *array) {
typedef uint8_t v16u8 __attribute__ ((vector_size (16), may_alias));
v16u8 *vecs = (v16u8*) array;
vecs[0] -= 1;
vecs[1] -= 1; // can be done in a loop.
}
สำหรับ RISC-V ที่ไม่มี HW SIMD คุณสามารถใช้vector_size(8)
เพื่อแสดงความละเอียดที่คุณสามารถใช้ได้อย่างมีประสิทธิภาพและทำเวกเตอร์ขนาดเล็กจำนวนสองเท่า
แต่vector_size(8)
คอมไพล์อย่างน่าประหลาดใจสำหรับ x86 ด้วยทั้ง GCC และ clang: GCC ใช้ SWAR bithacks ในการลงทะเบียน GP-จำนวนเต็มเสียงดังกราว unpacks องค์ประกอบ 2 ไบต์เพื่อเติมลงทะเบียน XMM 16- ไบต์แล้ว repacks (MMX ล้าสมัยเหลือเกินที่ GCC / เสียงดังกังวานไม่ได้สนใจใช้มันอย่างน้อยก็สำหรับ x86-64)
แต่ด้วยvector_size (16)
( Godbolt ) เราได้รับความคาดหวัง/movdqa
paddb
(ด้วยเวกเตอร์ทั้งหมดที่สร้างโดยpcmpeqd same,same
) ด้วย-march=skylake
เรายังคงได้รับ XMM ops แยกกันสองตัวแทนที่จะเป็นหนึ่ง YMM ดังนั้นน่าเสียดายที่คอมไพเลอร์ปัจจุบันยังไม่ได้เวกเตอร์ "auto-vectorize" ใน ops ที่กว้างกว่า:
สำหรับ AArch64 มันไม่เลวเลยที่จะใช้vector_size(8)
( Godbolt ) ARM / AArch64 สามารถทำงานในชิ้นส่วน 8 หรือ 16 ไบต์ได้ด้วยd
หรือq
รีจิสเตอร์
ดังนั้นคุณอาจต้องการvector_size(16)
ที่จะรวบรวมจริงด้วยถ้าคุณต้องการประสิทธิภาพการทำงานแบบพกพาทั่ว x86, RISC-V, ARM / AArch64 และพลัง อย่างไรก็ตาม ISAs อื่น ๆ ทำ SIMD ภายในการลงทะเบียนจำนวนเต็ม 64 บิตเช่น MIPS MSA ฉันคิดว่า
vector_size(8)
ทำให้ง่ายต่อการดู asm (เพียงหนึ่งค่าลงทะเบียนของข้อมูล): Godbolt compiler explorer
# GCC8.2 -O3 for RISC-V for vector_size(8) and only one vector
dec_mem_gnu(unsigned char*):
lui a4,%hi(.LC1) # generate address for static constants.
ld a5,0(a0) # a5 = load from function arg
ld a3,%lo(.LC1)(a4) # a3 = 0x7F7F7F7F7F7F7F7F
lui a2,%hi(.LC0)
ld a2,%lo(.LC0)(a2) # a2 = 0x8080808080808080
# above here can be hoisted out of loops
not a4,a5 # nx = ~x
and a5,a5,a3 # x &= 0x7f... clear high bit
and a4,a4,a2 # nx = (~x) & 0x80... inverse high bit isolated
add a5,a5,a3 # x += 0x7f... (128-1)
xor a5,a4,a5 # x ^= nx restore high bit or something.
sd a5,0(a0) # store the result
ret
ฉันคิดว่ามันเป็นแนวคิดพื้นฐานเช่นเดียวกับคำตอบอื่น ๆ ป้องกันการพกพาและแก้ไขผลลัพธ์
นี่คือคำแนะนำ 5 ALU ซึ่งแย่กว่าคำตอบยอดนิยมที่ฉันคิด แต่ดูเหมือนว่าเวลาแฝงของเส้นทางวิกฤตนั้นมีเพียง 3 รอบด้วยสองสายโซ่ของ 2 คำสั่งแต่ละอันนำไปสู่ XOR @ Reinstate Monica - ζ - คำตอบของคอมไพล์กับ chain dep 4 รอบ (สำหรับ x86) ทรานแซคชันวนลูป 5 รอบได้รับการคอขวดโดยรวมถึงการไร้เดียงสาsub
บนพา ธ วิกฤติ
อย่างไรก็ตามมันไม่มีประโยชน์อะไรกับเสียงดังกราว มันไม่ได้เพิ่มและจัดเก็บในลำดับเดียวกันกับที่โหลดดังนั้นมันจึงไม่ได้ทำการวางท่อซอฟต์แวร์ที่ดี!
# RISC-V clang (trunk) -O3
dec_mem_gnu(unsigned char*):
lb a6, 7(a0)
lb a7, 6(a0)
lb t0, 5(a0)
...
addi t1, a5, -1
addi t2, a1, -1
addi t3, a2, -1
...
sb a2, 7(a0)
sb a1, 6(a0)
sb a5, 5(a0)
...
ret