ฉันควรใช้การคูณหรือการหาร?


119

นี่เป็นคำถามสนุก ๆ โง่ ๆ :

สมมติว่าเราต้องดำเนินการง่ายๆโดยที่เราต้องการครึ่งหนึ่งของค่าของตัวแปร โดยทั่วไปมีสองวิธีในการดำเนินการนี้:

y = x / 2.0;
// or...
y = x * 0.5;

สมมติว่าเราใช้โอเปอเรเตอร์มาตรฐานที่ให้มาพร้อมกับภาษาอันไหนมีประสิทธิภาพดีกว่ากัน?

ฉันเดาว่าการคูณมักจะดีกว่าดังนั้นฉันจึงพยายามยึดติดกับสิ่งนั้นเมื่อฉันเขียนโค้ด แต่ฉันต้องการยืนยันสิ่งนี้

แม้ว่าโดยส่วนตัวแล้วฉันสนใจคำตอบสำหรับPython 2.4-2.5 แต่อย่าลังเลที่จะโพสต์คำตอบสำหรับภาษาอื่น ๆ ! และหากคุณต้องการอย่าลังเลที่จะโพสต์วิธีที่น่าสนใจอื่น ๆ (เช่นการใช้ตัวดำเนินการกะบิต) เช่นกัน


5
คุณใช้เกณฑ์มาตรฐานหรือไม่? มีโค้ดประมาณหนึ่งโหลเท่านั้น คุณเรียนรู้อะไรจากการใช้เกณฑ์มาตรฐาน? [คำแนะนำ: การทำเช่นนั้นจะเร็วกว่าการโพสต์คำถามที่นี่]
S.Lott

4
คำถามที่ดีซึ่งทำให้เกิดคำตอบ / การอภิปรายที่น่าสนใจ ขอบคุณ :)
stealthcopter

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

1
การหารช้ากว่าการคูณมาก แต่ตัวเปรียบเทียบ / VM ที่ชาญฉลาดบางตัวเปลี่ยนการหารเป็นการคูณดังนั้นการทดสอบของคุณจะได้ผลลัพธ์ที่เหมือนกัน (การทดสอบทั้งสองแบบทดสอบการคูณ)
Ivan Kuckir

4
นอกประเด็นไปหน่อย แต่ฉันแค่อยากจะบอกว่าฉันเห็นด้วยกับ @KevinWhitefoot มากแค่ไหน ไม่มีอะไรน่าหงุดหงิดเท่ากับการอ่านจากผู้เทศนามากกว่าคำตอบทางเทคนิคสำหรับคำถามทางเทคนิค ขอบคุณ Kevin สำหรับความคิดเห็นของคุณ!
Jean-François

คำตอบ:


79

งูหลาม:

time python -c 'for i in xrange(int(1e8)): t=12341234234.234 / 2.0'
real    0m26.676s
user    0m25.154s
sys     0m0.076s

time python -c 'for i in xrange(int(1e8)): t=12341234234.234 * 0.5'
real    0m17.932s
user    0m16.481s
sys     0m0.048s

การคูณเร็วขึ้น 33%

Lua:

time lua -e 'for i=1,1e8 do t=12341234234.234 / 2.0 end'
real    0m7.956s
user    0m7.332s
sys     0m0.032s

time lua -e 'for i=1,1e8 do t=12341234234.234 * 0.5 end'
real    0m7.997s
user    0m7.516s
sys     0m0.036s

=> ไม่มีความแตกต่างอย่างแท้จริง

LuaJIT:

time luajit -O -e 'for i=1,1e8 do t=12341234234.234 / 2.0 end'
real    0m1.921s
user    0m1.668s
sys     0m0.004s

time luajit -O -e 'for i=1,1e8 do t=12341234234.234 * 0.5 end'
real    0m1.843s
user    0m1.676s
sys     0m0.000s

=> เร็วขึ้นเพียง 5%

ข้อสรุป: ใน Python การคูณจะเร็วกว่าการหาร แต่เมื่อคุณเข้าใกล้ CPU มากขึ้นโดยใช้ VM หรือ JIT ขั้นสูงมากขึ้นความได้เปรียบจะหายไป ค่อนข้างเป็นไปได้ที่ Python VM ในอนาคตจะทำให้ไม่เกี่ยวข้อง


ขอขอบคุณสำหรับเคล็ดลับในการใช้คำสั่ง time สำหรับการเปรียบเทียบ!
Edmundito

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

