การประกอบเร็วกว่า C เมื่อใด


475

หนึ่งในเหตุผลที่ระบุไว้สำหรับการทราบแอสเซมเบลอร์คือในบางครั้งมันสามารถใช้ในการเขียนรหัสที่จะมีประสิทธิภาพมากกว่าการเขียนโค้ดนั้นในภาษาระดับสูงโดยเฉพาะอย่างยิ่ง C แต่ผมเคยได้ยินยังระบุไว้หลายครั้งแล้วว่าถึงแม้ว่ามันจะไม่ได้เป็นเท็จอย่างสิ้นเชิงกรณีที่ผู้ประกอบสามารถจริงจะนำมาใช้เพื่อสร้างรหัส performant มากขึ้นมีทั้งหายากมากและต้องใช้ความรู้จากผู้เชี่ยวชาญและประสบการณ์เกี่ยวกับการชุมนุม

คำถามนี้ไม่ได้เกี่ยวกับข้อเท็จจริงที่ว่าคำสั่งแอสเซมเบลอร์จะเป็นแบบเฉพาะเครื่องจักรและไม่ใช่แบบพกพาหรือด้านอื่น ๆ ของแอสเซมเบลอร์ มีเหตุผลที่ดีมากมายในการรู้จักการประกอบนอกเหนือจากนี้แน่นอน แต่นี่เป็นคำถามที่เฉพาะเจาะจงเพื่อชักชวนตัวอย่างและข้อมูลไม่ใช่การสนทนาที่เพิ่มขึ้นของผู้ประกอบการกับภาษาระดับสูง

ทุกคนสามารถให้ตัวอย่างเฉพาะของกรณีที่การชุมนุมจะเร็วกว่ารหัส C ที่เขียนดีโดยใช้คอมไพเลอร์ที่ทันสมัยและคุณสามารถสนับสนุนการเรียกร้องที่มีหลักฐานการทำโปรไฟล์? ฉันค่อนข้างมั่นใจว่ากรณีเหล่านี้มีอยู่จริง แต่ฉันต้องการทราบอย่างแน่ชัดว่าคดีเหล่านี้เป็นความลับอย่างไรเพราะมันดูเหมือนจะเป็นประเด็นของความขัดแย้ง


17
อันที่จริงแล้วมันค่อนข้างเล็กน้อยในการปรับปรุงเมื่อรวบรวมโค้ด ทุกคนที่มีความรู้ด้านภาษาแอสเซมบลีและ C สามารถเห็นสิ่งนี้ได้โดยการตรวจสอบโค้ดที่สร้างขึ้น สิ่งที่ง่าย ๆ คือหน้าผาแห่งแรกที่คุณพลาดเมื่อคุณหมดการลงทะเบียนแบบใช้ครั้งเดียวในเวอร์ชั่นที่รวบรวม โดยเฉลี่ยแล้วคอมไพเลอร์จะทำได้ดีกว่ามนุษย์สำหรับโครงการขนาดใหญ่ แต่ก็ไม่ยากในโครงการขนาดที่เหมาะสมในการค้นหาปัญหาด้านประสิทธิภาพในโค้ดที่คอมไพล์
old_timer

14
ที่จริงแล้วคำตอบสั้น ๆ คือ Assembler เร็วกว่าเสมอหรือเท่ากับความเร็วของ C เหตุผลก็คือคุณสามารถมีการชุมนุมโดยไม่มี C แต่คุณไม่สามารถมี C โดยไม่มีการประกอบ (ในรูปแบบไบนารีซึ่งเราในสมัยโบราณ วันที่เรียกว่า "รหัสเครื่อง") ที่กล่าวมาคำตอบยาว ๆ คือ: C Compilers นั้นค่อนข้างดีในการปรับและ "คิด" เกี่ยวกับสิ่งที่คุณไม่ได้นึกถึงดังนั้นมันขึ้นอยู่กับทักษะของคุณ แต่โดยปกติแล้วคุณสามารถเอาชนะคอมไพเลอร์ C ได้เสมอ ยังคงเป็นเพียงซอฟต์แวร์ที่ไม่สามารถคิดและรับแนวคิดได้ คุณยังสามารถเขียนแอสเซมเบลอร์แบบพกพาหากคุณใช้มาโครและคุณอดทนได้

11
ฉันไม่เห็นด้วยอย่างยิ่งว่าคำตอบสำหรับคำถามนี้ต้องเป็น "ฐานความคิดเห็น" - พวกเขามีวัตถุประสงค์ค่อนข้าง - ไม่ใช่สิ่งที่ต้องการเปรียบเทียบประสิทธิภาพของภาษาสัตว์เลี้ยงตัวโปรดซึ่งแต่ละคนจะมีจุดแข็งและดึงกลับ นี่เป็นเรื่องของการทำความเข้าใจว่าผู้รวบรวมสามารถพาเราไปได้ไกลแค่ไหนและจากจุดไหนดีกว่าที่จะรับช่วงต่อ
jsbueno

21
ก่อนหน้านี้ในอาชีพของฉันฉันเขียน C และแอสเซมเบลอร์เมนเฟรมจำนวนมากที่ บริษัท ซอฟต์แวร์ หนึ่งในคนรอบข้างของฉันคือสิ่งที่ฉันเรียกว่า "ผู้ประกอบพิธีการผู้พิถีพิถัน" (ทุกอย่างจะต้องเป็นผู้รวบรวม) ดังนั้นฉันจึงคิดว่าเขาสามารถเขียนกิจวัตรที่กำหนดไว้ซึ่งวิ่งได้เร็วกว่าใน C มากกว่าสิ่งที่เขาสามารถเขียนได้ ฉันชนะ. แต่หลังจากที่ฉันได้รับรางวัลฉันบอกเขาว่าฉันต้องการเดิมพันครั้งที่สอง - ฉันสามารถเขียนอะไรบางอย่างได้เร็วขึ้นในแอสเซมเบลอร์กว่าโปรแกรม C ที่เอาชนะเขาในการเดิมพันครั้งก่อน ฉันได้รับรางวัลเช่นกันการพิสูจน์ว่าส่วนใหญ่ลงมาถึงทักษะและความสามารถของโปรแกรมเมอร์มากกว่าสิ่งอื่นใด
Valerie R

3
นอกจากว่าสมองของคุณจะมี-O3ธงคุณน่าจะดีกว่าที่จะเพิ่มประสิทธิภาพให้กับคอมไพเลอร์ C :-)
paxdiablo

คำตอบ:


272

นี่คือตัวอย่างโลกแห่งความจริง: จุดคงที่คูณกับคอมไพเลอร์เก่า

สิ่งเหล่านี้ไม่เพียงมีประโยชน์กับอุปกรณ์ที่ไม่มีจุดลอยตัวเท่านั้น แต่ยังส่องแสงเมื่อถึงความแม่นยำเนื่องจากให้ความแม่นยำ 32 บิตพร้อมข้อผิดพลาดที่คาดเดาได้ (ลอยเพียง 23 บิตและยากที่จะทำนายการสูญเสียความแม่นยำ) เช่นความแม่นยำสัมบูรณ์สม่ำเสมอตลอดช่วงแทนที่จะเป็นความแม่นยำสัมพัทธ์ใกล้เคียงกับชุด( float)


คอมไพเลอร์สมัยใหม่จะปรับตัวอย่างจุดคงที่นี้อย่างเหมาะสมดังนั้นสำหรับตัวอย่างที่ทันสมัยกว่าซึ่งยังคงต้องการโค้ดเฉพาะคอมไพเลอร์ให้ดู

  • การได้รับส่วนสูงของการคูณจำนวนเต็ม 64 บิต : รุ่นพกพาที่ใช้ uint64_tสำหรับการคูณ 32x32 => 64- บิตล้มเหลวในการปรับให้เหมาะสมบน CPU 64 บิตดังนั้นคุณต้องใช้อินทิลิตี้หรือ__int128รหัสที่มีประสิทธิภาพสำหรับระบบ 64 บิต
  • _umul128 บน Windows 32 บิต : MSVC ไม่ได้ผลดีเสมอไปเมื่อทำการคูณจำนวนเต็ม 32 บิตที่มีอยู่เป็น 64 ดังนั้นอินทรินซิลช่วยได้มาก

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? สร้างโดยอัตโนมัติโดยคอมไพเลอร์จากรหัสสเกลาร์


6
วิธีการเกี่ยวกับสิ่งต่าง ๆ เช่น {x = c% d; y = c / d;}, คอมไพเลอร์ฉลาดพอที่จะทำให้มันเป็น div เดียวหรือ idiv?
Jens Björnhager

4
ที่จริงแล้วคอมไพเลอร์ที่ดีจะสร้างรหัสที่ดีที่สุดจากฟังก์ชั่นแรก การปิดบังซอร์สโค้ดด้วยอินทิลิตี้หรืออินไลน์แอสเซมบลีอย่างไม่มีประโยชน์ไม่ได้เป็นสิ่งที่ดีที่สุดที่จะทำ
คนเกียจคร้าน

65
สวัสดี Slacker ฉันคิดว่าคุณไม่ต้องทำงานกับรหัสที่สำคัญต่อเวลามาก่อน ... การประกอบแบบอินไลน์สามารถสร้างความแตกต่างอย่างมาก สำหรับคอมไพเลอร์แล้วอินทรินซิกก็เหมือนกับเลขคณิตปกติในซีนั่นคือจุดที่อยู่ภายใน พวกเขาให้คุณใช้คุณสมบัติสถาปัตยกรรมโดยไม่ต้องจัดการกับข้อเสีย
Nils Pipenbrinck

6
@slacker ที่จริงแล้วโค้ดที่นี่อ่านได้ค่อนข้างง่าย: โค้ดอินไลน์ทำการดำเนินการที่ไม่ซ้ำใครซึ่งสามารถอ่านลายเซ็นวิธีการได้ทันที รหัสจะสูญหายอย่างช้าๆในการอ่านได้เมื่อใช้คำสั่งที่คลุมเครือ สิ่งสำคัญในที่นี้คือเรามีวิธีการที่จะทำการดำเนินการที่ชัดเจนเพียงอย่างเดียวและนั่นเป็นวิธีที่ดีที่สุดในการสร้างโค้ดที่สามารถอ่านได้ฟังก์ชั่นอะตอมมิกเหล่านี้ โดยวิธีการนี้จะไม่ปิดบังความคิดเห็นเล็ก ๆ เช่น / * (a * b) >> 16 * / ไม่สามารถอธิบายได้ทันที
Dereckson

5
เพื่อความเป็นธรรมตัวอย่างนี้เป็นตัวอย่างที่แย่อย่างน้อยวันนี้ คอมไพเลอร์ C สามารถทำได้ 32x32 -> 64 คูณแม้ว่าภาษาจะไม่เสนอโดยตรง: พวกเขารับรู้ว่าเมื่อคุณส่งอาร์กิวเมนต์ 32- บิตเป็น 64- บิตแล้วคูณพวกเขาไม่จำเป็นต้อง ทำทวีคูณ 64 บิตเต็มรูปแบบ แต่ 32x32 -> 64 จะทำได้ดี ฉันตรวจสอบแล้วทั้งหมดของclang, gcc และ MSVC ในเวอร์ชันปัจจุบันจะได้รับสิทธิ์นี้ นี่ไม่ใช่เรื่องใหม่ - ฉันจำได้ว่าดูผลลัพธ์ของคอมไพเลอร์และสังเกตเห็นเมื่อสิบปีก่อน
BeeOnRope

143

หลายปีที่ผ่านมาฉันสอนคนที่จะเขียนโปรแกรมในซี เขากลับมาพร้อมทางออกที่ต้องใช้เวลาหลายนาทีจึงจะเสร็จสมบูรณ์ส่วนใหญ่เป็นเพราะเขาใช้ทวีคูณและหารเป็นต้น

