ดูคำตอบนี้ในเวอร์ชันก่อนหน้านี้ในคำถามหมุนเวียนอื่นพร้อมรายละเอียดเพิ่มเติมเกี่ยวกับสิ่งที่ 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 ยกเว้นบนแพลตฟอร์มที่ไม่มีที่กว้างกว่าint
uint16_t
บน x86เวอร์ชันนี้สอดแทรกไปยังrol r32, cl
rol 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
/ _rotr
intrinsics จาก<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