นี่คือตัวอย่างโลกแห่งความจริง: จุดคงที่คูณกับคอมไพเลอร์เก่า
สิ่งเหล่านี้ไม่เพียงมีประโยชน์กับอุปกรณ์ที่ไม่มีจุดลอยตัวเท่านั้น แต่ยังส่องแสงเมื่อถึงความแม่นยำเนื่องจากให้ความแม่นยำ 32 บิตพร้อมข้อผิดพลาดที่คาดเดาได้ (ลอยเพียง 23 บิตและยากที่จะทำนายการสูญเสียความแม่นยำ) เช่นความแม่นยำสัมบูรณ์สม่ำเสมอตลอดช่วงแทนที่จะเป็นความแม่นยำสัมพัทธ์ใกล้เคียงกับชุด( float
)
คอมไพเลอร์สมัยใหม่จะปรับตัวอย่างจุดคงที่นี้อย่างเหมาะสมดังนั้นสำหรับตัวอย่างที่ทันสมัยกว่าซึ่งยังคงต้องการโค้ดเฉพาะคอมไพเลอร์ให้ดู
C ไม่มีตัวดำเนินการคูณแบบเต็ม (ผลลัพธ์ 2N บิตจากอินพุต N-bit) วิธีปกติในการแสดงมันใน C คือการส่งสัญญาณอินพุตไปยังประเภทที่กว้างขึ้นและหวังว่าคอมไพเลอร์จะรับรู้ว่าบิตส่วนบนของอินพุตนั้นไม่น่าสนใจ:
// on a 32-bit machine, int can hold 32-bit fixed-point integers.
int inline FixedPointMul (int a, int b)
{
long long a_long = a; // cast to 64 bit.
long long product = a_long * b; // perform multiplication
return (int) (product >> 16); // shift by the fixed point bias
}
ปัญหาของรหัสนี้คือเราทำบางสิ่งที่ไม่สามารถแสดงออกได้โดยตรงในภาษา C เราต้องการที่จะคูณสองตัวเลข 32 บิตและรับผล 64 บิตซึ่งเรากลับกลาง 32 บิต อย่างไรก็ตามใน C การคูณนี้ไม่มีอยู่ สิ่งที่คุณสามารถทำได้คือการส่งเสริมจำนวนเต็มถึง 64 บิตและทำคูณ 64 * 64 = 64
อย่างไรก็ตาม x86 (และ ARM, MIPS และอื่น ๆ ) สามารถทำคูณได้ในคำสั่งเดียว คอมไพเลอร์บางรายใช้เพื่อเพิกเฉยต่อข้อเท็จจริงนี้และสร้างรหัสที่เรียกใช้ฟังก์ชันไลบรารีรันไทม์เพื่อคูณ การกะด้วย 16 มักจะทำโดยรูทีนไลบรารี (เช่น x86 สามารถทำกะได้)
ดังนั้นเราจึงเหลือการเรียกใช้ไลบรารีหนึ่งหรือสองครั้งเพื่อเพิ่มทวีคูณ สิ่งนี้มีผลกระทบร้ายแรง ไม่เพียง แต่การเลื่อนช้าลงการลงทะเบียนจะต้องถูกรักษาไว้ระหว่างการเรียกใช้ฟังก์ชั่นและมันไม่ได้ช่วยให้การอินไลน์และการคลายรหัส
หากคุณเขียนรหัสเดียวกันในแอสเซมเบลอร์ (inline) คุณสามารถเพิ่มความเร็วได้อย่างมาก
นอกจากนี้: การใช้ ASM ไม่ใช่วิธีที่ดีที่สุดในการแก้ปัญหา คอมไพเลอร์ส่วนใหญ่อนุญาตให้คุณใช้คำสั่งแอสเซมเบลอร์ในรูปแบบที่อยู่ภายในหากคุณไม่สามารถแสดงมันในซีคอมไพเลอร์ VS.NET2008 เช่น exposes 32 * 32 = 64 บิต mul เป็น __emul และ 64 บิต shift เป็น __ll_rshift
การใช้งานอินทรินสิกคุณสามารถเขียนฟังก์ชันใหม่ในลักษณะที่คอมไพเลอร์ C มีโอกาสที่จะเข้าใจสิ่งที่เกิดขึ้น สิ่งนี้ทำให้โค้ดสามารถ inline, register register, การกำจัด subexpression ทั่วไปและการแพร่กระจายคงที่สามารถทำได้เช่นกัน คุณจะได้รับการปรับปรุงประสิทธิภาพอย่างมากผ่านโค้ดแอสเซมเบลอร์ที่เขียนด้วยมือ
สำหรับการอ้างอิง: ผลลัพธ์สุดท้ายสำหรับ mul แบบจุดคงที่สำหรับคอมไพเลอร์ VS.NET คือ:
int inline FixedPointMul (int a, int b)
{
return (int) __ll_rshift(__emul(a,b),16);
}
ความแตกต่างของประสิทธิภาพของการแบ่งจุดคงที่ยิ่งใหญ่กว่า ฉันมีการปรับปรุงถึงปัจจัย 10 สำหรับการหารรหัสจุดคงที่หนักโดยการเขียนสองบรรทัด asm
การใช้ Visual C ++ 2013 ให้รหัสแอสเซมบลีที่เหมือนกันทั้งสองวิธี
gcc4.1 จากปี 2007 ยังปรับรุ่น C บริสุทธิ์อย่างดี (ตัวรวบรวมคอมไพเลอร์ Godbolt ไม่มีการติดตั้ง gcc รุ่นก่อนหน้านี้ แต่สันนิษฐานว่าแม้แต่รุ่น GCC ที่เก่ากว่าก็สามารถทำได้โดยไม่ต้องมีอินทิลินภายใน)
ดูแหล่งที่มา asm + สำหรับ x86 (32 บิต) และ ARM บนคอมไพเลอร์สำรวจ Godbolt (น่าเสียดายที่มันไม่มีคอมไพเลอร์ใด ๆ ที่เก่าพอที่จะสร้างโค้ดที่ไม่ดีจากเวอร์ชั่น C แบบง่าย ๆ )
ซีพียูที่ทันสมัยสามารถทำสิ่ง C ไม่ได้มีผู้ประกอบการสำหรับการที่ทุกคนเหมือนpopcnt
หรือบิตสแกนเพื่อหาสิ่งที่บิตชุดแรกหรือสุดท้าย (POSIX มีffs()
ฟังก์ชั่น แต่ความหมายของมันไม่ตรงกับ x86 bsf
/ bsr
ดูhttps://en.wikipedia.org/wiki/Find_first_set )
คอมไพเลอร์บางตัวสามารถจำลูปที่นับจำนวนบิตที่ตั้งไว้ในจำนวนเต็มและคอมไพล์มันเป็นpopcnt
คำสั่ง (ถ้าเปิดใช้งานในเวลาคอมไพล์) แต่ก็มีความน่าเชื่อถือมากกว่าที่จะใช้__builtin_popcnt
ใน GNU C หรือ x86 ถ้าคุณเท่านั้น กำหนดเป้าหมายฮาร์ดแวร์ SSE4.2: จาก_mm_popcnt_u32
<immintrin.h>
หรือใน C ++ กำหนดให้และการใช้งานstd::bitset<32>
.count()
(นี่เป็นกรณีที่ภาษาพบวิธีที่จะเปิดเผยการปรับใช้ popcount ให้เหมาะสมผ่านไลบรารีมาตรฐานในลักษณะที่จะรวบรวมสิ่งที่ถูกต้องเสมอและสามารถใช้ประโยชน์จากสิ่งที่สนับสนุนเป้าหมาย) ดูเพิ่มเติมที่https :
ในทำนองเดียวกันntohl
สามารถคอมไพล์ไปที่bswap
(x86 32- บิตสลับไบต์สำหรับการแปลง endian) ในการใช้งาน C บางอย่างที่มี
พื้นที่สำคัญอื่น ๆ สำหรับอินทิลิตี้หรือ asm เขียนด้วยมือคือ vectorization ด้วยตนเองพร้อมคำแนะนำ SIMD คอมไพเลอร์ไม่ได้เลวร้ายกับลูปแบบง่าย ๆdst[i] += src[i] * 10.0;
แต่มักจะทำไม่ดีหรือไม่ปรับเวกเตอร์อัตโนมัติเมื่อสิ่งต่าง ๆ มีความซับซ้อนมากขึ้น ตัวอย่างเช่นคุณไม่น่าจะได้อะไรเช่นวิธีใช้ atoi โดยใช้ SIMD? สร้างโดยอัตโนมัติโดยคอมไพเลอร์จากรหัสสเกลาร์