ตัวเลือกใดดีกว่าที่จะใช้สำหรับหารจำนวนเต็ม 2


406

เทคนิคใดต่อไปนี้เป็นตัวเลือกที่ดีที่สุดสำหรับการหารจำนวนเต็ม 2 และทำไม

เทคนิค 1:

x = x >> 1;

เทคนิค 2:

x = x / 2;

นี่xคือจำนวนเต็ม


75
หากคุณต้องการกำหนดผลลัพธ์xใหม่อีกครั้งไม่เหมาะสมในวิธีนี้: ควรเป็นอย่างใดอย่างหนึ่งx >>= 1หรือx /= 2ขึ้นอยู่กับสิ่งที่คุณตั้งใจจะแสดงออกด้วยการดำเนินการ ไม่ใช่เพราะมันเร็วกว่า (คอมไพเลอร์สมัยใหม่จะคอมไพล์ชุดเทียบเท่าทั้งหมดเพื่อการประกอบที่รวดเร็วและเหมือนกัน) แต่เพราะมันสับสนน้อยกว่า
leftaroundabout

33
ฉันไม่เห็นด้วยกับรอบซ้าย - แต่ฉันคิดว่ามันน่าสังเกตว่ามีการดำเนินการที่เรียกว่าการเปลี่ยนแปลงทางคณิตศาสตร์ในภาษาการเขียนโปรแกรมจำนวนมากที่ช่วยให้บิตการลงชื่อเข้าใช้ ไวยากรณ์อาจเป็นเช่นx = x >>> 1นั้น นอกจากนี้โปรดทราบว่าขึ้นอยู่กับแพลตฟอร์มและคอมไพเลอร์มันอาจจะค่อนข้างสมเหตุสมผลในการปรับการแบ่งแยกและการคูณด้วยตนเองโดยใช้กะ - การคิดถึงตัวควบคุมไมโครเช่นสนับสนุน ALU โดยตรงโดยไม่มีการคูณ
JimmyB

36
ฉันชอบx /= 2เพราะx >>= 1ดูเหมือนว่าจะผูก monadic มากเกินไป)
fredoverflow

19
@leftaroundabout - ฉันแค่คิดว่ามันมากอ่านได้มากขึ้นที่จะเขียนแทนx = x / 2 x /= 2การตั้งค่าอัตนัยอาจ :)
JimmyB

8
@ HannoBinder: ส่วนตัวแน่นอนโดยเฉพาะอย่างยิ่งนิสัย IMO ในภาษาที่ตัวดำเนินการทางคณิตศาสตร์ทั้งหมดมี⬜=ชุดค่าผสมเหล่านี้ควรใช้ทุกครั้งที่เป็นไปได้ มันขจัดเสียงรบกวนและให้ความสำคัญกับความจริงที่xได้รับการแก้ไขในขณะที่=ผู้ปฏิบัติงานทั่วไปค่อนข้างแนะนำว่ามันใช้ค่าใหม่ที่เป็นอิสระจากเก่า - ควรหลีกเลี่ยงการประกอบรวมกัน (เพื่อที่จะสามารถอ่านได้ดังนั้นคนที่รู้เพียงดำเนินการทางคณิตศาสตร์) อาจมีจุดเช่นกัน แต่แล้วคุณจะต้องสละประโยชน์อย่างมาก++, --, +=มากเกินไป
leftaroundabout

คำตอบ:


847

ใช้การดำเนินการที่อธิบายสิ่งที่คุณพยายามทำได้ดีที่สุด

  • หากคุณปฏิบัติต่อตัวเลขเป็นลำดับของบิตให้ใช้ bitshift
  • หากคุณถือว่ามันเป็นค่าตัวเลขให้ใช้การหาร

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

-5 / 2  = -2
-5 >> 1 = -3

(ideone)


20
คำถามเดิมยังคลุมเครือเกี่ยวกับคำว่า 'ดีที่สุด' 'ดีที่สุด' ในแง่ของความเร็วการอ่านคำถามสอบเพื่อหลอกลวงนักเรียน ฯลฯ ... หากไม่มีคำอธิบายว่า 'ดีที่สุด' หมายถึงอะไรนี่น่าจะเป็นคำตอบที่ถูกต้องที่สุด
เรย์