ฉันแสดงให้เขาเห็นวิธีการสร้างปัญหาใหม่โดยใช้การเลื่อนบิตและเวลาในการประมวลผลลดลงเหลือประมาณ 30 วินาทีสำหรับคอมไพเลอร์ที่ไม่มีการเพิ่มประสิทธิภาพที่เขามี

ฉันเพิ่งได้คอมไพเลอร์ที่ปรับให้เหมาะสมและรหัสเดียวกันหมุนกราฟิกในเวลา <5 วินาที ฉันดูรหัสประกอบที่คอมไพเลอร์กำลังสร้างและจากสิ่งที่ฉันเห็นตัดสินใจที่นั่นแล้ววันที่ฉันเขียนแอสเซมเบลอร์ก็สิ้นสุดลง


3
ใช่มันเป็นระบบขาวดำหนึ่งบิตโดยเฉพาะมันเป็นบล็อกภาพขาวดำบน Atari ST
lilburne

16
คอมไพเลอร์ที่ได้รับการปรับให้เหมาะสมได้รวบรวมโปรแกรมต้นฉบับหรือเวอร์ชั่นของคุณหรือไม่?
Thorbjørn Ravn Andersen

เกี่ยวกับโปรเซสเซอร์อะไร? ใน 8086 ฉันคาดหวังว่ารหัสที่ดีที่สุดสำหรับการหมุน 8x8 จะโหลด DI ด้วยข้อมูล 16 บิตโดยใช้ SI ทำซ้ำadd di,di / adc al,al / add di,di / adc ah,ahและอื่น ๆ สำหรับการลงทะเบียน 8 บิตทั้ง 8 บิตจากนั้นทำการลงทะเบียนทั้งหมด 8 ครั้งอีกครั้งจากนั้นทำซ้ำขั้นตอนทั้งหมด เวลามากขึ้นและในที่สุดก็บันทึกคำสี่คำใน ax / bx / cx / dx ไม่มีทางที่แอสเซมเบลอร์จะเข้าใกล้สิ่งนั้น
supercat

1
ฉันไม่สามารถนึกถึงแพลตฟอร์มใด ๆ ที่คอมไพเลอร์น่าจะได้รับปัจจัยหรือโค้ดที่เหมาะสมที่สุดสำหรับการหมุน 8x8
supercat

65

เมื่อใดก็ตามที่คอมไพเลอร์เห็นรหัสจุดลอยตัวเวอร์ชันที่เขียนด้วยมือจะเร็วขึ้นหากคุณกำลังใช้คอมไพเลอร์ตัวเก่า (การอัพเดต 2019: นี่ไม่เป็นความจริงโดยทั่วไปสำหรับคอมไพเลอร์สมัยใหม่ โดยเฉพาะเมื่อทำการคอมไพล์เพื่อสิ่งอื่นที่ไม่ใช่ x87 คอมไพเลอร์มีเวลาที่ง่ายขึ้นกับ SSE2 หรือ AVX สำหรับคณิตศาสตร์สเกลาร์หรือไม่ใช่ x86 register stack)

เหตุผลหลักคือคอมไพเลอร์ไม่สามารถทำการปรับให้เหมาะสมที่มีประสิทธิภาพ ดูบทความนี้จาก MSDNสำหรับการสนทนาในเรื่อง นี่คือตัวอย่างที่แอสเซมบลีเวอร์ชันเร็วเป็นสองเท่าของเวอร์ชั่น C (คอมไพล์ด้วย VS2K5):

#include "stdafx.h"
#include <windows.h>

float KahanSum(const float *data, int n)
{
   float sum = 0.0f, C = 0.0f, Y, T;

   for (int i = 0 ; i < n ; ++i) {
      Y = *data++ - C;
      T = sum + Y;
      C = T - sum - Y;
      sum = T;
   }

   return sum;
}

float AsmSum(const float *data, int n)
{
  float result = 0.0f;

  _asm
  {
    mov esi,data
    mov ecx,n
    fldz
    fldz
l1:
    fsubr [esi]
    add esi,4
    fld st(0)
    fadd st(0),st(2)
    fld st(0)
    fsub st(0),st(3)
    fsub st(0),st(2)
    fstp st(2)
    fstp st(2)
    loop l1
    fstp result
    fstp result
  }

  return result;
}

int main (int, char **)
{
  int count = 1000000;

  float *source = new float [count];

  for (int i = 0 ; i < count ; ++i) {
    source [i] = static_cast <float> (rand ()) / static_cast <float> (RAND_MAX);
  }

  LARGE_INTEGER start, mid, end;

  float sum1 = 0.0f, sum2 = 0.0f;

  QueryPerformanceCounter (&start);

  sum1 = KahanSum (source, count);

  QueryPerformanceCounter (&mid);

  sum2 = AsmSum (source, count);

  QueryPerformanceCounter (&end);

  cout << "  C code: " << sum1 << " in " << (mid.QuadPart - start.QuadPart) << endl;
  cout << "asm code: " << sum2 << " in " << (end.QuadPart - mid.QuadPart) << endl;

  return 0;
}

และตัวเลขบางอย่างจากพีซีของฉันที่ใช้งาน build release เริ่มต้น* :

  C code: 500137 in 103884668
asm code: 500137 in 52129147

ฉันเปลี่ยนสลับลูปเป็น dec / jnz และทำให้ไม่มีความแตกต่างกับการกำหนดเวลา - บางครั้งเร็วขึ้นบางครั้งช้ากว่า ฉันเดาว่าความทรงจำที่ จำกัด อยู่ที่ดาวแคระปรับให้เหมาะสมอื่น ๆ (หมายเหตุจากบรรณาธิการ: มีโอกาสมากขึ้นที่คอขวด FP แฝงก็เพียงพอที่จะซ่อนค่าใช้จ่ายเพิ่มเติมการloopทำ Kahan summations สองชุดพร้อมกันสำหรับองค์ประกอบคี่ / คู่และเพิ่มตอนท้ายอาจเพิ่มความเร็วได้ถึง 2 เท่า )

อ๊ะฉันใช้โค้ดรุ่นที่แตกต่างกันเล็กน้อยและเอาท์พุทตัวเลขในทางที่ผิด (เช่น C เร็วขึ้น!) แก้ไขและปรับปรุงผลลัพธ์


20
หรือใน GCC คุณสามารถแก้มือของคอมไพเลอร์ในการเพิ่มประสิทธิภาพจุดลอยตัว (ตราบใดที่คุณสัญญาว่าจะไม่ทำอะไรกับ infinities หรือ NaNs) โดยใช้แฟล็-ffast-mathก พวกเขามีระดับการเพิ่มประสิทธิภาพ-Ofastที่เทียบเท่ากับในปัจจุบัน-O3 -ffast-mathแต่ในอนาคตอาจมีการเพิ่มประสิทธิภาพมากขึ้นที่สามารถนำไปสู่การสร้างรหัสที่ไม่ถูกต้องในกรณีมุม (เช่นรหัสที่อาศัย IEEE NaNs)
David Stone

2
ใช่ลอยไม่สับเปลี่ยนคอมไพเลอร์จะต้องทำสิ่งที่คุณเขียนโดยทั่วไปสิ่งที่ @DavidStone พูด
Alec Teal

2
คุณลองคณิตศาสตร์ SSE หรือไม่ ประสิทธิภาพเป็นหนึ่งในสาเหตุที่ทำให้ MS ละทิ้ง x87 อย่างสมบูรณ์ใน x86_64 และ 80 บิตยาวสองเท่าใน x86
phuclv

4
@Praxeolitic: FP add เป็น commutative ( a+b == b+a) แต่ไม่เชื่อมโยง (เรียงลำดับการดำเนินการดังนั้นการปัดเศษของตัวกลางจึงแตกต่างกัน) เรื่อง: รหัสนี้: ฉันไม่คิดว่า x87 uncommented และการloopเรียนการสอนเป็นการสาธิตที่ยอดเยี่ยมของ asm เร็ว loopเห็นได้ชัดว่าไม่ใช่คอขวดเนื่องจากความล่าช้าของ FP ฉันไม่แน่ใจว่าเขาใช้ระบบปฏิบัติการของ FP หรือไม่ x87 ยากที่มนุษย์จะอ่าน สองfstp resultsส่วนท้ายที่ชัดเจนไม่เหมาะสม การเปิดผลลัพธ์พิเศษจากสแต็กจะทำได้ดีกว่ากับที่ไม่ใช่ร้านค้า เช่นเดียวกับfstp st(0)IIRC
Peter Cordes

2
@PeterCordes: ผลลัพธ์ที่น่าสนใจของการเพิ่มคอมมิวเตชันคือขณะที่ 0 + x และ x + 0 เทียบเท่ากันและไม่เท่ากับ x เสมอไป
supercat

58

คุณสามารถเขียนแอสเซมเบลอร์ได้ดีกว่าคอมไพเลอร์เมื่อคุณรู้มากกว่าคอมไพเลอร์

ในกรณีทั่วไปคอมไพเลอร์ C รุ่นใหม่รู้มากขึ้นเกี่ยวกับวิธีการปรับรหัสให้เหมาะสม: รู้วิธีการทำงานของไพพ์ไลน์โปรเซสเซอร์สามารถลองเรียงลำดับคำแนะนำได้เร็วกว่ามนุษย์ทั่วไปและอื่น ๆ - โดยพื้นฐานแล้วมันเหมือนกับ คอมพิวเตอร์ที่ดีหรือดีกว่าผู้เล่นมนุษย์ที่ดีที่สุดสำหรับเกมกระดาน ฯลฯ เพียงเพราะมันสามารถทำการค้นหาภายในพื้นที่ปัญหาได้เร็วกว่ามนุษย์ทั่วไป แม้ว่าในทางทฤษฎีคุณสามารถทำงานได้ดีเท่ากับคอมพิวเตอร์ในบางกรณี แต่คุณไม่สามารถทำได้ด้วยความเร็วเท่ากันทำให้ไม่สามารถทำได้มากกว่าสองสามกรณี (เช่นคอมไพเลอร์จะมีประสิทธิภาพมากกว่าถ้าคุณพยายามเขียน มากกว่าสองสามรูทีนในแอสเซมเบลอร์)

ในทางกลับกันมีบางกรณีที่คอมไพเลอร์ไม่มีข้อมูลมากพอ - ฉันจะบอกว่าเมื่อทำงานกับฮาร์ดแวร์ภายนอกรูปแบบต่าง ๆ ซึ่งคอมไพเลอร์ไม่มีความรู้ ตัวอย่างหลักอาจเป็นไดร์เวอร์อุปกรณ์ที่แอสเซมเบลอร์รวมกับความรู้ส่วนตัวของฮาร์ดแวร์ที่เป็นปัญหาสามารถให้ผลลัพธ์ที่ดีกว่าคอมไพเลอร์ C

คนอื่น ๆ ได้กล่าวถึงคำแนะนำวัตถุประสงค์พิเศษซึ่งเป็นสิ่งที่ฉันกำลังพูดถึงในย่อหน้าข้างต้น - คำแนะนำที่ผู้แปลอาจมีความรู้ จำกัด หรือไม่มีเลยทำให้มนุษย์สามารถเขียนโค้ดได้เร็วขึ้น


โดยทั่วไปแล้วข้อความนี้เป็นจริง คอมไพเลอร์ทำดีที่สุดสำหรับ DWIW แต่ในบางกรณีขอบแอสเซมเบลอร์การเขียนโค้ดด้วยมือจะทำงานได้เมื่อต้องใช้ประสิทธิภาพแบบเรียลไทม์
spoulson

1
@Liedman: "มันสามารถลองเรียงลำดับคำสั่งเร็วกว่าที่มนุษย์ทำได้" OCaml เป็นที่รู้จักกันดีว่ารวดเร็วและน่าประหลาดใจที่คอมไพเลอร์โค๊ดแบบดั้งเดิมocamloptข้ามการตั้งเวลาคำสั่งบน x86 และปล่อยให้มันขึ้นอยู่กับ CPU เพราะมันสามารถเรียงลำดับใหม่ได้อย่างมีประสิทธิภาพในเวลาทำงาน
Jon Harrop

