x + = เร็วกว่า x = x + a หรือไม่?


84

ฉันกำลังอ่าน "The C ++ Programming Language" ของ Stroustrup ซึ่งเขาบอกว่าจากสองวิธีในการเพิ่มบางสิ่งลงในตัวแปร

x = x + a;

และ

x += a;

เขาชอบ+=เพราะมันน่าจะดีกว่า ฉันคิดว่าเขาหมายความว่ามันทำงานได้เร็วขึ้นด้วย
แต่มันจริงเหรอ? ถ้ามันขึ้นอยู่กับคอมไพเลอร์และสิ่งอื่น ๆ ฉันจะตรวจสอบได้อย่างไร?


45
"ภาษาการเขียนโปรแกรม C ++" ได้รับการตีพิมพ์ครั้งแรกในปี พ.ศ. 2528 เวอร์ชันล่าสุดได้รับการตีพิมพ์ในปี พ.ศ. 2540 และฉบับพิเศษ พ.ศ. 2540 ได้รับการตีพิมพ์ในปี พ.ศ. 2543 ด้วยเหตุนี้บางส่วนจึงล้าสมัยอย่างมาก
JoeG

5
ทั้งสองบรรทัดอาจทำบางสิ่งที่แตกต่างกันอย่างสิ้นเชิง คุณต้องเจาะจงมากขึ้น
Kerrek SB


26
คอมไพเลอร์สมัยใหม่ฉลาดพอที่คำถามเหล่านี้จะถือว่า 'ล้าสมัย'
gd1

2
เปิดอีกครั้งเนื่องจากคำถามที่ซ้ำกันถามเกี่ยวกับ C ไม่ใช่ C ++
Kev

คำตอบ:


212

ใด ๆ ที่คุ้มค่าคอมไพเลอร์ของเกลือจะสร้างตรงลำดับเครื่องภาษาเดียวกันสำหรับโครงสร้างทั้งสำหรับการใด ๆ ในตัวชนิด ( int, floatฯลฯ ) ตราบใดที่คำสั่งมันเป็นง่ายๆเป็นและการเพิ่มประสิทธิภาพถูกเปิดใช้งานx = x + a; (โดยเฉพาะอย่างยิ่ง GCC -O0ซึ่งเป็นโหมดเริ่มต้นจะทำการป้องกันการเพิ่มประสิทธิภาพเช่นการแทรกร้านค้าที่ไม่จำเป็นทั้งหมดลงในหน่วยความจำเพื่อให้แน่ใจว่าผู้ดีบั๊กสามารถค้นหาค่าตัวแปรได้ตลอดเวลา)

หากคำสั่งมีความซับซ้อนมากขึ้นอาจแตกต่างกัน สมมติว่าfเป็นฟังก์ชันที่ส่งกลับตัวชี้แล้ว

*f() += a;

โทรfเพียงครั้งเดียวในขณะที่

*f() = *f() + a;

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

และเนื่องจากเรากำลังพูดถึง C ++ ที่นี่สถานการณ์จึงแตกต่างกันอย่างสิ้นเชิงสำหรับประเภทคลาสที่โอเวอร์โหลดoperator+และoperator+=. หากxเป็นประเภทดังกล่าว - ก่อนการปรับให้เหมาะสม - x += aแปลเป็น

x.operator+=(a);

ในขณะที่x = x + aแปลเป็น

auto TEMP(x.operator+(a));
x.operator=(TEMP);

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


14
นอกจากนี้ยังมีด้านอื่น - การอ่าน c ++ สำนวนสำหรับการเพิ่มexprที่จะvarเป็นvar+=exprและการเขียนมันด้วยวิธีอื่น ๆ จะสร้างความสับสนให้ผู้อ่าน
Tadeusz Kopec

21
หากคุณพบว่าตัวเองกำลังเขียน*f() = *f() + a;คุณอาจต้องการพิจารณาสิ่งที่คุณพยายามจะบรรลุอย่างจริงจัง ...
อดัมเดวิส

3
และถ้า var = var + expr ทำให้คุณสับสน แต่ var + = expr ไม่แสดงว่าคุณเป็นวิศวกรซอฟต์แวร์ที่แปลกที่สุดที่ฉันเคยพบ สามารถอ่านได้; แค่ตรวจสอบให้แน่ใจว่าคุณมีความสม่ำเสมอ (และเราทุกคนใช้ op = ดังนั้นมันจึง moot อยู่ดี = P)
WhozCraig