7
@rasmus: เมื่อ JIT ดีขึ้นจึงมีแนวโน้มที่จะใช้คำสั่งการคูณของ CPU แม้ว่าคุณจะขอการหารก็ตาม
Ben Voigt

69

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

การเพิ่มประสิทธิภาพก่อนวัยเป็นรากเหง้าของความชั่วร้ายทั้งหมด จำกฎสามข้อของการเพิ่มประสิทธิภาพไว้เสมอ!

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

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

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


7
นั่นไม่ใช่คำพูดของ Knuth แบบเต็ม ดูen.wikipedia.org/wiki/…
Jason S

ไม่ได้มีคำพูดที่แตกต่างกันประมาณ 40 รายการจากแหล่งที่มา ฉันชอบชิ้นส่วนสองสามชิ้นเข้าด้วยกัน
Bill K

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

2
มันทำให้คุณสับสนจริงๆเหรอ? ใช้กฎ 1 และ 2 เสมอเว้นแต่คุณจะไม่ตรงตามข้อกำหนดของไคลเอ็นต์และคุ้นเคยกับระบบทั้งหมดรวมถึงภาษาและลักษณะการแคชของ CPU เมื่อถึงจุดนั้นให้ทำตามขั้นตอนในข้อ 3 เท่านั้นอย่าเพิ่งคิดว่า "เฮ้ถ้าฉันแคชตัวแปรนี้ไว้ในเครื่องแทนที่จะเรียก getter สิ่งต่างๆอาจเร็วขึ้นก่อนอื่นให้พิสูจน์ว่ายังไม่เร็วพอจากนั้นทดสอบการเพิ่มประสิทธิภาพแต่ละรายการแยกกันและ ทิ้งสิ่งที่ไม่ช่วยออกเอกสารอย่างหนักตลอดทาง
Bill K

49

ฉันคิดว่านี่เป็นเรื่องที่น่าสนใจมากจนคุณควรทำทุกอย่างที่ทำให้โค้ดอ่านง่ายขึ้น ฉันสงสัยว่าจะมีใครสังเกตเห็นความแตกต่างไม่ได้

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


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

ฉันเดาว่าบางอย่างคุณอาจสนใจเกี่ยวกับการดำเนินการเพียงครั้งเดียว แต่ฉันคาดหวังว่าใน 99% ของแอปพลิเคชันที่มีอยู่นั้นไม่สำคัญ
Thomas Owens

27
โดยเฉพาะอย่างยิ่งเมื่อ OP กำลังมองหาคำตอบใน Python ฉันสงสัยว่าสิ่งใดที่ต้องการประสิทธิภาพในระดับนั้นจะถูกเขียนด้วย Python
Ed S.

4
การหารอาจเป็นการดำเนินการที่แพงที่สุดในรูทีนสี่แยกสามเหลี่ยมซึ่งเป็นพื้นฐานสำหรับผู้ตรวจจับรังสีส่วนใหญ่ หากคุณจัดเก็บซึ่งกันและกันและคูณแทนที่จะหารคุณจะพบกับการเร่งความเร็วหลายเท่า
solinent

@solinent - ใช่ speedup แต่ฉันสงสัยว่า "หลายครั้ง" - การหารทศนิยมและการคูณไม่ควรแตกต่างกันมากกว่า 4: 1 เว้นแต่ว่าโปรเซสเซอร์ที่มีปัญหานั้นได้รับการปรับให้เหมาะสมที่สุดสำหรับการคูณและไม่ใช่การหารจริงๆ
Jason S

39

การคูณเร็วขึ้นการหารแม่นยำยิ่งขึ้น คุณจะสูญเสียความแม่นยำหากตัวเลขของคุณไม่ใช่เลข 2:

y = x / 3.0;
y = x * 0.333333;  // how many 3's should there be, and how will the compiler round?

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

x = 100.0;
x / 3.0 == x * (1.0/3.0)  // is false in the test I just performed

ปัญหาความเร็วมีแนวโน้มที่จะมีความสำคัญในภาษา C / C ++ หรือ JIT เท่านั้นและแม้ว่าการดำเนินการจะวนซ้ำที่คอขวดเท่านั้น


การหารมีความแม่นยำหากคุณหารด้วยจำนวนเต็ม
แท่น

