ดูคำตอบนี้ในเวอร์ชันก่อนหน้านี้ในคำถามหมุนเวียนอื่นพร้อมรายละเอียดเพิ่มเติมเกี่ยวกับสิ่งที่ asm gcc / clang ผลิตขึ้นสำหรับ x86
วิธีคอมไพเลอร์ที่เหมาะที่สุดที่จะแสดงหมุนใน C และ C ++ ที่หลีกเลี่ยงไม่ได้กำหนดพฤติกรรมใด ๆ ดูเหมือนว่าจะมีการดำเนินงานของจอห์นเรเจหยร ฉันปรับให้หมุนตามความกว้างของประเภท (ใช้ประเภทความกว้างคงที่เช่นuint32_t)
#include <stdint.h> // for uint32_t
#include <limits.h> // for CHAR_BIT
#include <assert.h>
static inline uint32_t rotl32 (uint32_t n, unsigned int c)
{
const unsigned int mask = (CHAR_BIT*sizeof(n) - 1);
c &= mask;
return (n<<c) | (n>>( (-c)&mask ));
}
static inline uint32_t rotr32 (uint32_t n, unsigned int c)
{
const unsigned int mask = (CHAR_BIT*sizeof(n) - 1);
c &= mask;
return (n>>c) | (n<<( (-c)&mask ));
}
ใช้ได้กับประเภทจำนวนเต็มที่ไม่ได้ลงนามไม่ใช่เพียงแค่uint32_tคุณสามารถสร้างเวอร์ชันสำหรับขนาดอื่น ๆ ได้
ดูเวอร์ชันเทมเพลต C ++ 11 ที่มีการตรวจสอบความปลอดภัยมากมาย (รวมถึงstatic_assertความกว้างของประเภทเป็น 2)ซึ่งไม่ใช่กรณีของ DSP แบบ 24 บิตหรือเมนเฟรม 36 บิตบางตัวเช่น
ฉันขอแนะนำให้ใช้เทมเพลตเป็นส่วนหลังสำหรับ Wrapper ที่มีชื่อที่มีความกว้างการหมุนอย่างชัดเจน กฎการส่งเสริมจำนวนเต็มหมายถึงการrotl_template(u16 & 0x11UL, 7)หมุน 32 หรือ 64 บิตไม่ใช่ 16 (ขึ้นอยู่กับความกว้างของunsigned long) แม้uint16_t & uint16_tจะเลื่อนตำแหน่งให้เป็นsigned intตามกฎจำนวนเต็มโปรโมชั่น C ++ 's ยกเว้นบนแพลตฟอร์มที่ไม่มีที่กว้างกว่าintuint16_t
บน x86เวอร์ชันนี้สอดแทรกไปยังrol r32, clrol r32, imm8คอมไพเลอร์เดียว (หรือ) ที่มีคอมไพเลอร์ที่ส่งเสียงดังเนื่องจากคอมไพเลอร์รู้ว่าx86 คำแนะนำในการหมุนและเปลี่ยนจะปิดบังจำนวนกะแบบเดียวกับที่แหล่ง C
การสนับสนุนคอมไพเลอร์สำหรับสำนวนการหลีกเลี่ยง UB บน x86 สำหรับuint32_t xและunsigned int nสำหรับการเปลี่ยนแปลงจำนวนตัวแปร:
- เสียงดัง: ได้รับการยอมรับสำหรับการนับตัวแปรที่หมุนตั้งแต่ clang3.5, หลายกะ + หรือ insns ก่อนหน้านั้น
- gcc: ได้รับการยอมรับสำหรับการนับตัวแปรหมุนตั้งแต่ gcc4.9หลายกะ + หรือ insns ก่อนหน้านั้น gcc5 และเพิ่มประสิทธิภาพสาขาและมาสก์ในเวอร์ชันวิกิพีเดียในภายหลังเช่นกันโดยใช้เพียงคำสั่ง
rorหรือrolคำสั่งสำหรับการนับตัวแปร
- ICC: การสนับสนุนสำหรับการหมุนตัวแปรนับตั้งแต่ ICC13 หรือก่อนหน้านั้น การนับค่าคงที่จะหมุนการใช้งาน
shld edi,edi,7ซึ่งช้ากว่าและใช้ไบต์มากกว่าrol edi,7ซีพียูบางตัว (โดยเฉพาะ AMD แต่ยังรวมถึง Intel บางตัวด้วย) เมื่อ BMI2 ไม่สามารถrorx eax,edi,25บันทึก MOV ได้
- MSVC: x86-64 CL19: ได้รับการยอมรับสำหรับการหมุนจำนวนคงที่เท่านั้น (สำนวนวิกิพีเดียเป็นที่รู้จัก แต่สาขาและ AND ไม่ได้รับการปรับให้เหมาะสมที่สุด) ใช้
_rotl/ _rotrintrinsics จาก<intrin.h>บน x86 (รวม x86-64)
GCC สำหรับ ARM ใช้and r1, r1, #31สำหรับหมุนตัวแปรนับ แต่ยังคงไม่หมุนที่เกิดขึ้นจริงกับคำสั่งเดียวror r0, r0, r1 : ดังนั้น gcc จึงไม่ทราบว่าจำนวนการหมุนนั้นเป็นแบบแยกส่วนโดยเนื้อแท้ ในฐานะที่เป็นเอกสาร ARM กล่าวว่า"มีความยาว ROR กะnมากกว่า 32 เป็นเช่นเดียวกับที่มีความยาว ROR กะn-32 " ฉันคิดว่า gcc สับสนที่นี่เพราะการเลื่อนซ้าย / ขวาบน ARM ทำให้การนับอิ่มตัวดังนั้นการเลื่อน 32 ขึ้นไปจะล้างการลงทะเบียน (ไม่เหมือน x86 ที่กะจะปกปิดการนับเหมือนกับการหมุน) มันอาจจะตัดสินใจว่ามันต้องการคำสั่ง AND ก่อนที่จะรับรู้สำนวนการหมุนเนื่องจากการกะแบบไม่เป็นวงกลมทำงานกับเป้าหมายนั้นอย่างไร
คอมไพเลอร์ x86 ปัจจุบันยังคงใช้คำสั่งพิเศษเพื่อปกปิดจำนวนตัวแปรสำหรับการหมุน 8 และ 16 บิตอาจเป็นเพราะเหตุผลเดียวกับที่พวกเขาไม่หลีกเลี่ยง AND บน ARM นี่เป็นการเพิ่มประสิทธิภาพที่ไม่ได้รับเนื่องจากประสิทธิภาพไม่ได้ขึ้นอยู่กับจำนวนการหมุนของ CPU x86-64 ใด ๆ (การกำบังการนับถูกนำมาใช้ด้วย 286 ด้วยเหตุผลด้านประสิทธิภาพเนื่องจากมีการจัดการกะซ้ำ ๆ ไม่ใช่ด้วยความหน่วงคงที่เหมือนซีพียูสมัยใหม่)
BTW ชอบการหมุนขวาสำหรับการหมุนจำนวนตัวแปรเพื่อหลีกเลี่ยงการทำให้คอมไพเลอร์ทำ32-nเพื่อใช้การหมุนซ้ายบนสถาปัตยกรรมเช่น ARM และ MIPS ที่ให้เฉพาะการหมุนขวาเท่านั้น (สิ่งนี้จะเพิ่มประสิทธิภาพด้วยการนับค่าคงที่เวลาคอมไพล์)
สนุกจริง: ARM ไม่ได้จริงๆต้องทุ่มเทกะ / คำแนะนำหมุนก็เพียง MOV กับแหล่งที่มาถูกดำเนินการจะผ่านบาร์เรลจำแลงในโหมด RORmov r0, r0, ror r1 : ดังนั้นการหมุนสามารถพับเป็นตัวดำเนินการรีจิสเตอร์ซอร์สสำหรับคำสั่ง EOR หรืออะไรก็ได้
ให้แน่ใจว่าคุณใช้ชนิดที่ได้รับการรับรองสำหรับการnและค่าตอบแทนหรืออื่น ๆ มันจะไม่หมุน (เป้าหมาย gcc สำหรับ x86 จะเปลี่ยนขวาทางเลขคณิตโดยเปลี่ยนสำเนาของบิตเครื่องหมายแทนที่จะเป็นศูนย์ซึ่งนำไปสู่ปัญหาเมื่อคุณORทั้งสองค่าที่เลื่อนเข้าด้วยกันการเลื่อนทางขวาของจำนวนเต็มที่ลงชื่อเชิงลบเป็นพฤติกรรมที่กำหนดไว้ใน C)
นอกจากนี้ตรวจสอบให้แน่ใจว่าจำนวนกะเป็นประเภทที่ไม่ได้ลงชื่อเนื่องจากประเภทที่(-n)&31มีการลงนามอาจเป็นส่วนเติมเต็มหรือเครื่องหมาย / ขนาดและไม่เหมือนกับโมดูลาร์ 2 ^ n ที่คุณได้รับจากส่วนเสริมที่ไม่ได้ลงนามหรือสอง (ดูความคิดเห็นในบล็อกโพสต์ของ Regehr) unsigned intทำได้ดีกับทุกคอมไพเลอร์ที่ฉันดูสำหรับทุกความกว้างของxไฟล์. ประเภทอื่น ๆ บางประเภทเอาชนะการจดจำสำนวนสำหรับคอมไพเลอร์บางตัวดังนั้นอย่าใช้ประเภทเดียวกับx.
คอมไพเลอร์บางตัวมีอินทรินซิคสำหรับการหมุนซึ่งดีกว่าอินไลน์ - asm มากหากเวอร์ชันพกพาไม่ได้สร้างโค้ดที่ดีบนคอมไพเลอร์ที่คุณกำหนดเป้าหมาย ไม่มีอินทรินข้ามแพลตฟอร์มสำหรับคอมไพเลอร์ใด ๆ ที่ฉันรู้จัก นี่คือบางส่วนของตัวเลือก x86:
- เอกสารของ Intel ที่
<immintrin.h>จัดเตรียม_rotlและ_rotl64เนื้อแท้และเหมือนกันสำหรับการเปลี่ยนที่ถูกต้อง MSVC ต้อง<intrin.h>ในขณะที่ GCC <x86intrin.h>ต้อง #ifdefดูแล gcc กับ ICC แต่เสียงดังกราวดูเหมือนจะไม่ให้พวกเขาได้ทุกที่ยกเว้นในโหมดที่เข้ากันกับ MSVC -fms-extensions -fms-compatibility -fms-compatibility-version=17.00และ asm ที่มันปล่อยออกมามันห่วย (การกำบังพิเศษและ CMOV)
- MSVC:
_rotr8และ_rotr16 .
- gcc และ icc (ไม่ใช่เสียงดัง):
<x86intrin.h>ยังมี__rolb/ __rorbสำหรับการหมุน 8 บิตไปทางซ้าย / ขวา, __rolw/ __rorw(16 บิต), __rold/ __rord(32 บิต), __rolq/ __rorq(64 บิตกำหนดไว้สำหรับเป้าหมาย 64 บิตเท่านั้น) สำหรับการหมุนแบบแคบการใช้งานจะใช้__builtin_ia32_rolhiหรือ...qiแต่การหมุน 32 และ 64 บิตถูกกำหนดโดยใช้ shift / หรือ (โดยไม่มีการป้องกัน UB เนื่องจากโค้ดในia32intrin.hต้องทำงานบน gcc สำหรับ x86 เท่านั้น) GNU C ดูเหมือนจะไม่มี__builtin_rotateฟังก์ชั่นข้ามแพลตฟอร์มอย่างที่มันทำ__builtin_popcount(ซึ่งขยายไปสู่สิ่งที่เหมาะสมที่สุดบนแพลตฟอร์มเป้าหมายแม้ว่าจะไม่ใช่คำสั่งเดียวก็ตาม) เวลาส่วนใหญ่คุณจะได้รหัสที่ดีจากการจดจำสำนวน
#if defined(__x86_64__) || defined(__i386__)
#ifdef _MSC_VER
#include <intrin.h>
#else
#include <x86intrin.h> // Not just <immintrin.h> for compilers other than icc
#endif
uint32_t rotl32_x86_intrinsic(rotwidth_t x, unsigned n) {
return _rotl(x, n);
}
#endif
สันนิษฐานว่าคอมไพเลอร์ที่ไม่ใช่ x86 บางตัวมีอยู่ภายในด้วยเช่นกัน แต่อย่าขยายคอมมิวนิตี้วิกิคำตอบนี้เพื่อรวมไว้ (อาจทำเช่นนั้นในคำตอบที่มีอยู่เกี่ยวกับเนื้อแท้ )
(คำตอบรุ่นเก่าแนะนำ MSVC-specific inline asm (ซึ่งใช้ได้กับโค้ด 32 บิต x86 เท่านั้น) หรือhttp://www.devx.com/tips/Tip/14043สำหรับเวอร์ชัน C ความคิดเห็นกำลังตอบกลับไปว่า .)
asm Inline เอาชนะการเพิ่มประสิทธิภาพจำนวนมาก , โดยเฉพาะอย่างยิ่ง MSVC สไตล์เพราะมันบังคับให้ปัจจัยการผลิตจะถูกเก็บไว้ การหมุนแบบอินไลน์ - asm ของ GNU C ที่เขียนอย่างระมัดระวังจะช่วยให้การนับเป็นตัวดำเนินการในทันทีสำหรับการนับจำนวนกะค่าคงที่ของเวลาคอมไพล์ แต่ก็ยังไม่สามารถปรับให้เหมาะสมได้ทั้งหมดหากค่าที่จะเปลี่ยนเป็นค่าคงที่เวลาคอมไพล์ด้วย หลังจากซับใน https://gcc.gnu.org/wiki/DontUseInlineAsm