7
@PiotrDobrogost: ตอบคำถามผิดอะไร ไม่ว่าในกรณีใดควรเป็นผู้ถามที่ตรวจสอบรายการที่ซ้ำกัน
Gorpik

4
@PiotrDobrogost ดูเหมือนว่าฉันชอบคุณตัวเล็ก ๆ ... อิจฉา ... ถ้าคุณต้องการไปรอบ ๆ เพื่อค้นหารายการที่ซ้ำกันไปได้เลย สำหรับคนหนึ่งฉันชอบที่จะตอบคำถามมากกว่าที่จะมองหา dupes (เว้นแต่จะเป็นคำถามที่ฉันจำได้ว่าเคยเห็นมาก่อนโดยเฉพาะ) บางครั้งอาจเร็วกว่าและ ergo ช่วยให้ผู้ถามคำถามเร็วขึ้น นอกจากนี้โปรดทราบว่านี่ไม่ใช่การวนซ้ำ 1เป็นค่าคงที่aสามารถระเหยได้ประเภทที่ผู้ใช้กำหนดเองหรืออะไรก็ได้ แตกต่างกันอย่างสิ้นเชิง. อันที่จริงฉันไม่เข้าใจว่ามันปิดได้ยังไง
Luchian Grigore

56

คุณสามารถตรวจสอบได้โดยดูจากการแยกชิ้นส่วนซึ่งจะเหมือนกัน

สำหรับประเภทพื้นฐานทั้งสองอย่างรวดเร็วเท่ากัน

นี่คือผลลัพธ์ที่สร้างขึ้นโดยการสร้างดีบัก (เช่นไม่มีการปรับให้เหมาะสม):

    a += x;
010813BC  mov         eax,dword ptr [a]  
010813BF  add         eax,dword ptr [x]  
010813C2  mov         dword ptr [a],eax  
    a = a + x;
010813C5  mov         eax,dword ptr [a]  
010813C8  add         eax,dword ptr [x]  
010813CB  mov         dword ptr [a],eax  

สำหรับประเภทที่ผู้ใช้กำหนดซึ่งคุณสามารถโอเวอร์โหลดได้operator +และoperator +=ขึ้นอยู่กับการใช้งานที่เกี่ยวข้อง


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

ผู้ใช้กำหนดประเภทอย่างไร คอมไพเลอร์ที่ดีควรสร้างแอสเซมบลีที่เทียบเท่ากัน แต่ไม่มีการรับประกันดังกล่าว
mfontanini

1
@LuchianGrigore Nope ถ้าaเป็นที่ 1 และxเป็นคอมไพเลอร์สามารถสร้างvolatile inc DWORD PTR [x]เรื่องนี้ช้า
James

1
@Chiffa ไม่ได้ขึ้นอยู่กับคอมไพเลอร์ แต่ขึ้นอยู่กับผู้พัฒนา คุณสามารถนำoperator +ไปใช้โดยไม่ต้องทำอะไรเลยและoperator +=คำนวณจำนวนเฉพาะที่ 100000 แล้วส่งกลับ แน่นอนว่านั่นเป็นเรื่องโง่ที่ต้องทำ แต่ก็เป็นไปได้
Luchian Grigore

3
@ เจมส์: ถ้าโปรแกรมของคุณเหมาะสมกับความแตกต่างของประสิทธิภาพระหว่าง++xและtemp = x + 1; x = temp;ส่วนใหญ่น่าจะเขียนในแอสเซมบลีมากกว่า c ++ ...
EmirCalabuch

11

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


8

มันขึ้นอยู่กับประเภทของ x และ a และการใช้งานของ + สำหรับ

   T x, a;
   ....
   x = x + a;

คอมไพเลอร์ต้องสร้าง T ชั่วคราวเพื่อให้มีค่า x + ในขณะที่มันประเมินค่าซึ่งจะสามารถกำหนดให้กับ x ได้ (ไม่สามารถใช้ x หรือ a เป็นพื้นที่ทำงานในระหว่างการดำเนินการนี้)

สำหรับ x + = a ไม่จำเป็นต้องชั่วคราว

สำหรับประเภทเล็กน้อยไม่มีความแตกต่าง


8