47
ใน C ++ 03 ทั้งคู่มีการใช้งานที่กำหนดไว้สำหรับจำนวนลบและอาจให้ผลลัพธ์เดียวกัน ใน C ++ 11 การหารถูกกำหนดอย่างดีสำหรับจำนวนลบ แต่การเลื่อนยังคงมีการนำไปใช้งาน
James Kanze

2
ในขณะที่คำจำกัดความของ / คือการนำไปใช้ (ทำถ้าปัดเศษขึ้นหรือลงสำหรับจำนวนลบ) กำหนดไว้ในมาตรฐานต้น จะต้องสอดคล้องกับ% (ตัวดำเนินการโมดูโล / ส่วนที่เหลือ)
ctrl-alt-delor

7
"การใช้งานที่กำหนดไว้" หมายถึงว่าผู้ดำเนินการของคอมไพเลอร์ต้องเลือกระหว่างตัวเลือกการนำไปใช้งานหลายอย่างโดยปกติจะมีข้อ จำกัด มากมาย นี่คือหนึ่งในข้อ จำกัด ก็คือว่า%และ/ผู้ประกอบการจะต้องมีความสอดคล้องกันสำหรับทั้งสองตัวถูกดำเนินการในเชิงบวกและเชิงลบเพื่อให้(a/b)*b+(a%b)==aเป็นความจริงโดยไม่คำนึงถึงสัญญาณของและa bโดยปกติผู้เขียนจะทำการเลือกที่จะได้รับประสิทธิภาพที่ดีที่สุดจาก CPU
RBerteig

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

225

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


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

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

9
@ exDM69: คอมไพเลอร์จำนวนมากจะทำเช่นนั้นแม้จะเป็นจำนวนเต็มที่ลงนามแล้วและเพียงแค่ปรับค่าตามความต้องการ เครื่องมือที่ดีในการเล่นกับสิ่งเหล่านี้คือ: tinyurl.com/6uww253
PlasmaHH

19
@ exDM69: และที่เกี่ยวข้องได้อย่างไร ฉันพูดว่า "ถ้าเป็นไปได้" ไม่ใช่ "เสมอ" หากการเพิ่มประสิทธิภาพไม่ถูกต้องการทำด้วยตนเองจะไม่ทำให้ถูกต้อง (บวกดังที่กล่าวไว้ GCC ฉลาดพอที่จะหาการแทนที่ที่เหมาะสมสำหรับจำนวนเต็มที่ลงนามแล้ว)
Cat Plus Plus

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

189

เพื่อกองบน: มีหลายเหตุผลที่จะสนับสนุนการใช้x = x / 2; นี่คือบางส่วน:

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

  • คอมไพเลอร์จะลดสิ่งนี้เป็นการดำเนินการกะต่อไป

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

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

    x = x / 2 + 5;
    x = x >> 1 + 5;  // not the same as above
  • เลขคณิตที่ลงนามแล้วอาจทำให้สิ่งต่าง ๆ มีความซับซ้อนยิ่งกว่าปัญหาที่มีมาก่อนดังกล่าวข้างต้น

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

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


5
นอกจากนี้ยังควรเพิ่มว่าในขณะที่มีกฎของการมาก่อนก็ไม่ผิดอะไรกับการใช้วงเล็บ ในขณะที่ปรับปรุงรหัสการผลิตบางอย่างจริง ๆ แล้วฉันเห็นบางสิ่งบางอย่างของแบบฟอร์มa/b/c*d(ซึ่งa..dแสดงถึงตัวแปรตัวเลข) แทนที่จะอ่านง่าย(a*d)/(b*c)ขึ้น