7
การหารทศนิยมที่มีตัวส่วน> ตัวเศษต้องนำเสนอค่าที่ไม่มีความหมายในบิตลำดับต่ำ การหารมักจะลดความแม่นยำ
S.Lott

8
@ S.Lott: ไม่จริงไม่จริง การใช้งานทศนิยมที่เป็นไปตามมาตรฐาน IEEE-754 ทั้งหมดจะต้องปัดเศษผลลัพธ์ของทุกการดำเนินการอย่างสมบูรณ์แบบ (กล่าวคือไปยังหมายเลขทศนิยมที่ใกล้ที่สุด) ตามโหมดการปัดเศษปัจจุบัน การคูณด้วยซึ่งกันและกันจะทำให้เกิดข้อผิดพลาดมากขึ้นเสมออย่างน้อยก็เพราะว่าต้องมีการปัดเศษอีกครั้ง
Electro

1
ฉันรู้ว่าคำตอบนี้มีอายุเกิน 8 ปีแล้ว แต่มันทำให้เข้าใจผิด คุณสามารถทำการหารได้โดยไม่สูญเสียความแม่นยำอย่างมีนัยสำคัญ: y = x * (1.0/3.0);และโดยทั่วไปคอมไพเลอร์จะคำนวณ 1/3 ในเวลาคอมไพล์ ใช่ 1/3 ไม่สามารถแสดงได้อย่างสมบูรณ์แบบใน IEEE-754 แต่เมื่อคุณทำการคำนวณทศนิยมคุณจะสูญเสียความแม่นยำอยู่ดีไม่ว่าคุณจะทำการคูณหรือหารเนื่องจากบิตลำดับต่ำจะถูกปัดเศษ หากคุณรู้ว่าการคำนวณของคุณมีความอ่อนไหวต่อข้อผิดพลาดในการปัดเศษคุณควรรู้วิธีจัดการกับปัญหานี้ให้ดีที่สุด
Jason S

1
@ JasonS ฉันเพิ่งออกจากโปรแกรมที่ทำงานข้ามคืนเริ่มต้นที่ 1.0 และนับ 1 ULP; ผมเปรียบเทียบผลลัพธ์ของการคูณโดยมีหารด้วย(1.0/3.0) 3.0ฉันได้รับมากถึง 1.0000036666774155 และในพื้นที่นั้น 7.3% ของผลลัพธ์นั้นแตกต่างกัน ฉันคิดว่าพวกเขาต่างกันเพียง 1 บิต แต่เนื่องจากเลขคณิตของ IEEE รับประกันว่าจะปัดเศษเป็นผลลัพธ์ที่ถูกต้องที่สุดฉันจึงยืนตามคำสั่งของฉันว่าการหารนั้นแม่นยำกว่า ความแตกต่างนั้นสำคัญขึ้นอยู่กับคุณหรือไม่
Mark Ransom

25

หากคุณต้องการเพิ่มประสิทธิภาพโค้ดของคุณ แต่ยังคงชัดเจนให้ลองทำดังนี้:

y = x * (1.0 / 2.0);

คอมไพเลอร์ควรจะทำการหารได้ในเวลาคอมไพล์ดังนั้นคุณจะได้รับการคูณ ณ รันไทม์ ฉันคาดหวังว่าความแม่นยำจะเหมือนกับในy = x / 2.0เคส

ในกรณีที่สิ่งนี้อาจมีความสำคัญ LOT อยู่ในโปรเซสเซอร์แบบฝังซึ่งจำเป็นต้องมีการจำลองจุดลอยตัวเพื่อคำนวณเลขคณิตทศนิยม


12
เหมาะกับตัวเอง (และใครก็ตามที่ -1) - เป็นแนวปฏิบัติมาตรฐานในโลกที่ฝังตัวและวิศวกรซอฟต์แวร์ในสาขานั้นพบว่าชัดเจน
Jason S

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

1
OMG มีโปรแกรมเมอร์อย่างน้อย 6 คนที่คิดว่าคณิตศาสตร์ระดับประถมศึกษานั้นไม่ชัดเจน AFAIK การคูณ IEEE 754 เป็นการสับเปลี่ยน (แต่ไม่เชื่อมโยง)
maaartinus