1
คอมไพเลอร์สมัยใหม่ทำอะไรมากมายและใช้เวลานานเกินกว่าจะทำได้ด้วยมือ ค้นหาบั๊กของตัวติดตาม gcc หรือ llvm เพื่อหาบั๊ก "ที่ไม่ได้รับการเพิ่มประสิทธิภาพ" มีมากมาย. นอกจากนี้เมื่อเขียนเป็น asm คุณสามารถใช้ประโยชน์จากเงื่อนไขเบื้องต้นได้ง่ายขึ้นเช่น "อินพุตนี้ไม่สามารถลบได้" ซึ่งยากสำหรับคอมไพเลอร์ที่จะพิสูจน์
Peter Cordes

48

ในงานของฉันมีสามเหตุผลที่ฉันต้องรู้จักและใช้ชุดประกอบ ตามลำดับความสำคัญ:

  1. การดีบัก - ฉันมักจะได้รับรหัสห้องสมุดที่มีข้อบกพร่องหรือเอกสารไม่สมบูรณ์ ฉันคิดออกว่ากำลังทำอะไรอยู่โดยก้าวเข้าสู่ระดับการชุมนุม ฉันต้องทำสัปดาห์ละครั้ง ฉันยังใช้เป็นเครื่องมือในการแก้ปัญหาที่ตาของฉันไม่เห็นข้อผิดพลาดในสำนวนใน C / C ++ / C # ดูที่การชุมนุมได้ผ่านที่

  2. การปรับให้เหมาะสม - คอมไพเลอร์ทำงานได้ค่อนข้างดีในการปรับให้เหมาะสม แต่ฉันเล่นใน ballpark ที่แตกต่างกว่าส่วนใหญ่ ฉันเขียนรหัสการประมวลผลภาพที่มักเริ่มต้นด้วยรหัสที่มีลักษณะดังนี้:

    for (int y=0; y < imageHeight; y++) {
        for (int x=0; x < imageWidth; x++) {
           // do something
        }
    }

    "ทำบางสิ่งบางอย่าง" มักจะเกิดขึ้นตามลำดับหลายล้านครั้ง (เช่นระหว่าง 3 และ 30) ด้วยการคัดลอกวงจรในช่วง "ทำบางสิ่งบางอย่าง" การเพิ่มประสิทธิภาพจะเพิ่มขึ้นอย่างมหาศาล ฉันมักจะไม่เริ่มต้นที่นั่น - ฉันมักจะเริ่มต้นด้วยการเขียนรหัสให้ทำงานก่อนจากนั้นทำอย่างดีที่สุดเพื่อ refactor C ให้ดีขึ้นตามธรรมชาติ (อัลกอริทึมที่ดีกว่าโหลดน้อยลงในลูปเป็นต้น) ฉันมักจะต้องอ่านแอสเซมบลีเพื่อดูว่าเกิดอะไรขึ้นและไม่ค่อยจำเป็นต้องเขียน ฉันอาจทำเช่นนี้ทุกสองหรือสามเดือน

  3. การทำบางสิ่งบางอย่างภาษาจะไม่ให้ฉัน สิ่งเหล่านี้รวมถึง - การได้รับสถาปัตยกรรมโปรเซสเซอร์และคุณสมบัติโปรเซสเซอร์ที่เฉพาะเจาะจง, การเข้าถึงค่าสถานะที่ไม่ได้อยู่ใน CPU (ผู้ชาย, ฉันอยากให้ C ให้คุณเข้าถึงค่าสถานะพกพา), ฯลฯ ฉันทำแบบนี้อาจปีละครั้งหรือสองปี


คุณไม่เรียงลูปของคุณใช่ไหม :-)
Jon Harrop

1
@plinth: คุณหมายถึง "scraping cycles" อย่างไร
lang2

@ lang2: มันหมายถึงการกำจัดเวลาฟุ่มเฟือยจำนวนมากที่ใช้ในลูปภายในเท่าที่จะทำได้ - อะไรก็ตามที่คอมไพเลอร์ไม่สามารถดึงออกมาได้ซึ่งอาจรวมถึงการใช้พีชคณิตเพื่อยกทวีคูณออกจากหนึ่งลูปเพื่อเพิ่ม ด้านใน ฯลฯ
ฐานของ

1
การเรียงแบบวนซ้ำดูเหมือนจะไม่จำเป็นถ้าคุณทำการส่งผ่านข้อมูลเพียงครั้งเดียว
James M. Lay

@ JamesM.Lay: หากคุณสัมผัสทุกองค์ประกอบเพียงครั้งเดียวลำดับการสำรวจเส้นทางที่ดีกว่าจะช่วยให้คุณมีพื้นที่เชิงพื้นที่ได้ (เช่นใช้ไบต์ทั้งหมดของแคชบรรทัดที่คุณแตะแทนการวนลูปลงคอลัมน์ของเมทริกซ์โดยใช้หนึ่งองค์ประกอบต่อหนึ่งบรรทัดแคช)
Peter Cordes

42

เมื่อใช้คำสั่งวัตถุประสงค์พิเศษเฉพาะชุดคอมไพเลอร์ไม่สนับสนุน

ในการเพิ่มประสิทธิภาพการคำนวณของ CPU สมัยใหม่ด้วยหลายท่อและการแยกย่อยการทำนายคุณจำเป็นต้องจัดโครงสร้างโปรแกรมประกอบในลักษณะที่ทำให้ก) เป็นไปไม่ได้ที่มนุษย์จะเขียนข) ไม่สามารถรักษาได้

นอกจากนี้อัลกอริธึมที่ดีกว่าโครงสร้างข้อมูลและการจัดการหน่วยความจำจะช่วยให้คุณมีลำดับความสำคัญในการทำงานมากกว่าการปรับให้เหมาะสมแบบไมโครที่คุณสามารถทำได้ในการประกอบ


4
+1, แม้ว่าประโยคสุดท้ายไม่ได้อยู่ในการสนทนานี้ - ใครจะสมมติว่าแอสเซมเบลอร์เข้ามาเล่นหลังจากการปรับปรุงอัลกอริทึมและการรับรู้ที่เป็นไปได้ทั้งหมดแล้วเท่านั้น
mghie

18
@ Matt: มือ ASM เขียนมักจะเป็นจำนวนมากที่ดีขึ้นในบางส่วนของงานเล็ก ๆ ซีพียู EE กับที่มีการสนับสนุนผู้ผลิตคอมไพเลอร์เส็งเคร็ง
Zan Lynx

5
"เฉพาะเมื่อใช้ชุดคำสั่งเพื่อวัตถุประสงค์พิเศษบางอย่างเท่านั้น" ?? คุณอาจไม่เคยเขียนรหัส asm ที่ได้รับการปรับปรุงด้วยมือมาก่อน ความรู้ที่ลึกซึ้งเกี่ยวกับสถาปัตยกรรมที่คุณกำลังทำอยู่นั้นเป็นโอกาสที่ดีสำหรับคุณในการสร้างโค้ดที่ดีกว่า (ขนาดและความเร็ว) กว่าคอมไพเลอร์ของคุณ เห็นได้ชัดว่า @mghie แสดงความคิดเห็นคุณจะเริ่มเขียนโค้ด algos ที่ดีที่สุดที่คุณสามารถมีให้กับคุณได้เสมอ แม้แต่คอมไพเลอร์ที่ดีมากคุณต้องเขียนโค้ด C ของคุณในแบบที่ทำให้คอมไพเลอร์เป็นโค้ดที่ดีที่สุด มิฉะนั้นรหัสที่สร้างขึ้นจะเหมาะสมที่สุด
ysap

2
@ysap - สำหรับคอมพิวเตอร์จริง ๆ (ไม่ใช่ชิปฝังตัวเล็ก ๆ ที่ใช้กำลังน้อย) ในการใช้งานจริงรหัส "ดีที่สุด" จะไม่เร็วขึ้นเพราะสำหรับชุดข้อมูลขนาดใหญ่ที่คุณใช้งานจะถูก จำกัด โดยการเข้าถึงหน่วยความจำและข้อบกพร่องของหน้าเว็บ และถ้าคุณไม่มีชุดข้อมูลขนาดใหญ่สิ่งนี้จะเป็นไปอย่างรวดเร็วไม่ว่าทางใดและจะไม่มีประโยชน์ในการเพิ่มประสิทธิภาพ) - วันที่ฉันทำงานส่วนใหญ่ใน C # (ไม่ใช่แม้แต่ c) และประสิทธิภาพเพิ่มขึ้นจากตัวจัดการหน่วยความจำขนาดกะทัดรัด ชั่งน้ำหนักเหนือศีรษะของการรวบรวมขยะการกระชับและการรวบรวม JIT
Nir

4
+1 สำหรับการระบุคอมไพเลอร์ (esp. JIT) สามารถทำงานได้ดีกว่ามนุษย์ถ้าพวกเขาได้รับการปรับให้เหมาะสมกับฮาร์ดแวร์ที่พวกเขาทำงาน
เซบาสเตียน

38