ความแตกต่างระหว่างx = x + aและx += aคือปริมาณงานที่เครื่องต้องดำเนินการ - คอมไพเลอร์บางตัวอาจ (และมักจะทำ) เพิ่มประสิทธิภาพให้ห่างออกไป แต่โดยปกติแล้วหากเราเพิกเฉยต่อการปรับให้เหมาะสมไประยะหนึ่งสิ่งที่เกิดขึ้นคือในข้อมูลโค้ดในอดีต เครื่องต้องค้นหาค่าxสองครั้งในขณะที่ค่าหลังการค้นหานี้จะต้องเกิดขึ้นเพียงครั้งเดียว

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

PS: คำตอบแรกใน Stack Overflow!


6

ตามที่คุณติดป้ายกำกับ C ++ นี้ไม่มีทางทราบได้จากคำสั่งทั้งสองที่คุณโพสต์ คุณต้องรู้ว่า 'x' คืออะไร (คล้ายกับคำตอบ '42') ถ้าxเป็น POD ก็จะไม่สร้างความแตกต่างมากนัก อย่างไรก็ตามหากxเป็นคลาสอาจมีโอเวอร์โหลดสำหรับoperator +และoperator +=วิธีการซึ่งอาจมีพฤติกรรมที่แตกต่างกันซึ่งนำไปสู่เวลาการดำเนินการที่แตกต่างกันมาก


6

ถ้าคุณบอกว่า+=คุณทำให้ชีวิตง่ายขึ้นมากสำหรับคอมไพเลอร์ เพื่อให้คอมไพลเลอร์รับรู้ว่าx = x+aเหมือนกับx += aคอมไพเลอร์ต้อง

  • วิเคราะห์ด้านซ้ายมือ ( x) เพื่อให้แน่ใจว่าไม่มีผลข้างเคียงและอ้างอิงถึงค่า l เดียวกันเสมอ ตัวอย่างเช่นอาจเป็นได้z[i]และต้องแน่ใจว่าทั้งสองอย่างzและiไม่เปลี่ยนแปลง

  • วิเคราะห์ด้านขวามือ ( x+a) และตรวจสอบให้แน่ใจว่าเป็นการสรุปและด้านซ้ายมือเกิดขึ้นครั้งเดียวทางด้านขวามือแม้ว่าจะสามารถเปลี่ยนรูปz[i] = a + *(z+2*0+i)ได้ก็ตาม

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


5

ตัวอย่างที่เป็นรูปธรรมลองนึกภาพประเภทจำนวนเชิงซ้อนง่ายๆ:

struct complex {
    double x, y;
    complex(double _x, double _y) : x(_x), y(_y) { }
    complex& operator +=(const complex& b) {
        x += b.x;
        y += b.y;
        return *this;
    }
    complex operator +(const complex& b) {
        complex result(x+b.x, y+b.y);
        return result;
    }
    /* trivial assignment operator */
}

สำหรับกรณี a = a + b จะต้องสร้างตัวแปรชั่วคราวเพิ่มเติมจากนั้นจึงคัดลอก


นี่เป็นตัวอย่างที่ดีมากแสดงให้เห็นว่าตัวดำเนินการ 2 ตัวใช้งานอย่างไร
Grijesh Chauhan

5

คุณกำลังถามคำถามผิด

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

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

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


จริงอยู่ แต่มันหมายความว่าเป็นคำถามระดับต่ำไม่ใช่ภาพรวม "เมื่อไหร่ที่ฉันควรพิจารณาความแตกต่างเช่นนี้"
Chiffa

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

4

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

mov $[y],$ACC

iadd $ACC, $[i] ; i += y. WHICH MIGHT ALSO STORE IT INTO "i"

ในขณะที่i = i + yอาจได้รับการแปลเป็น (ไม่มีการปรับให้เหมาะสม):

mov $[i],$ACC

mov $[y],$B 

iadd $ACC,$B

mov $B,[i]


ที่กล่าวว่าiควรนึกถึงภาวะแทรกซ้อนอื่น ๆ เช่น if เป็นฟังก์ชัน return pointer เป็นต้น คอมไพเลอร์ระดับการผลิตส่วนใหญ่รวมถึง GCC สร้างรหัสเดียวกันสำหรับทั้งสองคำสั่ง (หากเป็นจำนวนเต็ม)


2

ไม่ทั้งสองวิธีได้รับการจัดการเหมือนกัน


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