13
บางทีคุณอาจพลาดประเด็น ไม่มีอะไรเกี่ยวข้องกับความถูกต้องทางพีชคณิต ในโลกอุดมคติคุณควรหารด้วยสองได้y = x / 2.0;แต่ในโลกแห่งความเป็นจริงคุณอาจต้องกระตุ้นให้คอมไพเลอร์ทำการคูณด้วยราคาที่ถูกกว่า บางทีมันอาจจะเป็นที่ชัดเจนว่าทำไมy = x * (1.0 / 2.0);จะดีกว่าและมันจะเป็นที่ชัดเจนให้กับรัฐy = x * 0.5;แทน แต่เปลี่ยน2.0ไป7.0และฉันชอบที่จะดูดีกว่าy = x * (1.0 / 7.0); y = x * 0.142857142857;
Jason S

3
สิ่งนี้ทำให้ชัดเจนว่าเหตุใดการใช้วิธีของคุณจึงชัดเจน (และแม่นยำ) มากขึ้น
Juan Martinez

21

แค่จะเพิ่มบางอย่างสำหรับตัวเลือก "ภาษาอื่น ๆ "
C: เนื่องจากนี่เป็นเพียงแบบฝึกหัดทางวิชาการที่ไม่สร้างความแตกต่างฉันจึงคิดว่าจะมีส่วนร่วมในสิ่งที่แตกต่าง

ฉันรวบรวมเพื่อประกอบโดยไม่มีการปรับให้เหมาะสมและดูที่ผลลัพธ์
รหัส:

int main() {

    volatile int a;
    volatile int b;

    asm("## 5/2\n");
    a = 5;
    a = a / 2;

    asm("## 5*0.5");
    b = 5;
    b = b * 0.5;

    asm("## done");

    return a + b;

}

รวบรวมด้วย gcc tdiv.c -O1 -o tdiv.s -S

หารด้วย 2:

movl    $5, -4(%ebp)
movl    -4(%ebp), %eax
movl    %eax, %edx
shrl    $31, %edx
addl    %edx, %eax
sarl    %eax
movl    %eax, -4(%ebp)

และการคูณด้วย 0.5:

movl    $5, -8(%ebp)
movl    -8(%ebp), %eax
pushl   %eax
fildl   (%esp)
leal    4(%esp), %esp
fmuls   LC0
fnstcw  -10(%ebp)
movzwl  -10(%ebp), %eax
orw $3072, %ax
movw    %ax, -12(%ebp)
fldcw   -12(%ebp)
fistpl  -16(%ebp)
fldcw   -10(%ebp)
movl    -16(%ebp), %eax
movl    %eax, -8(%ebp)

อย่างไรก็ตามเมื่อฉันเปลี่ยนints เป็นdoubles (ซึ่งเป็นสิ่งที่ python น่าจะทำ) ฉันได้รับสิ่งนี้:

แผนก:

flds    LC0
fstl    -8(%ebp)
fldl    -8(%ebp)
flds    LC1
fmul    %st, %st(1)
fxch    %st(1)
fstpl   -8(%ebp)
fxch    %st(1)

คูณ:

fstpl   -16(%ebp)
fldl    -16(%ebp)
fmulp   %st, %st(1)
fstpl   -16(%ebp)

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

ตามบันทึกข้างคุณจะเห็นว่าในโปรแกรมของฉันผลตอบแทนmain() a + bเมื่อฉันนำคีย์เวิร์ดที่ระเหยออกไปคุณจะไม่มีทางเดาได้เลยว่าแอสเซมบลีมีลักษณะอย่างไร (ไม่รวมการตั้งค่าโปรแกรม):

## 5/2

## 5*0.5
## done

movl    $5, %eax
leave
ret

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

ขออภัยสำหรับคำตอบที่ยาวเกินไป


1
ไม่ใช่ "คำสั่งเดียว" มันพับได้คงที่
kvanberendonck

5
@kvanberendonck แน่นอนมันเป็นคำสั่งเดียว นับพวกเขา: movl $5, %eax ชื่อของการเพิ่มประสิทธิภาพไม่สำคัญหรือเกี่ยวข้องแม้แต่น้อย คุณแค่อยากจะตอบคำถามอายุสี่ขวบ
Carson Myers