1
ประสิทธิภาพและการปรับให้เหมาะสมนั้นขึ้นอยู่กับคอมไพเลอร์และเป้าหมาย ตัวอย่างเช่นฉันทำงานบางอย่างให้กับไมโครคอนโทรลเลอร์ที่มีอะไรสูงกว่า -O0 ถูกปิดใช้งานเว้นแต่คุณจะซื้อคอมไพเลอร์เชิงพาณิชย์ดังนั้นคอมไพเลอร์จะไม่เปลี่ยนการแบ่งเป็นบิตกะ ยิ่งไปกว่านั้น bitshifts ใช้เวลาหนึ่งรอบและแบ่งใช้เวลา 18 รอบในเป้าหมายนี้และเนื่องจากความเร็วสัญญาณนาฬิกาของไมโครคอนโทรลเลอร์ต่ำมากนี่อาจเป็นผลการดำเนินงานที่สังเกตเห็นได้ชัดเจน (แต่ขึ้นอยู่กับรหัสของคุณ - คุณควรใช้ / จนกว่าแน่นอน มันเป็นปัญหา!)

4
@JackManey ถ้ามีความเป็นไปได้ใด ๆ ที่a*dหรือb*cจะผลิตล้นในรูปแบบที่อ่านได้น้อยจะไม่เทียบเท่าและมีประโยชน์ที่ชัดเจน ป.ล. ฉันยอมรับว่าวงเล็บเป็นเพื่อนที่ดีที่สุดของคุณ
Mark Ransom

@ MarkRansom - จุดยุติธรรม (แม้ว่าฉันจะวิ่งเข้าไปa/b/c*dในรหัส R - ในบริบทที่มากเกินไปจะหมายความว่ามีบางอย่างผิดปกติกับข้อมูล - และไม่พูดบล็อกที่สำคัญในการปฏิบัติงานของรหัส C)