แม้ว่า C คือ "ปิด" กับการจัดการระดับต่ำของข้อมูล 8 บิต, 16- บิต, 32- บิต, 64- บิต, มีการดำเนินการทางคณิตศาสตร์ไม่กี่สนับสนุน C ซึ่งมักจะสามารถดำเนินการได้อย่างสวยงามในคำสั่งประกอบบางอย่าง ชุด:

  1. การคูณจุดคงที่: ผลิตภัณฑ์ของตัวเลข 16 บิตสองรายการคือหมายเลข 32 บิต แต่กฎใน C บอกว่าผลิตภัณฑ์ของตัวเลข 16 บิตสองตัวคือตัวเลข 16 บิตและผลิตภัณฑ์ของตัวเลข 32 บิตสองตัวนั้นเป็นตัวเลข 32 บิต - ครึ่งล่างของทั้งสองกรณี ถ้าคุณต้องการครึ่งบนของ 16x16 คูณหรือ 32x32 คูณคุณต้องเล่นเกมกับคอมไพเลอร์ วิธีทั่วไปคือการร่ายไปที่ความกว้างบิตที่ใหญ่กว่าที่จำเป็นทวีคูณเลื่อนลงและเหวี่ยงกลับ:

    int16_t x, y;
    // int16_t is a typedef for "short"
    // set x and y to something
    int16_t prod = (int16_t)(((int32_t)x*y)>>16);`

    ในกรณีนี้คอมไพเลอร์อาจฉลาดพอที่จะรู้ว่าคุณแค่พยายามเอาครึ่งบนของ 16x16 คูณและทำสิ่งที่ถูกต้องกับ 16x16multiply ดั้งเดิมของเครื่อง หรืออาจจะโง่และต้องมีการเรียกไลบรารี่เพื่อทำคูณ 32x32 ที่เกินความจำเป็นเพราะคุณต้องการเพียง 16 บิตของผลิตภัณฑ์ - แต่มาตรฐาน C ไม่ได้ให้วิธีใดในการแสดงออก

  2. การดำเนินการบางอย่างของการเลื่อนบิต (การหมุน / การพกพา):

    // 256-bit array shifted right in its entirety:
    uint8_t x[32];
    for (int i = 32; --i > 0; )
    {
       x[i] = (x[i] >> 1) | (x[i-1] << 7);
    }
    x[0] >>= 1;

    นี่ไม่ได้ซับซ้อนเกินไปใน C แต่อีกครั้งเว้นแต่ว่าคอมไพเลอร์ฉลาดพอที่จะรู้ว่าคุณกำลังทำอะไรอยู่มันจะทำงานมากมาย "ไม่จำเป็น" ชุดคำสั่งประกอบหลายชุดอนุญาตให้คุณหมุนหรือเลื่อนไปทางซ้าย / ขวาพร้อมกับผลลัพธ์ในการลงทะเบียนพกพาดังนั้นคุณสามารถทำตามคำแนะนำใน 34 คำแนะนำ: โหลดตัวชี้ไปที่จุดเริ่มต้นของอาร์เรย์ บิตเลื่อนไปทางขวาโดยใช้การเพิ่มขึ้นอัตโนมัติบนตัวชี้

    อีกตัวอย่างหนึ่งมีการตอบรับเชิงเส้นการเปลี่ยนแปลงการลงทะเบียน (LFSR) ที่ดำเนินการอย่างสง่างามในการประกอบ: ใช้ชิ้นส่วนของบิต N (8, 16, 32, 64, 64, 128, ฯลฯ ) เลื่อนสิ่งทั้งหมดโดย 1 (ดูด้านบน อัลกอริทึม) แล้วถ้าผลการดำเนินการเป็น 1 แล้วคุณ XOR ในรูปแบบบิตที่แสดงถึงพหุนาม

ต้องบอกว่าฉันจะไม่หันไปใช้เทคนิคเหล่านี้จนกว่าฉันจะมีข้อ จำกัด ด้านประสิทธิภาพที่ร้ายแรง ดังที่คนอื่น ๆ ได้กล่าวไว้การประกอบนั้นยากกว่าการทำเอกสาร / debug / ทดสอบ / ดูแลมากกว่ารหัส C: การเพิ่มประสิทธิภาพนั้นมาพร้อมกับค่าใช้จ่ายที่ร้ายแรง

แก้ไข: 3. การตรวจจับกระแสเกินเป็นไปได้ในแอสเซมบลี (ไม่สามารถทำได้ใน C จริง ๆ ) ซึ่งทำให้อัลกอริทึมบางอย่างง่ายขึ้นมาก


23

คำตอบสั้น ๆ ? บางครั้ง

ในทางเทคนิคแล้วนามธรรมทุกอย่างมีค่าใช้จ่ายและภาษาการเขียนโปรแกรมเป็นสิ่งที่เป็นนามธรรมสำหรับวิธีการทำงานของ CPU อย่างไรก็ตาม C อยู่ใกล้มาก หลายปีที่ผ่านมาฉันจำได้ว่าหัวเราะดัง ๆ เมื่อฉันเข้าสู่ระบบบัญชี UNIX ของฉันและได้รับข้อความพยากรณ์ต่อไปนี้ (เมื่อสิ่งต่าง ๆ เป็นที่นิยม):

ภาษาโปรแกรม C - ภาษาที่รวมความยืดหยุ่นของภาษาแอสเซมบลีกับพลังของภาษาแอสเซมบลี

มันตลกเพราะมันเป็นความจริง: C เป็นเหมือนภาษาแอสเซมบลีแบบพกพา

เป็นที่น่าสังเกตว่าภาษาแอสเซมบลีเพิ่งรัน แต่คุณเขียนมัน อย่างไรก็ตามมีคอมไพเลอร์ระหว่าง C และภาษาแอสเซมบลีที่สร้างขึ้นและที่สำคัญอย่างยิ่งเพราะรหัส C ของคุณเร็วแค่ไหนมีจำนวนมากที่น่ากลัวสำหรับคอมไพเลอร์ของคุณ

เมื่อ gcc เข้ามาในฉากหนึ่งในสิ่งที่ทำให้มันเป็นที่นิยมก็คือมันมักจะดีกว่าคอมไพเลอร์ C ที่ส่งมาพร้อมกับรสชาติของ UNIX เชิงพาณิชย์มากมาย ไม่เพียง แต่มันคือ ANSI C (ไม่มีขยะ K&R C นี้) แต่ก็แข็งแกร่งกว่าและมักจะสร้างรหัสที่ดีกว่า (เร็วกว่า) ไม่เสมอไป แต่บ่อยครั้ง

ฉันบอกคุณทั้งหมดนี้เพราะไม่มีกฎแบบครอบคลุมเกี่ยวกับความเร็วของ C และแอสเซมเบลอร์เนื่องจากไม่มีมาตรฐานวัตถุประสงค์สำหรับ C.

ในทำนองเดียวกันแอสเซมเบลอร์จะแตกต่างกันมากขึ้นอยู่กับโปรเซสเซอร์ที่คุณใช้งานข้อกำหนดระบบของคุณชุดคำสั่งที่คุณใช้และอื่น ๆ ในอดีตมีตระกูลสถาปัตยกรรม CPU สองตระกูล: CISC และ RISC ผู้เล่นที่ใหญ่ที่สุดใน CISC เคยเป็นและยังเป็นสถาปัตยกรรมของ Intel x86 (และชุดคำสั่ง) RISC ครองโลก UNIX (MIPS6000, Alpha, Sparc และอื่น ๆ ) CISC ชนะการต่อสู้เพื่อหัวใจและความคิด

อย่างไรก็ตามภูมิปัญญาที่เป็นที่นิยมเมื่อฉันยังเป็นนักพัฒนาอายุน้อยก็คือ x86 ที่เขียนด้วยมือมักจะเร็วกว่า C มากเพราะวิธีการทำงานของสถาปัตยกรรมมันมีความซับซ้อนที่ได้รับประโยชน์จากมนุษย์ที่ทำมัน RISC ในทางกลับกันดูเหมือนจะออกแบบมาสำหรับคอมไพเลอร์ดังนั้นไม่มีใคร (ฉันรู้) เขียนว่า Sparc แอสเซมเบลอร์ ฉันแน่ใจว่าคนเหล่านี้มีอยู่จริง แต่ไม่ต้องสงสัยเลยว่าพวกเขาทั้งสองเสียสติไปแล้ว

ชุดคำสั่งเป็นจุดสำคัญแม้อยู่ในตระกูลเดียวกันของโปรเซสเซอร์ โปรเซสเซอร์ Intel บางตัวมีส่วนขยายเช่น SSE ถึง SSE4 AMD มีคำสั่ง SIMD ของตนเอง ประโยชน์ของภาษาการเขียนโปรแกรมเช่น C คือใครบางคนสามารถเขียนไลบรารีของพวกเขาดังนั้นจึงเหมาะสำหรับโปรเซสเซอร์ที่คุณใช้งานอยู่ นั่นเป็นงานหนักในแอสเซมเบลอร์

ยังมีการเพิ่มประสิทธิภาพที่คุณสามารถทำได้ในแอสเซมเบลอร์ที่ไม่สามารถคอมไพเลอร์ได้ คำถามที่ใหญ่กว่าคือมันคุ้มหรือไม่

ในที่สุดแม้ว่าผู้ประกอบเป็นผลิตภัณฑ์ของเวลาและเป็นที่นิยมมากขึ้นในช่วงเวลาที่วงจรซีพียูมีราคาแพง ทุกวันนี้ซีพียูที่มีค่าใช้จ่ายประมาณ $ 5-10 ในการผลิต (Intel Atom) สามารถทำอะไรก็ได้ที่ทุกคนต้องการ เหตุผลที่แท้จริงเพียงอย่างเดียวในการเขียนแอสเซมเบลอร์ในวันนี้คือสิ่งต่าง ๆ ในระดับต่ำเช่นบางส่วนของระบบปฏิบัติการ (แม้ว่าเคอร์เนลส่วนใหญ่จะเขียนด้วยภาษา C) ไดรเวอร์อุปกรณ์อาจฝังอุปกรณ์ไว้ (แม้ว่า C เกินไป) และอื่น ๆ หรือเพียงเพื่อเตะ (ซึ่งค่อนข้างร้าย)


มีหลายคนที่ใช้แอสเซมเบลอร์ ARM เป็นภาษาที่เลือกใช้กับเครื่องโอ๊ก (ต้นยุค 90) IIRC พวกเขากล่าวว่าชุดคำสั่ง risc ขนาดเล็กทำให้ง่ายขึ้นและสนุกมากขึ้น แต่ฉันสงสัยว่าเป็นเพราะคอมไพเลอร์ C มาถึงช้าสำหรับ Acorn และคอมไพเลอร์ C ++ ก็ยังไม่เสร็จ
Andrew M

3
"... เพราะไม่มีมาตรฐานส่วนตัวสำหรับ C. " คุณหมายถึงวัตถุประสงค์
โทมัส

@AndrewM: ใช่ฉันเขียนแอปพลิเคชันแบบผสมใน BASIC และ ARM Assembler เป็นเวลาประมาณ 10 ปี ฉันเรียนรู้ C ในช่วงเวลานั้น แต่มันไม่ได้มีประโยชน์มากเพราะมันยุ่งยากเหมือนการประกอบและช้ากว่า Norcroft ทำการปรับแต่งที่ยอดเยี่ยม แต่ฉันคิดว่าชุดคำสั่งแบบมีเงื่อนไขเป็นปัญหาสำหรับคอมไพเลอร์ประจำวัน
Jon Harrop

1
@AndrewM: อืมจริงๆแล้ว ARM เป็นแบบ RISC ที่ทำย้อนหลัง ISA RISC อื่น ๆ ได้รับการออกแบบเริ่มต้นด้วยสิ่งที่คอมไพเลอร์จะใช้ ARM ISA ดูเหมือนว่าจะได้รับการออกแบบเริ่มต้นด้วยสิ่งที่ซีพียูจัดเตรียมไว้ให้ (ตัวเปลี่ยนลำกล้อง, ธงสภาพ→ให้เราเปิดเผยในทุกคำสั่ง)
ninjalj

16

กรณีการใช้งานที่อาจไม่สามารถใช้งานได้อีกต่อไป แต่เพื่อความพึงพอใจของคุณ: ใน Amiga, CPU และกราฟิก / ชิปเสียงจะต่อสู้เพื่อเข้าถึงพื้นที่บางส่วนของ RAM (RAM 2MB แรกจะเฉพาะเจาะจง) ดังนั้นเมื่อคุณมี RAM เพียง 2MB (หรือน้อยกว่า) การแสดงกราฟิกที่ซับซ้อนรวมถึงการเล่นเสียงจะทำให้ประสิทธิภาพของ CPU ลดลง

ในแอสเซมเบลอร์คุณสามารถแทรกโค้ดของคุณได้อย่างชาญฉลาดซึ่ง CPU จะพยายามเข้าถึง RAM เมื่อกราฟิก / ชิปเสียงไม่ว่างภายใน (เช่นเมื่อบัสว่าง) ดังนั้นโดยการจัดเรียงคำสั่งใหม่การใช้งาน CPU แคชอย่างชาญฉลาดเวลาบัสคุณสามารถได้รับผลบางอย่างซึ่งไม่สามารถทำได้โดยใช้ภาษาระดับสูงกว่าเพราะคุณต้องใช้เวลาทุกคำสั่งแม้แต่ใส่ NOP ที่นี่และเก็บสิ่งต่าง ๆ ไว้ ชิปออกจากเรดาร์ของกันและกัน

ซึ่งเป็นอีกสาเหตุหนึ่งที่ทำให้คำสั่ง NOP (No Operation - ไม่ทำอะไรเลย) ของ CPU จะทำให้แอปพลิเคชันของคุณทำงานได้เร็วขึ้น

[แก้ไข] แน่นอนเทคนิคขึ้นอยู่กับการตั้งค่าฮาร์ดแวร์เฉพาะ ซึ่งเป็นเหตุผลหลักที่ทำให้เกม Amiga หลายเกมไม่สามารถรับมือกับ CPU ที่เร็วกว่า: เวลาของคำแนะนำถูกปิด


Amiga ไม่มีชิปแรม 16 MB เช่น 512 kB ถึง 2 MB ขึ้นอยู่กับชิปเซ็ต นอกจากนี้เกม Amiga จำนวนมากไม่ได้ทำงานกับ CPU ที่เร็วขึ้นเนื่องจากเทคนิคเช่นคุณอธิบาย
bk1e

1
@ bk1e - Amiga ผลิตคอมพิวเตอร์หลากหลายรุ่นจำนวนมาก Amiga 500 มาพร้อมกับ ram 512K ขยายเป็น 1Meg ในกรณีของฉัน amigahistory.co.uk/amiedevsys.htmlเป็นเอมิกับ 128Meg Ram
David Waters

@ bk1e: ฉันยืนแก้ไขแล้ว หน่วยความจำของฉันอาจล้มเหลว แต่ชิป RAM ไม่ จำกัด พื้นที่ที่อยู่ 24 บิตแรก (เช่น 16MB) และ Fast ถูกแมปข้างต้นนั้น?
Aaron Digulla

@Aaron Digulla: Wikipedia มีข้อมูลเพิ่มเติมเกี่ยวกับความแตกต่างระหว่างชิพ / แรม / RAM ช้า: en.wikipedia.org/wiki/Amiga_Chip_RAM
bk1e

@ bk1e: ความผิดของฉัน CPU 68k มีช่องทางที่อยู่เพียง 24 ช่องเท่านั้นนั่นคือสาเหตุที่ฉันมี 16MB ในหัวของฉัน
Aaron Digulla

15

จุดหนึ่งซึ่งไม่ใช่คำตอบ
แม้ว่าคุณจะไม่เคยเขียนโปรแกรม แต่ก็พบว่ามีประโยชน์ที่จะทราบชุดคำสั่งแอสเซมเบลอร์อย่างน้อยหนึ่งชุด นี่เป็นส่วนหนึ่งของโปรแกรมเมอร์ที่ไม่มีวันจบที่จะรู้มากขึ้นและดีขึ้นกว่าเดิม ยังมีประโยชน์เมื่อก้าวเข้าสู่เฟรมเวิร์กคุณไม่มีซอร์สโค้ดและอย่างน้อยก็มีความคิดคร่าว ๆ ว่าเกิดอะไรขึ้น นอกจากนี้ยังช่วยให้คุณเข้าใจ JavaByteCode และ. Net IL เนื่องจากทั้งคู่มีลักษณะคล้ายกับแอสเซมเบลอร์

เพื่อตอบคำถามเมื่อคุณมีรหัสจำนวนน้อยหรือมีเวลามาก มีประโยชน์มากที่สุดสำหรับการใช้งานในชิปฝังตัวที่ความซับซ้อนของชิปต่ำและการแข่งขันที่ไม่ดีในคอมไพเลอร์ที่กำหนดเป้าหมายชิปเหล่านี้สามารถทำให้เกิดความสมดุลในความโปรดปรานของมนุษย์ นอกจากนี้สำหรับอุปกรณ์ที่ จำกัด คุณมักจะซื้อขายรหัสขนาด / ขนาดหน่วยความจำ / ประสิทธิภาพในทางที่ยากที่จะสั่งให้คอมไพเลอร์ทำ เช่นฉันรู้ว่าการกระทำของผู้ใช้ไม่ได้ถูกเรียกบ่อย ๆ ดังนั้นฉันจะมีขนาดรหัสเล็กและประสิทธิภาพไม่ดี แต่ฟังก์ชั่นอื่นที่มีลักษณะคล้ายกันนี้ถูกใช้ทุกวินาทีดังนั้นฉันจะมีขนาดรหัสที่ใหญ่ขึ้นและประสิทธิภาพที่เร็วขึ้น นั่นคือการเรียงลำดับของการค้าปิดโปรแกรมเมอร์ประกอบที่มีทักษะสามารถใช้

ฉันต้องการเพิ่มมีพื้นกลางจำนวนมากที่คุณสามารถเขียนโค้ดในคอมไพล์ C และตรวจสอบชุดประกอบที่ผลิตแล้วเปลี่ยนรหัส C หรือบิดและรักษาเป็นชุดประกอบ

เพื่อนของฉันทำงานกับไมโครคอนโทรลเลอร์ปัจจุบันเป็นชิปสำหรับควบคุมมอเตอร์ไฟฟ้าขนาดเล็ก เขาทำงานในการรวมกันของระดับต่ำคและสภา ครั้งหนึ่งเขาเคยบอกฉันว่าเป็นวันที่ดีในการทำงานซึ่งเขาลดลูปหลักจาก 48 คำสั่งเป็น 43 เขายังต้องเผชิญกับตัวเลือกต่าง ๆ เช่นรหัสที่พัฒนาขึ้นเพื่อเติมเต็มชิป 256k และธุรกิจต้องการคุณสมบัติใหม่หรือไม่

  1. ลบคุณสมบัติที่มีอยู่
  2. ลดขนาดของฟีเจอร์ที่มีอยู่บางส่วนหรือทั้งหมดที่อาจมีค่าใช้จ่าย
  3. Advocate ย้ายไปสู่ชิปขนาดใหญ่ที่มีต้นทุนสูงกว่ากินไฟมากขึ้น

ฉันต้องการเพิ่มเป็นนักพัฒนาซอฟต์แวร์เชิงพาณิชย์ที่มีพอร์ทฟอลิโอหรือภาษาแพลตฟอร์มประเภทแอปพลิเคชั่นที่ฉันไม่เคยรู้สึกว่าจำเป็นต้องดำน้ำในการเขียนชุดประกอบ ฉันมีความสุขที่ได้รับความรู้เกี่ยวกับมัน และบางครั้งก็ดีบั๊ก

ฉันรู้ว่าฉันตอบคำถามมากกว่านี้ "ทำไมฉันถึงควรเรียนรู้แอสเซมเบลอร์" แต่ฉันรู้สึกว่ามันเป็นคำถามที่สำคัญกว่าเมื่อเร็วกว่า

ลองอีกครั้งคุณควรคิดถึงการชุมนุม

  • ทำงานกับฟังก์ชั่นระบบปฏิบัติการระดับต่ำ
  • ทำงานกับคอมไพเลอร์
  • ทำงานบนชิปที่ จำกัด อย่างมากระบบฝังตัวและอื่น ๆ

อย่าลืมเปรียบเทียบแอสเซมบลีของคุณกับคอมไพเลอร์ที่สร้างขึ้นเพื่อดูว่าเร็วกว่า / เล็กกว่า / ดีกว่า

เดวิด


4
+1 สำหรับการพิจารณาแอปพลิเคชันแบบฝังบนชิปขนาดเล็ก มีวิศวกรซอฟต์แวร์จำนวนมากที่นี่ไม่พิจารณาว่าฝังตัวหรือคิดว่าหมายถึงสมาร์ทโฟน (32 บิต, MB RAM, MB แฟลช)
Martin

1
แอปพลิเคชันแบบฝังเวลาเป็นตัวอย่างที่ยอดเยี่ยม! มักจะมีคำแนะนำแปลก ๆ (แม้แต่คำสั่งที่เรียบง่ายอย่าง avr's sbiและcbi) ที่คอมไพเลอร์เคยใช้ (และบางครั้งก็ยังทำ) ไม่ได้ใช้ประโยชน์เต็มที่เนื่องจากความรู้ที่ จำกัด ของฮาร์ดแวร์
felixphew

15

ฉันประหลาดใจที่ไม่มีใครพูดเรื่องนี้ strlen()ฟังก์ชั่นได้เร็วขึ้นมากถ้าเขียนในการชุมนุม! ใน C สิ่งที่ดีที่สุดที่คุณสามารถทำได้คือ

int c;
for(c = 0; str[c] != '\0'; c++) {}

ในขณะที่อยู่ในการชุมนุมคุณสามารถเร่งความเร็วได้อย่างมาก:

mov esi, offset string
mov edi, esi
xor ecx, ecx

lp:
mov ax, byte ptr [esi]
cmp al, cl
je  end_1
cmp ah, cl
je end_2
mov bx, byte ptr [esi + 2]
cmp bl, cl
je end_3
cmp bh, cl
je end_4
add esi, 4
jmp lp

end_4:
inc esi

end_3:
inc esi

end_2:
inc esi

end_1:
inc esi

mov ecx, esi
sub ecx, edi

ความยาวอยู่ใน ecx สิ่งนี้เปรียบเทียบตัวละคร 4 ตัวในเวลาดังนั้นมันจึงเร็วขึ้น 4 เท่า และคิดว่าใช้คำสั่งที่สูงของ eax และ ebx มันจะกลายเป็น8 เท่าเร็วกว่ารูทีน C ก่อนหน้านี้!


3
สิ่งนี้เปรียบเทียบกับสิ่งที่อยู่ในstrchr.nfshost.com/optimized_strlen_function ได้อย่างไร
ninjalj

@ninjalj: พวกเขาเหมือนกัน :) ฉันไม่คิดว่ามันจะทำแบบนี้ใน C. มันสามารถปรับปรุงได้เล็กน้อยฉันคิดว่า
BlackBear

ยังคงมีค่าบิตและการดำเนินงานก่อนที่จะเปรียบเทียบในรหัส C มีความเป็นไปได้ที่คอมไพเลอร์จะฉลาดพอที่จะลดการเปรียบเทียบให้สูงและไบต์ต่ำ แต่ฉันจะไม่วางเงินกับมัน จริงๆแล้วมันมีอัลกอริทึมวนรอบที่เร็วกว่าซึ่งอิงจากคุณสมบัติที่(word & 0xFEFEFEFF) & (~word + 0x80808080)เป็นศูนย์ถ้ามีไบต์ทั้งหมดในคำว่าไม่ใช่ศูนย์
2310967

@MichaWiedenmann จริงฉันควรโหลด bx หลังจากเปรียบเทียบอักขระสองตัวในขวาน ขอบคุณ
BlackBear

14

การทำงานของเมทริกซ์โดยใช้คำสั่ง SIMD อาจเร็วกว่าโค้ดที่คอมไพเลอร์สร้างขึ้น


คอมไพเลอร์บางตัว (VectorC ถ้าฉันจำได้ถูกต้อง) สร้างรหัส SIMD ดังนั้นแม้อาจไม่ได้เป็นอาร์กิวเมนต์สำหรับการใช้รหัสแอสเซมบลีอีกต่อไป
OregonGhost

คอมไพเลอร์สร้างรหัสทราบ SSE เพื่อให้อาร์กิวเมนต์ไม่เป็นความจริง
vartec

5
สำหรับหลาย ๆ สถานการณ์คุณสามารถใช้ Intrisics SSE แทนการประกอบ สิ่งนี้จะทำให้โค้ดของคุณพกพาได้มากขึ้น (gcc visual c ++, 64 บิต, 32 บิตเป็นต้น) และคุณไม่จำเป็นต้องทำการลงทะเบียนจัดสรร
Laserallan

1
แน่นอนว่าคุณต้องการ แต่คำถามไม่ได้ถามว่าฉันควรใช้แอสเซมบลีแทนซีที่ไหนมันบอกว่าเมื่อคอมไพเลอร์ C ไม่ได้สร้างรหัสที่ดีกว่า ฉันถือว่าแหล่ง C ที่ไม่ได้ใช้การเรียก SSE โดยตรงหรือชุดประกอบแบบอินไลน์
Mehrdad Afshari

9
แม้ว่า Mehrdad นั้นถูกต้องแล้ว การทำให้ SSE นั้นถูกต้องนั้นค่อนข้างยากสำหรับคอมไพเลอร์และแม้แต่ในสถานการณ์ที่ชัดเจน (สำหรับมนุษย์นั่นคือ) คอมไพเลอร์ส่วนใหญ่จะไม่ใช้มัน
Konrad Rudolph

13

ฉันไม่สามารถให้ตัวอย่างที่เฉพาะเจาะจงเพราะเมื่อหลายปีก่อน แต่มีหลายกรณีที่แอสเซมเบลอร์ที่เขียนด้วยมือสามารถทำคอมไพเลอร์ได้ เหตุผลที่:

  • คุณสามารถเบี่ยงเบนจากการเรียกประชุมผ่านการโต้แย้งในการลงทะเบียน

  • คุณสามารถพิจารณาวิธีการลงทะเบียนอย่างระมัดระวังและหลีกเลี่ยงการเก็บตัวแปรไว้ในหน่วยความจำ

  • สำหรับสิ่งต่าง ๆ เช่นตารางกระโดดคุณสามารถหลีกเลี่ยงการตรวจสอบดัชนีได้

โดยทั่วไปคอมไพเลอร์ทำงานได้ดีมากในการปรับให้เหมาะสมและนั่นก็เกือบจะ "ดีพอ" แต่ในบางสถานการณ์ (เช่นการเรนเดอร์กราฟิก) ที่คุณจ่ายแพงสำหรับทุกรอบคุณสามารถใช้ทางลัดได้เพราะคุณรู้รหัส ที่คอมไพเลอร์ไม่สามารถทำได้เพราะมันจะต้องอยู่ในด้านที่ปลอดภัย

ในความเป็นจริงฉันเคยได้ยินรหัสการแสดงผลกราฟิกบางอย่างที่รูทีนเช่นรูทีนการวาดเส้นหรือโพลิกอนเติมจริงสร้างบล็อกของรหัสเครื่องขนาดเล็กบนสแต็กและเรียกใช้งานที่นั่นเพื่อหลีกเลี่ยงการตัดสินใจอย่างต่อเนื่อง เกี่ยวกับลักษณะของเส้นความกว้างรูปแบบ ฯลฯ

ที่กล่าวมาสิ่งที่ฉันต้องการให้คอมไพเลอร์ต้องทำคือสร้างรหัสแอสเซมบลีที่ดีสำหรับฉัน แต่ไม่ฉลาดเกินไปและพวกเขาส่วนใหญ่ทำเช่นนั้น ในความเป็นจริงหนึ่งในสิ่งที่ฉันเกลียดเกี่ยวกับ Fortran คือการตรวจสอบรหัสในความพยายามที่จะ "เพิ่มประสิทธิภาพ" มันมักจะไม่มีวัตถุประสงค์ที่สำคัญ

โดยปกติเมื่อแอปมีปัญหาประสิทธิภาพการทำงานเกิดจากการออกแบบที่สิ้นเปลือง ทุกวันนี้ฉันจะไม่แนะนำแอสเซมเบลอร์ประกอบประสิทธิภาพเว้นแต่แอพโดยรวมได้รับการปรับแต่งภายในหนึ่งนิ้วของชีวิตมันยังไม่เร็วพอและใช้เวลาทั้งหมดในลูปด้านในอย่างแน่นหนา

เพิ่ม: ฉันเห็นแอปมากมายที่เขียนด้วยภาษาแอสเซมบลีและความได้เปรียบด้านความเร็วหลักเหนือภาษาอย่าง C, Pascal, Fortran และอื่น ๆ เป็นเพราะโปรแกรมเมอร์นั้นระมัดระวังมากกว่าเมื่อเขียนโค้ดในแอสเซมเบลอร์ เขาหรือเธอกำลังจะเขียนโค้ดประมาณ 100 บรรทัดต่อวันโดยไม่คำนึงถึงภาษาและในภาษาคอมไพเลอร์ที่จะมีคำแนะนำ 3 หรือ 400 เท่า


8
+1: "คุณสามารถเบี่ยงเบนจากการเรียกประชุม" คอมไพเลอร์ C / C ++ มีแนวโน้มที่จะดูดกลับหลายค่า พวกเขามักใช้รูปแบบ sret ที่ caller stack จัดสรรบล็อกที่ต่อเนื่องกันสำหรับ struct และส่งการอ้างอิงไปยัง callee เพื่อเติมเข้าไปการคืนค่าหลายค่าในการลงทะเบียนนั้นเร็วขึ้นหลายเท่า
Jon Harrop

1
@ จอน: คอมไพเลอร์ C / C ++ ทำเช่นนั้นได้ดีเมื่อฟังก์ชั่นได้รับการ inline (ฟังก์ชั่นที่ไม่ได้ inline ต้องสอดคล้องกับ ABI นี้ไม่ได้ จำกัด C และ C ++ แต่รูปแบบการเชื่อมโยง)
Ben Voigt

@BenVoigt: นี่คือตัวอย่างเคาน์เตอร์flyingfrogblog.blogspot.co.uk/2012/04/…
Jon Harrop

2
ฉันไม่เห็นการเรียกใช้ฟังก์ชันใด ๆ
Ben Voigt

13

ตัวอย่างจากประสบการณ์ของฉัน:

  • การเข้าถึงคำแนะนำที่ไม่สามารถเข้าถึงได้จาก C ตัวอย่างเช่นสถาปัตยกรรมจำนวนมาก (เช่น x86-64, IA-64, DEC Alpha และ MIPS 64 บิตหรือ PowerPC) สนับสนุนการคูณ 64 บิตโดย 64 บิตทำให้เกิดผลลัพธ์ 128 บิต GCC เพิ่งเพิ่มส่วนขยายที่ให้การเข้าถึงคำแนะนำดังกล่าว แต่ก่อนที่จะต้องมีการชุมนุม และการเข้าถึงคำสั่งนี้สามารถสร้างความแตกต่างอย่างมากบน CPU 64 บิตเมื่อใช้บางสิ่งบางอย่างเช่น RSA - บางครั้งก็เพิ่มประสิทธิภาพการทำงาน 4 เท่า

  • เข้าถึงแฟล็กเฉพาะ CPU สิ่งที่ทำให้ฉันกัดมากคือธงพก เมื่อทำการเพิ่มความแม่นยำแบบหลายจุดหากคุณไม่มีสิทธิ์เข้าถึงซีพียูบิตหนึ่งต้องเปรียบเทียบผลลัพธ์เพื่อดูว่าโอเวอร์โฟลว์หรือไม่ซึ่งต้องใช้คำสั่งเพิ่มเติม 3-5 คำต่อขา และแย่กว่านั้นซึ่งค่อนข้างต่อเนื่องในแง่ของการเข้าถึงข้อมูลซึ่งฆ่าประสิทธิภาพของโปรเซสเซอร์ superscalar ที่ทันสมัย เมื่อทำการประมวลผลจำนวนเต็มจำนวนเต็มนับพันในแถวความสามารถในการใช้ addc นั้นเป็นชัยชนะครั้งใหญ่ (มีปัญหาเรื่องซูเปอร์คาร์ที่มีการโต้แย้งในบิตพกพาเช่นกัน แต่ซีพียูสมัยใหม่จัดการได้ดีมาก)

  • SIMD แม้กระทั่งคอมไพเลอร์ตัวปรับอัตโนมัติสามารถทำได้เพียงแค่กรณีที่ค่อนข้างเรียบง่ายดังนั้นหากคุณต้องการประสิทธิภาพของ SIMD ที่ดีคุณต้องเขียนโค้ดโดยตรง แน่นอนว่าคุณสามารถใช้อินเทอร์นิกแทนการประกอบได้ แต่เมื่อคุณอยู่ในระดับที่แท้จริงคุณก็จะเขียนแอสเซมบลีอยู่แล้วเพียงแค่ใช้คอมไพเลอร์เป็นตัวจัดสรรการลงทะเบียนและกำหนดการแนะนำการใช้งาน (ฉันมักจะใช้ intrinsics สำหรับ SIMD เพียงเพราะคอมไพเลอร์สามารถสร้างฟังก์ชั่นอารัมภบทและ whatnot สำหรับฉันดังนั้นฉันสามารถใช้รหัสเดียวกันบน Linux, OS X และ Windows โดยไม่ต้องจัดการกับปัญหา ABI เช่นการประชุมเรียกฟังก์ชั่น แต่อื่น ๆ ยิ่งกว่านั้นการใช้งานจริงของ SSE นั้นไม่ค่อยดีนัก - Altivec นั้นดูดีกว่าแม้ว่าฉันจะไม่ได้มีประสบการณ์กับมันมากนัก)การแก้ไขข้อผิดพลาดบิต AESหรือSIMD - ใคร ๆ ก็นึกภาพคอมไพเลอร์ที่สามารถวิเคราะห์อัลกอริธึมและสร้างรหัสดังกล่าวได้ แต่สำหรับฉันแล้วรู้สึกว่าคอมไพเลอร์อัจฉริยะนั้นอยู่ห่างจากปัจจุบันอย่างน้อย 30 ปี

ในอีกทางหนึ่งเครื่องมัลติคอร์และระบบกระจายได้เปลี่ยนประสิทธิภาพที่ใหญ่ที่สุดมากมายในทิศทางอื่น - รับความเร็วเพิ่ม 20% พิเศษที่เขียนลูปภายในของคุณในการประกอบหรือ 300% โดยใช้พวกมันข้ามหลายแกนหรือ 10,000% โดย ใช้พวกเขาในกลุ่มของเครื่อง และแน่นอนว่าการเพิ่มประสิทธิภาพระดับสูง (สิ่งต่าง ๆ เช่นฟิวเจอร์สการบันทึก ฯลฯ ) มักจะทำได้ง่ายกว่าในภาษาระดับสูงกว่าเช่น ML หรือ Scala มากกว่า C หรือ asm และมักจะให้ประสิทธิภาพที่ดีกว่ามาก ดังนั้นเช่นเคยมีการแลกเปลี่ยนที่จะทำ


2
@Dennis ซึ่งเป็นสาเหตุที่ฉันเขียน 'แน่นอนคุณสามารถใช้อินทรินแทนการประกอบ แต่เมื่อคุณอยู่ในระดับอินทรินที่คุณกำลังเขียนแอสเซมบลีอยู่แล้วเพียงแค่ใช้คอมไพเลอร์เป็นตัวจัดสรรการลงทะเบียนและ
Jack Lloyd

ยิ่งไปกว่านั้นรหัส SIMD ที่อยู่ภายในนั้นมีแนวโน้มที่จะอ่านได้น้อยกว่ารหัสเดียวกันที่เขียนในแอสเซมเบลอร์: รหัส SIMD จำนวนมากอาศัยการตีความซ้ำโดยนัยของข้อมูลในเวกเตอร์ซึ่งเป็น PITA ที่เกี่ยวข้องกับคอมไพเลอร์
cmaster - คืนสถานะโมนิกา

10

การวนซ้ำแน่นเช่นเมื่อเล่นกับภาพเนื่องจากภาพอาจมีจุดภาพซ้อนกันหลายล้านพิกเซล การนั่งลงและหาวิธีใช้ประโยชน์จากการลงทะเบียนโปรเซสเซอร์จำนวน จำกัด ให้ดีที่สุดสามารถสร้างความแตกต่างได้ นี่คือตัวอย่างชีวิตจริง:

http://danbystrom.se/2008/12/22/optimizing-away-ii/

จากนั้นบ่อยครั้งที่ตัวประมวลผลมีคำแนะนำที่ลึกลับซึ่งมีความพิเศษเกินกว่าที่คอมไพเลอร์จะรบกวน แต่ในบางครั้งโปรแกรมเมอร์แอสเซมเบลอร์สามารถใช้ประโยชน์ได้ดี ใช้คำสั่ง XLAT เป็นตัวอย่าง ยอดเยี่ยมจริง ๆ ถ้าคุณต้องการค้นหาตารางในลูปและตาราง จำกัด 256 ไบต์!

อัปเดต: โอ้มาคิดว่าอะไรสำคัญที่สุดเมื่อเราพูดถึงลูปโดยทั่วไป: คอมไพเลอร์มักจะไม่ทราบว่าจะมีการซ้ำหลายครั้งในกรณีใด! มีเพียงโปรแกรมเมอร์เท่านั้นที่รู้ว่าลูปจะวนซ้ำหลายครั้งและดังนั้นจึงเป็นประโยชน์ในการเตรียมตัวสำหรับลูปที่มีงานพิเศษบางอย่างหรือถ้ามันถูกทำซ้ำสองสามครั้งดังนั้นการตั้งค่าจริงจะใช้เวลานานกว่าการวนซ้ำ ที่คาดหวัง


3
การปรับให้เหมาะสมของส่วนกำหนดค่าโปรไฟล์ให้ข้อมูลคอมไพเลอร์เกี่ยวกับความถี่ในการใช้งานลูป
Zan Lynx

10

บ่อยกว่าที่คุณคิด C ต้องทำสิ่งต่าง ๆ ที่ดูเหมือนไม่จำเป็นจากมุมมองของ coder ของ Assembly เนื่องจากมาตรฐาน C บอกเช่นนั้น

โปรโมชันจำนวนเต็มตัวอย่างเช่น ถ้าคุณต้องการที่จะเปลี่ยนตัวแปรถ่านใน C หนึ่งมักจะคาดหวังว่ารหัสจะทำในความเป็นจริงเพียงแค่นั้นการเปลี่ยนแปลงบิตเดียว

อย่างไรก็ตามมาตรฐานบังคับให้คอมไพเลอร์ทำเครื่องหมายขยายเป็น int ก่อนที่จะเปลี่ยนและตัดทอนผลลัพธ์เป็นอักขระหลังจากนั้นซึ่งอาจทำให้โค้ดซับซ้อนขึ้นอยู่กับสถาปัตยกรรมของโปรเซสเซอร์เป้าหมาย


คอมไพเลอร์คุณภาพสำหรับไมโครขนาดเล็กสามารถหลีกเลี่ยงการประมวลผลส่วนบนของค่าในกรณีที่การทำเช่นนั้นไม่สามารถส่งผลกระทบต่อผลลัพธ์อย่างมีนัยสำคัญ กฎการส่งเสริมการขายทำให้เกิดปัญหา แต่ส่วนใหญ่มักจะในกรณีที่คอมไพเลอร์ไม่มีทางรู้ว่ากรณีมุมใดและไม่เกี่ยวข้องกัน
supercat

9

คุณไม่รู้จริง ๆ ว่ารหัส C ที่เขียนได้ของคุณนั้นเร็วมาก ๆ หรือไม่ถ้าคุณไม่ได้ดูการแยกส่วนของคอมไพเลอร์ที่ผลิตออกมา หลายครั้งที่คุณมองมันและเห็นว่า "การเขียนดี" เป็นเรื่องส่วนตัว

ดังนั้นจึงไม่จำเป็นต้องเขียนในแอสเซมเบลอร์เพื่อรับโค้ดที่เร็วที่สุด แต่ก็คุ้มที่จะรู้ว่าแอสเซมเบลอร์ด้วยเหตุผลเดียวกัน


2
"ดังนั้นจึงไม่จำเป็นต้องเขียนในแอสเซมเบลอร์เพื่อรับรหัสที่เร็วที่สุดเท่าที่เคยมีมา" เอาละฉันไม่เคยเห็นคอมไพเลอร์ทำสิ่งที่ดีที่สุดในทุกกรณีที่ไม่สำคัญ มนุษย์ที่มีประสบการณ์สามารถทำได้ดีกว่าคอมไพเลอร์ในเกือบทุกกรณี ดังนั้นจึงจำเป็นอย่างยิ่งที่จะต้องเขียนในแอสเซมเบลอร์เพื่อรับ "รหัสที่เร็วที่สุด"
cmaster - คืนสถานะโมนิกา

@cmaster ในประสบการณ์ของฉันเอาท์พุทคอมไพเลอร์เป็นอย่างดีสุ่ม บางครั้งมันก็ดีและดีที่สุดและบางครั้งก็เป็น "ขยะนี้จะถูกปล่อยออกมาได้อย่างไร"
sharptooth

9

ฉันได้อ่านคำตอบทั้งหมด (มากกว่า 30) และไม่พบเหตุผลง่ายๆ: ประกอบเร็วกว่า C ถ้าคุณได้อ่านและฝึกIntel® 64 และ IA-32 สถาปัตยกรรมการเพิ่มประสิทธิภาพ Reference Manual , ดังนั้นเหตุผลที่ว่าทำไมการชุมนุมอาจ จะช้ากว่าก็คือคนที่เขียนแอสเซมบลีช้าเช่นนั้นไม่ได้อ่านคู่มือการเพิ่มประสิทธิภาพจะช้ากว่าคือคนที่เขียนเช่นช้าชุมนุมไม่ได้อ่านคู่มือการเพิ่มประสิทธิภาพ

ในวันเก่า ๆ ที่ดีของ Intel 80286 คำสั่งแต่ละคำสั่งถูกดำเนินการที่นับรอบ CPU ที่แน่นอน แต่ตั้งแต่ Pentium Pro เปิดตัวในปี 1995 โปรเซสเซอร์ของ Intel ก็กลายเป็นซูเปอร์คาลล่า ก่อนหน้านั้นใน Pentium ผลิตปี 1993 มีท่อ U และ V: ท่อสองเส้นที่สามารถรันคำสั่งง่าย ๆ สองคำสั่งที่หนึ่งรอบสัญญาณนาฬิกาหากพวกเขาไม่ได้พึ่งพากัน แต่นี่ไม่ได้เป็นการเปรียบเทียบการเปลี่ยนชื่อการดำเนินการตามคำสั่ง & การลงทะเบียนที่ปรากฏใน Pentium Pro และเกือบจะไม่มีการเปลี่ยนแปลงเลยในปัจจุบัน

เพื่ออธิบายด้วยคำสองสามคำรหัสที่เร็วที่สุดคือคำแนะนำไม่ได้ขึ้นอยู่กับผลลัพธ์ก่อนหน้าเช่นคุณควรล้างการลงทะเบียนทั้งหมด (โดย movzx) หรือใช้add rax, 1แทนหรือinc raxเพื่อลบการพึ่งพาสถานะก่อนหน้าของสถานะ ฯลฯ

คุณสามารถอ่านเพิ่มเติมเกี่ยวกับการดำเนินการตามคำสั่งซื้อและการเปลี่ยนชื่อหากการอนุญาตให้เวลามีข้อมูลมากมายในอินเทอร์เน็ต

นอกจากนี้ยังมีปัญหาที่สำคัญอื่น ๆ เช่นการคาดคะเนสาขาจำนวนหน่วยโหลดและหน่วยเก็บจำนวนประตูที่ใช้ micro-ops ฯลฯ แต่สิ่งที่สำคัญที่สุดที่ต้องพิจารณาคือการดำเนินการตามคำสั่ง

คนส่วนใหญ่ไม่ได้ตระหนักถึงการดำเนินการตามคำสั่งซื้อดังนั้นพวกเขาจึงเขียนโปรแกรมประกอบของพวกเขาเช่น 80286 โดยคาดหวังว่าคำสั่งของพวกเขาจะใช้เวลาที่กำหนดในการดำเนินการโดยไม่คำนึงถึงบริบท ในขณะที่คอมไพเลอร์ C ทราบถึงการดำเนินการตามคำสั่งซื้อและสร้างรหัสอย่างถูกต้อง นั่นเป็นสาเหตุที่รหัสของคนที่ไม่รู้จักนั้นช้าลง แต่ถ้าคุณรู้ตัวรหัสของคุณก็จะเร็วขึ้น


8

ฉันคิดว่ากรณีทั่วไปเมื่อแอสเซมเบลอร์เร็วขึ้นคือเมื่อโปรแกรมเมอร์แอสเซมบลีสมาร์ทดูที่เอาต์พุตของคอมไพเลอร์และกล่าวว่า "นี่เป็นเส้นทางที่สำคัญสำหรับประสิทธิภาพการทำงานและฉันสามารถเขียนสิ่งนี้ให้มีประสิทธิภาพมากขึ้น ตั้งแต่เริ่มต้น


7

ทุกอย่างขึ้นอยู่กับปริมาณงานของคุณ

สำหรับการทำงานแบบวันต่อวัน C และ C ++ นั้นใช้ได้ แต่มีภาระงานบางอย่าง (การแปลงใด ๆ ที่เกี่ยวข้องกับวิดีโอ (การบีบอัดการคลายการบีบอัดเอฟเฟกต์ภาพ ฯลฯ )) ที่ค่อนข้างต้องใช้ชุดประกอบ

พวกเขามักจะเกี่ยวข้องกับการใช้ส่วนขยายชิปเซ็ตเฉพาะของ CPU (MME / MMX / SSE / อะไรก็ตาม) ที่ได้รับการปรับแต่งสำหรับการดำเนินการเหล่านั้น


6

ฉันมีการดำเนินการขนย้ายบิตที่ต้องทำใน 192 หรือ 256 บิตทุกการขัดจังหวะที่เกิดขึ้นทุกๆ 50 microseconds

มันเกิดขึ้นจากแผนที่ที่ถูกแก้ไข (ข้อ จำกัด ด้านฮาร์ดแวร์) ใช้ C มันใช้เวลาประมาณ 10 ไมโครวินาทีในการทำ เมื่อฉันแปลสิ่งนี้ไปยังแอสเซมเบลอร์โดยคำนึงถึงคุณสมบัติเฉพาะของแผนที่นี้แคชการลงทะเบียนที่เฉพาะเจาะจงและการใช้การดำเนินการบิตที่มุ่งเน้น; ใช้เวลาน้อยกว่า 3.5 microsecond ในการแสดง


6

มันอาจคุ้มค่าที่จะดูการเพิ่มประสิทธิภาพไม่เปลี่ยนรูปและความบริสุทธิ์โดย Walter Brightไม่ใช่การทดสอบที่มีประวัติ แต่แสดงให้คุณเห็นตัวอย่างหนึ่งที่ดีของความแตกต่างระหว่างการเขียนด้วยลายมือและคอมไพเลอร์ที่สร้าง ASM วอลเตอร์ไบรท์เขียนคอมไพเลอร์ที่ปรับให้เหมาะสมดังนั้นจึงควรดูโพสต์บล็อกอื่น ๆ ของเขา



5

คำตอบง่ายๆ ... คนที่รู้จักการประกอบดี (หรือที่รู้จักมีการอ้างอิงข้างเขาและใช้ประโยชน์จากแคชตัวประมวลผลและฟีเจอร์ไปป์ไลน์ ฯลฯ ) รับประกันได้ว่าจะสามารถสร้างโค้ดได้เร็วกว่าคอมไพเลอร์ใด ๆ

อย่างไรก็ตามความแตกต่างในวันนี้ก็ไม่สำคัญในการใช้งานทั่วไป


1
คุณลืมที่จะพูดว่า "ให้เวลาและความพยายามมาก" และ "สร้างฝันร้ายบำรุงรักษา" เพื่อนร่วมงานของฉันกำลังทำงานเพื่อเพิ่มประสิทธิภาพส่วนสำคัญของรหัสระบบปฏิบัติการและเขาทำงานใน C มากกว่าการประกอบเนื่องจากมันให้เขาตรวจสอบผลกระทบด้านประสิทธิภาพของการเปลี่ยนแปลงระดับสูงภายในระยะเวลาที่เหมาะสม
Artelius

ฉันเห็นด้วย. บางครั้งคุณใช้แมโครและสคริปต์เพื่อสร้างรหัสแอสเซมบลีเพื่อประหยัดเวลาและพัฒนาอย่างรวดเร็ว แอสเซมบลีส่วนใหญ่วันนี้มีแมโคร หากไม่มีคุณสามารถสร้างมาโครตัวประมวลผลล่วงหน้า (ง่าย) โดยใช้สคริปต์ Perl (ค่อนข้างง่าย RegEx)

นี้. แม่นยำ. คอมไพเลอร์เพื่อเอาชนะผู้เชี่ยวชาญโดเมนยังไม่ได้รับการคิดค้น
cmaster - คืนสถานะโมนิกา

4

หนึ่งในความเป็นไปได้ของ PolyPascal รุ่น CP / M-86 (พี่น้องไปสู่ ​​Turbo Pascal) คือการเปลี่ยนสิ่งอำนวยความสะดวก "use-bios-to-output-characters-to-the-screen" ด้วยชุดคำสั่งภาษาเครื่องซึ่งเป็นสาระสำคัญ ได้รับ x และ y และสตริงที่จะใส่

สิ่งนี้ได้รับอนุญาตให้อัพเดทหน้าจอเร็วกว่าที่เคยมาก

มีที่ว่างในไบนารี่เพื่อฝังรหัสเครื่อง (ไม่กี่ร้อยไบต์) และมีสิ่งอื่นอีกด้วยดังนั้นจึงจำเป็นต้องบีบให้มากที่สุด

ปรากฎว่าเนื่องจากหน้าจอเป็น 80x25 พิกัดทั้งสองสามารถพอดีในแต่ละไบต์ดังนั้นทั้งสองสามารถพอดีกับคำสองไบต์ สิ่งนี้อนุญาตให้ทำการคำนวณที่จำเป็นในไบต์ที่น้อยลงเนื่องจากการเพิ่มครั้งเดียวสามารถจัดการทั้งสองค่าพร้อมกัน

สำหรับความรู้ของฉันไม่มีคอมไพเลอร์ C ที่สามารถรวมหลายค่าในการลงทะเบียนทำคำแนะนำ SIMD กับพวกเขาและแยกออกอีกครั้งในภายหลัง (และฉันไม่คิดว่าคำแนะนำเครื่องจะสั้นลงอยู่แล้ว)


4

หนึ่งในตัวอย่างที่มีชื่อเสียงมากขึ้นของการชุมนุมคือจากห่วงการทำแผนที่พื้นผิวของ Michael Abrash ( หมดอายุในรายละเอียดที่นี่ ):

add edx,[DeltaVFrac] ; add in dVFrac
sbb ebp,ebp ; store carry
mov [edi],al ; write pixel n
mov al,[esi] ; fetch pixel n+1
add ecx,ebx ; add in dUFrac
adc esi,[4*ebp + UVStepVCarry]; add in steps

ทุกวันนี้คอมไพเลอร์ส่วนใหญ่จะแสดงคำสั่งเฉพาะซีพียูขั้นสูงว่าเป็นอินทิลินซ์คือฟังก์ชั่นที่รวบรวมไว้ตามคำสั่งจริง MS Visual C ++ รองรับการใช้งานจริงสำหรับ MMX, SSE, SSE2, SSE3 และ SSE4 ดังนั้นคุณต้องกังวลน้อยลงเกี่ยวกับการเลื่อนลงไปที่แอสเซมบลีเพื่อใช้ประโยชน์จากคำแนะนำเฉพาะแพลตฟอร์ม Visual C ++ ยังสามารถใช้ประโยชน์จากสถาปัตยกรรมจริงที่คุณกำหนดเป้าหมายด้วยการตั้งค่า / ARCH ที่เหมาะสม


ยิ่งไปกว่านั้นอินเทอร์ฟินิก SSE เหล่านั้นจะถูกระบุโดย Intel เพื่อให้พกพาได้
James

4

ให้โปรแกรมเมอร์ที่ถูกต้องโปรแกรม Assembler สามารถสร้างได้เร็วกว่าโปรแกรม C (อย่างน้อยที่สุด) มันจะยากที่จะสร้างโปรแกรม C ที่คุณไม่สามารถทำตามคำสั่งอย่างน้อยหนึ่งคำสั่งของ Assembler


นี้จะเป็นบิตที่ถูกต้องมากขึ้น: "มันจะเป็นเรื่องยากที่จะสร้างขี้ปะติ๋วโปรแกรม C ที่ ..." หรือคุณอาจจะบอกว่า: "มันจะเป็นเรื่องยากที่จะพบกับโลกแห่งความจริงโปรแกรม C ที่ ..." จุด มีลูปเล็กน้อยสำหรับคอมไพเลอร์ที่ให้ผลผลิตดีที่สุด อย่างไรก็ตามคำตอบที่ดี
cmaster - คืนสถานะโมนิกา


4

gcc ได้กลายเป็นคอมไพเลอร์ที่ใช้กันอย่างแพร่หลาย การเพิ่มประสิทธิภาพโดยทั่วไปไม่ดี ดีกว่าโปรแกรมเมอร์เขียนแอสเซมเบลอร์ แต่สำหรับประสิทธิภาพที่แท้จริงมันไม่ดี มีคอมไพเลอร์ที่เหลือเชื่อในรหัสที่พวกเขาสร้างขึ้น ดังนั้นตามคำตอบทั่วไปจะมีหลาย ๆ สถานที่ที่คุณสามารถเข้าไปในผลลัพธ์ของคอมไพเลอร์และปรับแต่งแอสเซมเบลอร์เพื่อประสิทธิภาพการทำงาน


8
GCC ใช้การเพิ่มประสิทธิภาพที่ไม่ขึ้นกับแพลตฟอร์ม อย่างไรก็ตามมันไม่ดีนักในการใช้ชุดคำสั่งเฉพาะให้เต็มที่ สำหรับคอมไพเลอร์แบบพกพามันใช้งานได้ดีมาก
Artelius

2
ตกลง การพกพาภาษาที่เข้ามาและเป้าหมายที่ออกไปนั้นยอดเยี่ยมมาก ความสามารถในการพกพานั้นสามารถทำได้ดีในภาษาหรือเป้าหมายเดียว ดังนั้นโอกาสสำหรับมนุษย์ที่จะทำได้ดีกว่านั้นคือมีการเพิ่มประสิทธิภาพเฉพาะเป้าหมายที่เฉพาะเจาะจง
old_timer

+1: GCC ไม่สามารถแข่งขันกับการสร้างรหัสได้อย่างรวดเร็ว แต่ฉันไม่แน่ใจว่าเป็นเพราะพกพาได้ LLVM สามารถพกพาได้และฉันเห็นว่ามันสร้างโค้ดได้เร็วกว่า GCCs 4x
Jon Harrop

ฉันชอบ GCC เนื่องจากเป็นก้อนหินที่แข็งตัวมานานหลายปีแถมยังมีให้ในเกือบทุกแพลตฟอร์มที่สามารถใช้คอมไพเลอร์พกพาที่ทันสมัย น่าเสียดายที่ฉันไม่สามารถสร้าง LLVM (Mac OS X / PPC) ดังนั้นฉันอาจจะไม่สามารถเปลี่ยนไปใช้มันได้ ข้อดีอย่างหนึ่งของ GCC ก็คือถ้าคุณเขียนโค้ดที่สร้างใน GCC คุณมีแนวโน้มใกล้เคียงกับมาตรฐานมากที่สุดและคุณจะมั่นใจได้ว่าสามารถสร้างได้ในเกือบทุกแพลตฟอร์ม

4

Longpoke มีข้อ จำกัด เพียงข้อเดียวคือเวลา เมื่อคุณไม่มีทรัพยากรที่จะปรับการเปลี่ยนแปลงให้เป็นรหัสและใช้เวลาของคุณในการจัดสรรรีจิสเตอร์เพิ่มประสิทธิภาพการรั่วไหลเพียงเล็กน้อยและสิ่งที่ไม่เหมาะสมคอมไพเลอร์จะชนะทุกครั้ง คุณทำการปรับเปลี่ยนรหัส recompile และวัด ทำซ้ำหากจำเป็น

นอกจากนี้คุณยังสามารถทำอะไรได้มากมายในระดับสูง การตรวจสอบแอสเซมบลีที่เกิดขึ้นอาจให้ความประทับใจว่ารหัสนั้นเป็นอึ แต่ในทางปฏิบัติมันจะทำงานได้เร็วกว่าสิ่งที่คุณคิดว่าเร็วกว่า ตัวอย่าง:

int y = data [i]; // ทำบางสิ่งที่นี่ .. call_function (y, ... );

คอมไพเลอร์จะอ่านข้อมูลผลักไปที่สแต็ค (หก) และอ่านภายหลังจากสแต็กและส่งเป็นอาร์กิวเมนต์ ฟังดูเหรอ? อาจเป็นการชดเชยเวลาแฝงที่มีประสิทธิภาพมากและทำให้รันไทม์เร็วขึ้น

// เวอร์ชันที่ดีที่สุด call_function (data [i], ... ); // ไม่เหมาะที่สุดหลังจากทั้งหมด ..

แนวคิดของรุ่นที่ปรับปรุงแล้วคือเราลดความกดดันในการลงทะเบียนและหลีกเลี่ยงการหก แต่ในความจริงแล้วเวอร์ชั่น "shitty" นั้นเร็วกว่า!

ดูรหัสประกอบเพียงแค่ดูคำแนะนำและสรุป: คำแนะนำเพิ่มเติมช้ากว่าจะเป็นการตัดสินที่ผิด

สิ่งที่ควรใส่ใจคือผู้เชี่ยวชาญการชุมนุมหลายคนคิดว่าพวกเขารู้มาก แต่รู้น้อยมาก กฎการเปลี่ยนแปลงจากสถาปัตยกรรมต่อไปเช่นกัน ไม่มีรหัส x86 กระสุนเงินซึ่งเป็นวิธีที่เร็วที่สุดเสมอ วันนี้ดีกว่าที่จะไปโดยกฎของหัวแม่มือ:

  • หน่วยความจำช้า
  • แคชเร็ว
  • ลองใช้แคชดีกว่า
  • คุณจะคิดถึงบ่อยแค่ไหน? คุณมีกลยุทธ์การชดเชยความล่าช้าหรือไม่?
  • คุณสามารถดำเนินการตามคำแนะนำ 10-100 ALU / FPU / SSE สำหรับการแคชหนึ่งครั้ง
  • สถาปัตยกรรมแอปพลิเคชันมีความสำคัญ ..
  • .. แต่มันไม่ได้ช่วยเมื่อปัญหาไม่ได้อยู่ในสถาปัตยกรรม

นอกจากนี้การไว้วางใจมากเกินไปในคอมไพเลอร์อย่างน่าอัศจรรย์การแปลงรหัส C / C ++ ที่คิดไม่ดีออกไปเป็นรหัส "ทฤษฎีที่เหมาะสมที่สุด" คือการคิดที่ปรารถนา คุณต้องรู้คอมไพเลอร์และห่วงโซ่เครื่องมือที่คุณใช้ถ้าคุณสนใจ "ประสิทธิภาพ" ในระดับต่ำนี้

คอมไพเลอร์ใน C / C ++ โดยทั่วไปจะไม่ค่อยดีในการสั่งซื้อการแสดงออกย่อยอีกครั้งเพราะฟังก์ชั่นมีผลข้างเคียงสำหรับผู้เริ่ม ภาษาเชิงหน้าที่ไม่ได้รับผลกระทบจากข้อแม้นี้ แต่ไม่สอดคล้องกับระบบนิเวศในปัจจุบันที่เหมาะสม มีตัวเลือกคอมไพเลอร์เพื่ออนุญาตกฎความแม่นยำที่ผ่อนคลายซึ่งอนุญาตให้ลำดับการดำเนินการเปลี่ยนแปลงโดยตัวสร้างคอมไพเลอร์ / ลิงเกอร์ / โค้ด

หัวข้อนี้เป็นบิตของการสิ้นสุด สำหรับส่วนใหญ่มันไม่เกี่ยวข้องและส่วนที่เหลือพวกเขารู้ว่าสิ่งที่พวกเขากำลังทำอยู่แล้ว

ทุกอย่างลงไปที่สิ่งนี้: "เพื่อทำความเข้าใจสิ่งที่คุณกำลังทำ" มันแตกต่างจากการรู้ว่าคุณกำลังทำอะไรอยู่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.