2
ธรรมชาติของการเพิ่มประสิทธิภาพยังคงเป็นสิ่งสำคัญที่ต้องทำความเข้าใจเนื่องจากเป็นไปตามบริบท: จะใช้ได้เฉพาะเมื่อคุณกำลังเพิ่ม / คูณ / หาร / etc ค่าคงที่เวลาคอมไพล์ซึ่งคอมไพเลอร์สามารถทำคณิตศาสตร์ทั้งหมดล่วงหน้าและย้ายคำตอบสุดท้ายไปยังรีจิสเตอร์ที่รันไทม์ การหารช้ากว่าการคูณในกรณีทั่วไป (ตัวหารรันไทม์) มาก แต่ฉันคิดว่าการคูณด้วยส่วนต่างตอบแทนจะช่วยได้ก็ต่อเมื่อคุณหารด้วยตัวส่วนเดียวกันมากกว่าหนึ่งครั้ง คุณอาจจะรู้ทั้งหมดนี้ แต่โปรแกรมเมอร์รุ่นใหม่ ๆ อาจต้องการให้มันสะกดดังนั้น ... ในกรณี
Mike S

10

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

หากคุณกำลังพูดจากระดับสูงมันจะไม่ช้าลงอย่างวัดผลได้สำหรับสิ่งที่คุณน่าจะใช้ คุณจะเห็นในคำตอบอื่น ๆ ผู้คนต้องทำการคูณ / หารหนึ่งล้านเพื่อวัดความแตกต่างระหว่างมิลลิวินาทีย่อยระหว่างสอง

หากคุณยังอยากรู้อยากเห็นจากมุมมองการเพิ่มประสิทธิภาพระดับต่ำ:

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

ระยะเวลาที่ความแตกต่างของท่อขึ้นอยู่กับฮาร์ดแวร์อย่างสมบูรณ์ ฮาร์ดแวร์ล่าสุดที่ฉันใช้คือ 9 รอบสำหรับการคูณ FPU และ 50 รอบสำหรับการหาร FPU ฟังดูมาก แต่คุณจะสูญเสีย 1,000 รอบสำหรับการพลาดความทรงจำเพื่อที่จะได้มองเห็นสิ่งต่างๆ

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

ในทางปฏิบัติหากคุณจะไปถึงระดับของการดูแลเกี่ยวกับความแตกต่างระหว่าง Multiply และ Divide คุณต้องเข้าใจไปป์ไลน์แคชแผงขายสาขาการคาดการณ์ที่ไม่อยู่ในลำดับและการอ้างอิงไปป์ไลน์ หากฟังดูไม่เหมือนที่คุณตั้งใจจะไปกับคำถามนี้คำตอบที่ถูกต้องคือละเว้นความแตกต่างระหว่างสองคำถามนี้

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


7

เขียนตามที่ระบุเจตนาของคุณชัดเจนกว่า

หลังจากโปรแกรมของคุณทำงานแล้วให้หาสิ่งที่ช้าและทำให้เร็วขึ้น

อย่าทำแบบอื่น


6

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

ให้คอมไพเลอร์ทำประสิทธิภาพให้คุณ


5

หากคุณกำลังทำงานกับจำนวนเต็มหรือประเภทจุดลอยตัวอย่าลืมตัวดำเนินการบิทชิฟต์ของคุณ: << >>

    int y = 10;
    y = y >> 1;
    Console.WriteLine("value halved: " + y);
    y = y << 1;
    Console.WriteLine("now value doubled: " + y);

7
การเพิ่มประสิทธิภาพนี้จะดำเนินการโดยอัตโนมัติเบื้องหลังในคอมไพเลอร์สมัยใหม่ใด ๆ
Dustin Getz