รหัสx=x/2;เป็นเพียง "ชัดเจน" กว่าx>>=1ถ้าxจะไม่เป็นจำนวนลบคี่หรืออย่างใดอย่างหนึ่งไม่สนใจเกี่ยวกับข้อผิดพลาดออกโดยหนึ่ง มิฉะนั้นx=x/2;และx>>=1;มีความหมายแตกต่างกัน ถ้าสิ่งที่เราต้องการคือค่าที่คำนวณโดยx>>=1ฉันจะถือว่ามันชัดเจนกว่าx = (x & ~1)/2หรือx = (x < 0) ? (x-1)/2 : x/2หรือสูตรอื่น ๆ ที่ฉันสามารถคิดได้โดยใช้การหารด้วยสอง ในทำนองเดียวกันถ้าใครต้องการค่าที่คำนวณโดยที่เป็นที่ชัดเจนกว่าx/=2 ((x + ((unsigned)x>>31)>>1)
supercat

62

ตัวเลือกใดเป็นตัวเลือกที่ดีที่สุดและทำไมถึงต้องหารจำนวนเต็มด้วย 2

ขึ้นอยู่กับว่าคุณหมายถึงอะไรดีที่สุดดีที่สุด

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

หากคุณต้องการหารตัวเลขด้วย 2 ให้ไปกับตัวเลขที่สอง

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

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


58

เพียงใช้การหาร ( /) โดยสันนิษฐานว่ามันชัดเจนขึ้น คอมไพเลอร์จะปรับให้เหมาะสม


34
คอมไพเลอร์ควรปรับให้เหมาะสม
Noctis Skytower

12
หากคอมไพเลอร์ไม่ปรับให้เหมาะสมคุณควรใช้คอมไพเลอร์ที่ดีกว่า
David Stone

3
@DavidStone: ในการประมวลผลสิ่งที่สามารถรวบรวมการเพิ่มประสิทธิภาพของจำนวนเต็มลงนามอาจเป็นลบโดยค่าคงที่อื่น ๆ กว่า 1 จะมีประสิทธิภาพเป็นกะ?
supercat

1
@supercat: นั่นเป็นจุดที่ดี แน่นอนคุณสามารถจัดเก็บค่าในจำนวนเต็มไม่ได้ลงนาม (ซึ่งฉันรู้สึกมีชื่อเสียงที่เลวร้ายยิ่งกว่าที่ควรเมื่อรวมกับคำเตือนไม่ตรงกันลงชื่อ / ไม่ได้ลงนาม) และคอมไพเลอร์ส่วนใหญ่ยังมีวิธีบอกพวกเขาว่า . ฉันชอบการตัดว่าในแมโครเข้ากันได้และมีสิ่งที่ต้องการASSUME(x >= 0); x /= 2;มากกว่าx >>= 1;แต่ที่ยังคงเป็นจุดสำคัญที่ต้องนำมาขึ้น
David Stone

39

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

อย่างไรก็ตามอีกเหตุผลหนึ่งที่เลือกใช้x / 2มากกว่าx >> 1นี้ก็คือพฤติกรรมของ>>การใช้งานนั้นขึ้นอยู่กับการใช้งานหากxมีการลงชื่อintและเป็นลบ

จากส่วนที่ 6.5.7, หัวข้อย่อย 5 ของมาตรฐาน ISO C99:

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


3
เป็นสิ่งที่คุ้มค่าที่จะทราบว่าพฤติกรรมที่การใช้งานจำนวนมากกำหนดไว้สำหรับx>>scalepowerจำนวนลบจะเป็นสิ่งที่จำเป็นเมื่อทำการหารค่าด้วยอำนาจสองสำหรับวัตถุประสงค์เช่นการแสดงผลหน้าจอในขณะที่ใช้งานx/scalefactorจะผิด
supercat

32

x / 2ชัดเจนและx >> 1ไม่เร็วมาก (ตามมาตรฐานไมโคร - เร็วขึ้นประมาณ 30% สำหรับ Java JVM) ดังที่คนอื่น ๆ สังเกตไว้ว่าสำหรับจำนวนลบการปัดเศษจะแตกต่างกันเล็กน้อยดังนั้นคุณต้องพิจารณาสิ่งนี้เมื่อคุณต้องการประมวลผลจำนวนลบ คอมไพเลอร์บางคนอาจแปลงโดยอัตโนมัติx / 2เพื่อx >> 1ถ้าพวกเขารู้จำนวนที่ไม่สามารถลบ (แม้คิดว่าฉันไม่สามารถตรวจสอบได้)

แม้x / 2อาจจะไม่ได้ใช้ (ช้า) การเรียนการสอนส่วน CPU เพราะทางลัดบางส่วนจะเป็นไปได้x >> 1แต่ก็ยังช้ากว่า

(นี่คือ C / C ++ คำถามการเขียนโปรแกรมภาษาอื่น ๆ ที่มีผู้ประกอบการมากขึ้น. สำหรับ Java นอกจากนี้ยังมีการเปลี่ยนแปลงทางด้านขวาไม่ได้ลงนามx >>> 1ซึ่งจะแตกต่างอีกครั้ง. จะช่วยให้การอย่างถูกต้องคำนวณค่าเฉลี่ย (โดยเฉลี่ย) ค่าของค่าทั้งสองเพื่อให้(a + b) >>> 1น้ำพระทัย ส่งคืนค่าเฉลี่ยแม้สำหรับค่าที่มีขนาดใหญ่มากaและbนี่เป็นตัวอย่างที่จำเป็นสำหรับการค้นหาแบบไบนารีหากดัชนีอาร์เรย์สามารถมีขนาดใหญ่มากมีข้อผิดพลาดในการค้นหาแบบไบนารี่หลายรุ่นเพราะพวกเขาใช้(a + b) / 2ในการคำนวณค่าเฉลี่ย ใช้งานไม่ได้วิธีแก้ปัญหาที่ถูกต้องคือใช้(a + b) >>> 1แทน)


1
คอมไพเลอร์ไม่สามารถแปลงx/2เป็นx>>1ในกรณีที่xอาจเป็นลบ หากสิ่งที่ต้องการคือค่าที่x>>1จะคำนวณนั่นจะเร็วกว่าการแสดงออกใด ๆ ที่เกี่ยวข้องx/2ซึ่งคำนวณค่าเดียวกัน
supercat

คุณพูดถูก คอมไพเลอร์อาจแปลงx/2เป็นx>>1หากเขารู้ว่าค่าไม่เป็นลบ ฉันจะพยายามอัปเดตคำตอบของฉัน
โทมัสมูลเลอร์

คอมไพเลอร์ยังคงหลีกเลี่ยงการdivเรียนการสอนด้วยการแปลงx/2เป็น(x + (x<0?1:0)) >> 1(โดยที่ >> คือการคำนวณทางขวา - กะที่เปลี่ยนเป็นบิตเครื่องหมาย) ใช้เวลา 4 คำแนะนำ: คัดลอกค่า, shr (เพื่อรับเพียงบิตเครื่องหมายใน reg), เพิ่ม, sar goo.gl/4F8Ms4
Peter Cordes

คำถามถูกแท็กเป็น C และ C ++
Josh Sanford

22

Knuth กล่าวว่า:

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

ดังนั้นฉันแนะนำให้ใช้ x /= 2;

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


4
สิ่งที่คุณจะพิจารณาวิธีการที่ต้องการของการลดจำนวนลงด้วยพลังของสองถ้าใครต้องการจำนวนเต็มเพื่อสนับสนุนความจริง (ซึ่งใช้กับจำนวนธรรมชาติและจำนวนจริง) ที่ (n + d) / d = (n / d) + 1? การละเมิดที่ความจริงเมื่อปรับขนาดกราฟิกจะทำให้ "ตะเข็บ" ที่มองเห็นได้ในผล หากต้องการสิ่งที่เหมือนกันและสมมาตรเกือบเป็นศูนย์(n+8)>>4ทำงานได้ดี คุณสามารถเสนอวิธีการที่ชัดเจนหรือมีประสิทธิภาพโดยไม่ต้องใช้การเปลี่ยนแปลงที่ถูกต้องหรือไม่?
supercat

19

ลองดูที่คอมไพเลอร์เอาต์พุตเพื่อช่วยคุณตัดสินใจ ฉันรันการทดสอบนี้ใน x86-64 ด้วย
gcc (GCC) 4.2.1 20070719 [FreeBSD]

ยังเห็นผลเรียบเรียงออนไลน์ได้ที่ godbolt

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

รหัส C พร้อมเอาต์พุตประกอบ:

สำหรับการแบ่งข้อมูลของคุณจะเป็น

int div2signed(int a) {
  return a / 2;
}

และสิ่งนี้จะคอมไพล์

    movl    %edi, %eax
    shrl    $31, %eax
    addl    %edi, %eax
    sarl    %eax
    ret

ในทำนองเดียวกันสำหรับการเปลี่ยนแปลง

int shr2signed(int a) {
  return a >> 1;
}

ด้วยเอาท์พุท:

    sarl    %edi
    movl    %edi, %eax
    ret

มันอาจแก้ไขข้อผิดพลาดแบบ off-one หรืออาจทำให้เกิดข้อผิดพลาดแบบ off-by-one (เทียบกับสิ่งที่จำเป็นจริง) ซึ่งจำเป็นต้องใช้รหัสเพิ่มเติมเพื่อแก้ไข หากสิ่งที่ต้องการคือผลลัพธ์ที่เป็นพื้นการเปลี่ยนแปลงที่ถูกต้องนั้นเร็วและง่ายกว่าทางเลือกอื่น ๆ ที่ฉันรู้
supercat

หากคุณต้องการพื้นก็ไม่น่าที่คุณจะอธิบายสิ่งที่คุณต้องการเป็น "หารด้วย 2"
Michael Donohue

การหารของทั้งจำนวนธรรมชาติและจำนวนจริงจะสนับสนุนความจริงที่ (n + d) / d = (n / d) +1 การหารจำนวนจริงยังคงอยู่ (-n) / d = - (n / d) ซึ่งเป็นสัจพจน์ที่ไม่มีความหมายกับจำนวนธรรมชาติ มันเป็นไปไม่ได้ที่จะมีตัวดำเนินการหารที่ถูกปิดในจำนวนเต็มและเป็นไปตามหลักการทั้งสอง ในใจของฉันบอกว่าสัจพจน์แรกควรมีไว้สำหรับตัวเลขทั้งหมดและลำดับที่สองสำหรับ reals นั้นดูเป็นธรรมชาติมากกว่าการบอกว่าอันดับแรกควรเก็บไว้สำหรับจำนวนเต็มหรือ reals ทั้งหมด แต่ไม่ใช่สำหรับจำนวนเต็ม นอกจากนี้ฉันสงสัยในกรณีที่ความจริงที่สองมีประโยชน์จริงๆ
supercat

1
dวิธีการแบ่งจำนวนเต็มซึ่งตอบสนองความจริงแรกจะแบ่งพาร์ติชันเส้นจำนวนลงในพื้นที่ที่มีขนาด การแบ่งพาร์ติชันดังกล่าวมีประโยชน์สำหรับวัตถุประสงค์หลายประการ แม้ว่าจะมีจุดพักที่อื่นแทนที่จะอยู่ระหว่าง 0 ถึง -1 การเพิ่มออฟเซ็ตจะเป็นการย้าย ส่วนจำนวนเต็มซึ่งตอบสนองความจริงที่สองจะแบ่งพาร์ติชันเส้นจำนวนลงในพื้นที่ซึ่งส่วนใหญ่จะเป็นขนาดแต่หนึ่งซึ่งมีขนาดd 2*d-1ไม่เหมือนกันทุกส่วน "เท่ากัน" คุณสามารถให้คำแนะนำว่าเมื่อใดที่พาร์ทิชันลูกบอลคี่บอลมีประโยชน์จริงหรือไม่?
supercat

คอมไพเลอร์เอาต์พุตของคุณสำหรับ shr2signed ผิด gcc บน x86 เลือกที่จะใช้ >> จำนวนเต็มที่ลงนามด้วยการเปลี่ยนแปลงทางคณิตศาสตร์ ( sar) goo.gl/KRgIkb โพสต์รายชื่อผู้รับจดหมาย ( gcc.gnu.org/ml/gcc/2000-04/msg00152.html ) ยืนยันว่า gcc ใช้การคำนวณทางคณิตศาสตร์ในอดีตสำหรับ ints ที่ลงนามดังนั้นจึงไม่น่าเป็นไปได้อย่างมากที่ FreeBSD gcc 4.2.1 จะใช้การเปลี่ยนแปลงที่ไม่ได้ลงนาม ฉันอัปเดตโพสต์ของคุณเพื่อแก้ไขปัญหานั้นและวรรคต้นบอกว่าทั้งสองใช้ shr เมื่อเป็นจริง SAR ที่พวกเขาทั้งสองใช้ SHR คือวิธีที่แตกบิตสัญญาณสำหรับ/เคส รวมลิงค์ godbolt ด้วย
Peter Cordes

15

เพียงแค่บันทึกที่เพิ่มเข้ามา -

x * = 0.5 มักจะเร็วกว่าในภาษาที่ใช้ VM บางภาษาโดยเฉพาะ actionscript เนื่องจากตัวแปรไม่ต้องถูกตรวจสอบเพื่อหารด้วย 0


2
@minitech: นั่นเป็นการทดสอบที่ไม่ดี รหัสทั้งหมดในการทดสอบเป็นค่าคงที่ ก่อนที่รหัสจะเป็นแบบ JITed มันจะกำจัดค่าคงที่ทั้งหมด

@ M28: ฉันค่อนข้างแน่ใจว่าภายในของ jsPerf (เช่นeval) ทำให้เกิดขึ้นอีกครั้งในแต่ละครั้ง ไม่ว่าใช่มันเป็นการทดสอบที่แย่มากเพราะเป็นการเพิ่มประสิทธิภาพที่โง่มาก
Ry-

13

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


12

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

x >> 1 จะดีกว่า x / 2 ฉันตรวจสอบบน ideone.com ด้วยการรันโปรแกรมที่มีการดำเนินการมากกว่า 10 ^ 10 ส่วนโดยการดำเนินการ 2 ครั้ง x / 2 ใช้เวลาเกือบ 5.5 วินาทีในขณะที่ x >> 1 ใช้เวลาเกือบ 2.6 วินาทีในโปรแกรมเดียวกัน


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

12

ฉันจะบอกว่ามีหลายสิ่งที่ต้องพิจารณา

  1. Bitshift ควรเร็วขึ้นเนื่องจากไม่จำเป็นต้องมีการคำนวณพิเศษใด ๆ ในการเลื่อนบิตอย่างไรก็ตามตามที่กล่าวไว้มีปัญหาที่อาจเกิดขึ้นกับตัวเลขติดลบ หากคุณมั่นใจว่าจะมีตัวเลขที่เป็นบวกและกำลังมองหาความเร็วแล้วฉันจะแนะนำ bitshift

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

  3. การดำเนินการอาจมีความเร็วแตกต่างกันขึ้นอยู่กับฮาร์ดแวร์ที่ใช้งาน กฎหมายของอัมดาลคือการทำให้คดีเป็นเรื่องธรรมดา ดังนั้นคุณอาจมีฮาร์ดแวร์ที่สามารถทำงานต่าง ๆ ได้เร็วกว่าอุปกรณ์อื่น ๆ ตัวอย่างเช่นการคูณด้วย 0.5 อาจเร็วกว่าการหารด้วย 2 (คุณอาจต้องใช้พื้นที่ของการคูณหากคุณต้องการบังคับใช้การหารจำนวนเต็ม)

หากคุณมีประสิทธิภาพที่แท้จริงฉันขอแนะนำให้สร้างการทดสอบบางอย่างที่สามารถทำงานได้หลายล้านครั้ง ตัวอย่างการดำเนินการหลายครั้ง (ขนาดตัวอย่างของคุณ) เพื่อกำหนดว่าอันไหนที่ดีที่สุดในเชิงสถิติด้วย OS / Hardware / Compiler / Code ของคุณ


2
"Bitshift น่าจะเร็วกว่า" คอมไพเลอร์จะปรับการแบ่งส่วนให้เป็นบิตกะ
Trevor Hickey

ฉันหวังว่าพวกเขาจะอยู่ แต่ถ้าคุณมีการเข้าถึงแหล่งที่มาของคอมไพเลอร์ที่คุณไม่สามารถมั่นใจได้ :)
เจมส์ Oravec

1
ฉันยังขอแนะนำบิตเอฟเฟ็กต์หากการใช้งานของคนหนึ่งจัดการกับมันในแบบที่ธรรมดาที่สุดและวิธีที่เราต้องการจัดการกับจำนวนลบนั้นตรงกับสิ่งที่>>ทำและไม่ตรงกับสิ่งที่/ทำ
supercat

12

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


11

x = x / 2; เป็นรหัสที่เหมาะสมที่จะใช้ .. แต่การดำเนินการขึ้นอยู่กับโปรแกรมของคุณเองว่าผลลัพธ์ที่คุณต้องการผลิต


11

ทำให้ความตั้งใจของคุณชัดเจนขึ้น ... ตัวอย่างเช่นหากคุณต้องการหารใช้ x / 2 และให้คอมไพเลอร์ปรับแต่งมันให้เหมาะสมเพื่อเปลี่ยนโอเปอเรเตอร์ (หรืออะไรก็ได้)

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


10

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

  • หากคุณกำลังทำงานกับไมโครคอนโทรลเลอร์ 8 บิตหรืออะไรก็ตามที่ไม่ได้รับการสนับสนุนจากฮาร์ดแวร์สำหรับการคูณการเลื่อนบิตเป็นสิ่งที่คาดหวังและเป็นเรื่องธรรมดาและในขณะที่คอมไพเลอร์จะกลายx /= 2เป็นx >>= 1การปรากฏตัวของสัญลักษณ์ส่วนหนึ่งจะยกคิ้วมากขึ้นในสภาพแวดล้อมที่ดีกว่า ใช้ shift เพื่อทำให้เกิดการหาร
  • หากคุณกำลังทำงานในสภาพแวดล้อมที่มีความสำคัญต่อประสิทธิภาพหรือส่วนของรหัสหรือรหัสของคุณอาจถูกคอมไพล์ด้วยการเพิ่มประสิทธิภาพของคอมไพเลอร์ x >>= 1ความคิดเห็นอธิบายเหตุผลของมันอาจจะดีที่สุดเพียงเพื่อความชัดเจนของวัตถุประสงค์
  • x /= 2หากคุณไม่ได้อยู่ภายใต้การเป็นหนึ่งในเงื่อนไขดังกล่าวข้างต้นทำให้รหัสของคุณอ่านได้มากขึ้นโดยเพียงแค่ใช้ ดีกว่าที่จะบันทึกโปรแกรมเมอร์ถัดไปที่เกิดขึ้นเพื่อดูรหัสของคุณ 10 วินาทีสองครั้งในการดำเนินการกะของคุณกว่าที่จะพิสูจน์ว่าคุณรู้ว่าการเปลี่ยนแปลงนั้นมีประสิทธิภาพมากขึ้น sans คอมไพเลอร์การเพิ่มประสิทธิภาพ