มีใครทดสอบว่าตรวจสอบ (โดยใช้ bit ops) ว่าตัวถูกดำเนินการ (?) มีรุ่น shiftable ให้ใช้แทนหรือไม่? ฟังก์ชัน mul (a, b) {if (b is 2) return a << 1; ถ้า (b คือ 4) ส่งคืน a << 2; // ... ฯลฯ กลับ a * b; } ฉันเดาว่า IF แพงมากมันจะมีประสิทธิภาพน้อยกว่า
Christopher Lightfoot

นั่นไม่ได้พิมพ์ใกล้เคียงกับที่ฉันจินตนาการไว้เลย ไม่เป็นไร.
Christopher Lightfoot

สำหรับการดำเนินการ const คอมไพเลอร์ปกติควรทำงาน แต่ที่นี่เราใช้ python ดังนั้นฉันไม่แน่ใจว่ามันฉลาดพอที่จะรู้หรือไม่? (มันควรจะเป็น).
Christopher Lightfoot

ทางลัดที่ดียกเว้นว่าจะไม่ชัดเจนในทันทีว่าเกิดอะไรขึ้น โปรแกรมเมอร์ส่วนใหญ่ไม่รู้จักตัวดำเนินการ bitshift ด้วยซ้ำ
Blazemonger

4

จริงๆแล้วมีเหตุผลที่ดีที่ตามกฎทั่วไปของการคูณด้วยหัวแม่มือจะเร็วกว่าการหาร ลอยส่วนจุดในฮาร์ดแวร์จะทำทั้งที่มีการเปลี่ยนแปลงและขั้นตอนวิธีลบเงื่อนไข ( "หารยาว" กับเลขฐานสอง) หรือ - มีแนวโน้มวันนี้ - ด้วยซ้ำเช่นอูแบรต์ของ อัลกอริทึมการเปลี่ยนและลบต้องการความแม่นยำอย่างน้อยหนึ่งรอบต่อบิต (การทำซ้ำแทบจะเป็นไปไม่ได้ที่จะขนานกันเหมือนกับการเลื่อนและการเพิ่มของการคูณ) และอัลกอริทึมแบบวนซ้ำจะทำการคูณอย่างน้อยหนึ่งครั้งต่อการวนซ้ำไม่ว่าในกรณีใดก็เป็นไปได้สูงที่การแบ่งจะใช้รอบมากขึ้น แน่นอนว่าสิ่งนี้ไม่ได้คำนึงถึงความแปลกประหลาดในคอมไพเลอร์การเคลื่อนย้ายข้อมูลหรือความแม่นยำ แม้ว่าโดยทั่วไปแล้วถ้าคุณกำลังเขียนโค้ดวงในในส่วนที่ไวต่อเวลาของโปรแกรมการเขียน0.5 * xหรือ1.0/2.0 * xมากกว่าx / 2.0เป็นสิ่งที่ควรทำ ความอวดดีของ "รหัสที่ชัดเจนที่สุด" นั้นเป็นเรื่องจริงอย่างแน่นอน แต่ทั้งสามอย่างนี้มีความใกล้เคียงกันมากจนคนอวดรู้ในกรณีนี้เป็นเพียงการอวดรู้


3

ฉันเรียนรู้มาตลอดว่าการคูณมีประสิทธิภาพมากกว่า


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

3

การคูณมักจะเร็วกว่า - ไม่ช้าลงอย่างแน่นอน อย่างไรก็ตามหากความเร็วไม่สำคัญให้เขียนข้อใดที่ชัดเจนที่สุด


2

การหารจุดลอยตัวนั้น (โดยทั่วไป) ช้าเป็นพิเศษดังนั้นในขณะที่การคูณจุดลอยตัวนั้นค่อนข้างช้า แต่ก็น่าจะเร็วกว่าการหารจุดลอยตัว

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


2

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


2

ระวัง "โดยทั่วไปการเดาการคูณจะดีกว่าดังนั้นฉันจึงพยายามยึดติดกับสิ่งนั้นเมื่อฉันเขียนโค้ด"

ในบริบทของคำถามเฉพาะนี้หมายถึง "เร็วกว่า" ในที่นี้ดีกว่า ซึ่งไม่มีประโยชน์มากนัก.

การคิดถึงความเร็วอาจเป็นความผิดพลาดร้ายแรง มีผลกระทบของข้อผิดพลาดที่ลึกซึ้งในรูปแบบพีชคณิตเฉพาะของการคำนวณ

ดูเลขคณิต Floating Point ที่มีการวิเคราะห์ข้อผิดพลาด ดูปัญหาพื้นฐานใน Floating Point คณิตศาสตร์และการวิเคราะห์ข้อผิดพลาด

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

ปัญหาใหญ่ที่สุดมาจากการพยายามจัดการตัวเลขสองตัวที่เกือบเท่ากัน บิตขวาสุด (บิตข้อผิดพลาด) เข้ามาครอบงำผลลัพธ์

>>> for i in range(7):
...     a=1/(10.0**i)
...     b=(1/10.0)**i
...     print i, a, b, a-b
... 
0 1.0 1.0 0.0
1 0.1 0.1 0.0
2 0.01 0.01 -1.73472347598e-18
3 0.001 0.001 -2.16840434497e-19
4 0.0001 0.0001 -1.35525271561e-20
5 1e-05 1e-05 -1.69406589451e-21
6 1e-06 1e-06 -4.23516473627e-22

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


1

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

เว้นเสียแต่ว่ามันจะกลายเป็นปัญหาที่ยึดติดกับสิ่งที่บำรุงรักษา / อ่านได้มากกว่า - ฉันเกลียดมันเมื่อมีคนบอกฉัน แต่มันเป็นเรื่องจริง


1

ผมขอแนะนำให้คูณโดยทั่วไปเพราะคุณไม่ต้องใช้รอบเพื่อให้แน่ใจว่าตัวหารของคุณไม่ใช่ 0 แน่นอนว่าจะใช้ไม่ได้ถ้าตัวหารของคุณเป็นค่าคงที่


1

Java android มีโปรไฟล์บน Samsung GT-S5830

public void Mutiplication()
{
    float a = 1.0f;

    for(int i=0; i<1000000; i++)
    {
        a *= 0.5f;
    }
}
public void Division()
{
    float a = 1.0f;

    for(int i=0; i<1000000; i++)
    {
        a /= 2.0f;
    }
}

ผล?

Multiplications():   time/call: 1524.375 ms
Division():          time/call: 1220.003 ms

หารเร็วกว่าการคูณ (!) ประมาณ 20%


1
ที่จะเป็นจริงคุณควรทดสอบไม่a = i*0.5 a *= 0.5นั่นเป็นวิธีที่โปรแกรมเมอร์ส่วนใหญ่จะใช้การดำเนินการ
Blazemonger

1

เช่นเดียวกับโพสต์ # 24 (การคูณเร็วขึ้น) และ # 30 - แต่บางครั้งก็เข้าใจง่ายเหมือนกัน:

1*1e-6F;

1/1e6F;

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


1

มีความแตกต่าง แต่ขึ้นอยู่กับคอมไพเลอร์ ตอนแรกใน vs2003 (c ++) ฉันไม่มีความแตกต่างอย่างมีนัยสำคัญสำหรับประเภทสองเท่า (จุดลอยตัว 64 บิต) อย่างไรก็ตามการเรียกใช้การทดสอบอีกครั้งใน vs2010 ฉันตรวจพบความแตกต่างอย่างมากถึงปัจจัย 4 เร็วขึ้นสำหรับการคูณ การติดตามสิ่งนี้ดูเหมือนว่า vs2003 และ vs2010 จะสร้างรหัส fpu ที่แตกต่างกัน

บน Pentium 4, 2.8 GHz, เทียบกับ 2003:

  • การคูณ: 8.09
  • หาร: 7.97

สำหรับ Xeon W3530, vs2003:

  • การคูณ: 4.68
  • กอง: 4.64

สำหรับ Xeon W3530, vs2010:

  • การคูณ: 5.33
  • กอง: 21.05.2018

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

แก้ไขเมื่อ 18-03-2556: ข้อสังเกตสำหรับ vs2010


ฉันสงสัยว่ามีเหตุผลอะไรที่คอมไพเลอร์ไม่สามารถแทนที่เช่นn/10.0ด้วยนิพจน์ของฟอร์มได้(n * c1 + n * c2)หรือไม่? ฉันคาดหวังว่าในโปรเซสเซอร์ส่วนใหญ่การหารจะใช้เวลานานกว่าสองการคูณและการหารและฉันเชื่อว่าการหารด้วยค่าคงที่ใด ๆ สามารถให้ผลลัพธ์ที่ปัดเศษได้อย่างถูกต้องในทุกกรณีโดยใช้สูตรที่ระบุ
supercat

1

นี่คือคำตอบสนุก ๆ โง่ ๆ :

x / 2.0เป็นไม่เทียบเท่ากับx 0.5 *

สมมติว่าคุณเขียนวิธีนี้เมื่อ 22 ต.ค. 2551

double half(double x) => x / 2.0;

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

double half(double x) => x * 0.5;

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

double quantize(double x)
{
    if (half(x) > threshold))
        return 1;
    else
        return -1;
}

บรรทัดล่างคือ; เมื่อคุณได้ข้อใดข้อหนึ่งจากทั้งสองแล้วก็จงยึดมันไว้!


1
downvote? ความคิดเห็นที่อธิบายความคิดของคุณเป็นอย่างไร? คำตอบนี้เกี่ยวข้องแน่นอน 100%
l33t

ในวิทยาการคอมพิวเตอร์การคูณ / การหารค่าทศนิยมด้วยกำลังของ 2 จะไม่สูญเสียเว้นแต่ว่าค่านั้นจะกลายเป็นค่าปกติหรือมากเกินไป
อาทิตย์

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

1
“ จุดลอยตัวจะไม่สูญเสียในเวลาที่แบ่ง” เฉพาะเมื่อคุณสร้างด้วยคอมไพเลอร์โบราณที่ปล่อยรหัส x87 ที่เลิกใช้แล้ว สำหรับฮาร์ดแวร์สมัยใหม่เพียงแค่มีตัวแปร float / double จะไม่สูญเสียทั้ง 32 หรือ 64 บิต IEEE 754: en.wikipedia.org/wiki/IEEE_754เนื่องจากวิธีการทำงานของ IEEE 754 เมื่อคุณหารด้วย 2 หรือคูณด้วย 0.5 คุณจะลดลง เลขชี้กำลังด้วย 1 ส่วนที่เหลือของบิต (เครื่องหมาย + แมนทิสซา) ไม่เปลี่ยนแปลง และทั้งสอง2และ0.5ตัวเลขสามารถแสดงใน IEEE 754 ได้อย่างแน่นอนโดยไม่สูญเสียความแม่นยำใด ๆ (ไม่เหมือนเช่น0.4หรือ0.1ทำไม่ได้)
อาทิตย์