ทั้งหมดเหล่านี้ถือว่าเป็นจำนวนเต็มไม่ได้ลงนาม การเปลี่ยนแปลงอย่างง่ายอาจไม่ใช่สิ่งที่คุณต้องการลงชื่อ นอกจากนี้ DanielH ยังแนะนำประเด็นที่ดีเกี่ยวกับการใช้งานx *= 0.5สำหรับบางภาษาเช่น ActionScript


8

mod 2, การทดสอบสำหรับ = 1 เนื่องจากไวยากรณ์ใน c แต่นี่อาจเร็วที่สุด


7

การแบ่งการเปลี่ยนแปลงที่ถูกต้องโดยทั่วไป:

q = i >> n; is the same as: q = i / 2**n;

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

ลองดูที่หน้านี้จากการเขียนโปรแกรม C ++ ที่เป็นประโยชน์


หากใครต้องการคำนวณค่าที่(x+128)>>8จะคำนวณสำหรับค่าที่xไม่ใกล้ค่าสูงสุดวิธีหนึ่งทำได้อย่างรัดกุมโดยไม่ต้องกะ? โปรดทราบว่า(x+128)/256จะไม่ทำงาน คุณรู้หรือไม่เกี่ยวกับการแสดงออกที่ดีที่จะ?
supercat

7

เห็นได้ชัดว่าถ้าคุณเขียนรหัสของคุณสำหรับคนต่อไปที่อ่านให้ไปเพื่อความชัดเจนของ "x / 2"

อย่างไรก็ตามหากความเร็วเป็นเป้าหมายของคุณให้ลองทั้งผลและเวลาผลลัพธ์ไม่กี่เดือนที่ผ่านมาฉันทำงานกับรูทบิตแมปแบบ Convolution ซึ่งเกี่ยวข้องกับการก้าวผ่านอาร์เรย์จำนวนเต็มและหารแต่ละองค์ประกอบด้วย 2 ฉันได้ทำทุกอย่างเพื่อปรับให้เหมาะสมรวมถึงเคล็ดลับเก่า ๆ ของการแทน "x >> 1" สำหรับ "x / 2"

เมื่อฉันตั้งเวลาทั้งสองวิธีจริง ๆ ฉันค้นพบกับความประหลาดใจของฉันว่าx / 2 เร็วกว่า x >> 1

สิ่งนี้ใช้ Microsoft VS2008 C ++ โดยเปิดการปรับค่าเริ่มต้นไว้


4

ในแง่ของประสิทธิภาพ การดำเนินการกะของ CPU นั้นเร็วกว่าการแบ่ง op-code อย่างมีนัยสำคัญ ดังนั้นการหารด้วยสองหรือคูณด้วย 2 เป็นต้นทั้งหมดได้รับประโยชน์จากการดำเนินการกะ

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


3

X / Y เป็นตัวดำเนินการที่ถูกต้อง ... และ ">>" ตัวดำเนินการขยับ ... หากเราต้องการให้หารสองจำนวนเต็มเราสามารถใช้ตัวดำเนินการเงินปันผล (/) shift operator ใช้เพื่อเลื่อนบิต

x = x / 2; x / = 2; เราสามารถใช้แบบนี้ ..


0

ในขณะที่ x >> 1 เร็วกว่า x / 2 การใช้ >> ที่เหมาะสมเมื่อจัดการกับค่าลบนั้นซับซ้อนกว่าเล็กน้อย มันต้องการสิ่งที่คล้ายกับต่อไปนี้:

// Extension Method
public static class Global {
    public static int ShiftDivBy2(this int x) {
        return (x < 0 ? x + 1 : x) >> 1;
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.