0

ถ้าเราสมมติว่าการดำเนินการ add / subtrack มีค่าใช้จ่าย 1 แล้วคูณด้วยต้นทุน 5 และหารต้นทุนประมาณ 20


คุณได้ตัวเลขเหล่านี้มาจากไหน? ประสบการณ์? ความรู้สึกข้างใน? บทความบนอินเทอร์เน็ต? พวกเขาจะเปลี่ยนประเภทข้อมูลต่างๆอย่างไร
kroiz

0

หลังจากการสนทนาที่ยาวนานและน่าสนใจนี่คือสิ่งที่ฉันทำ: ไม่มีคำตอบสุดท้ายสำหรับคำถามนี้ เป็นบางคนชี้ให้เห็นว่ามันขึ้นอยู่กับทั้งฮาร์ดแวร์ (CF piotrkและgast128 ) และคอมไพเลอร์ (CF @Javierทดสอบ 's) หากความเร็วไม่สำคัญหากแอปพลิเคชันของคุณไม่จำเป็นต้องประมวลผลข้อมูลจำนวนมากแบบเรียลไทม์คุณอาจเลือกใช้ความชัดเจนโดยใช้การแบ่งส่วนหากความเร็วในการประมวลผลหรือภาระของโปรเซสเซอร์เป็นปัญหาการคูณอาจจะปลอดภัยที่สุด สุดท้ายนี้หากคุณไม่ทราบแน่ชัดว่าแอปพลิเคชันของคุณจะนำไปใช้งานบนแพลตฟอร์มใดเกณฑ์มาตรฐานก็ไม่มีความหมาย และเพื่อความชัดเจนของรหัสความคิดเห็นเดียวจะทำงานได้!


-3

ในทางเทคนิคไม่มีสิ่งที่เรียกว่าการหารมีเพียงการคูณด้วยองค์ประกอบผกผัน ตัวอย่างเช่นคุณไม่เคยหารด้วย 2, คุณคูณด้วย 0.5

'ส่วน' - เด็กให้ของตัวเองว่ามันมีอยู่เป็นครั้งที่สอง - อยู่เสมอยากที่คูณเพราะ 'หาร' xโดยyหนึ่งในความต้องการเป็นครั้งแรกในการคำนวณค่าy^{-1}ดังกล่าวว่าแล้วทำคูณy*y^{-1} = 1 x*y^{-1}ถ้าคุณรู้y^{-1}แล้วไม่คำนวณจากyต้องเป็นการเพิ่มประสิทธิภาพ


3
ซึ่งละเลยความเป็นจริงของคำสั่งทั้งสองที่มีอยู่ในซิลิกอนโดยสิ้นเชิง
NPSF3000

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

@ BTyler. หากคำสั่งทั้งสองมีอยู่ในซิลิกอนและคำสั่งทั้งสองใช้จำนวนรอบเท่ากัน [ตามที่คาดหวัง] มากกว่าคำสั่งที่ค่อนข้างซับซ้อนนั้นไม่เกี่ยวข้องกับประสิทธิภาพ POV โดยสิ้นเชิง
NPSF3000

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