มีความแตกต่างของประสิทธิภาพระหว่าง i ++ และ ++ i ใน C หรือไม่?


471

มีความแตกต่างของประสิทธิภาพระหว่างi++และ++iหากไม่ได้ใช้ค่าผลลัพธ์หรือไม่


1
ที่เกี่ยวข้องคำถามเดียวกัน แต่ชัดเจนสำหรับ C ++ คือstackoverflow.com/questions/24901/ …
FluxLemur

คำตอบ:


396

บทสรุปผู้บริหาร: ไม่

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

เราสามารถแสดงให้เห็นถึงนี้โดยดูที่รหัสสำหรับฟังก์ชั่นนี้ทั้งที่มีและ++ii++

$ cat i++.c
extern void g(int i);
void f()
{
    int i;

    for (i = 0; i < 100; i++)
        g(i);

}

ไฟล์เหมือนกันยกเว้น++iและi++:

$ diff i++.c ++i.c
6c6
<     for (i = 0; i < 100; i++)
---
>     for (i = 0; i < 100; ++i)

เราจะรวบรวมพวกมันและรับแอสเซมเบลอร์ที่สร้างขึ้น:

$ gcc -c i++.c ++i.c
$ gcc -S i++.c ++i.c

และเราจะเห็นได้ว่าทั้งไฟล์ออบเจ็กต์และแอสเซมเบลอร์ที่สร้างขึ้นนั้นเหมือนกัน

$ md5 i++.s ++i.s
MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e
MD5 (++i.s) = 90f620dda862cd0205cd5db1f2c8c06e

$ md5 *.o
MD5 (++i.o) = dd3ef1408d3a9e4287facccec53f7d22
MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22

9
ฉันรู้ว่าคำถามนี้เกี่ยวกับ C แต่ฉันสนใจที่จะทราบว่าเบราว์เซอร์สามารถเพิ่มประสิทธิภาพให้กับจาวาสคริปต์ได้หรือไม่
TM

69
ดังนั้น "ไม่" เป็นจริงสำหรับคอมไพเลอร์เดียวที่คุณทดสอบด้วย
Andreas

173
@ Andreas: เป็นคำถามที่ดี ฉันเคยเป็นนักเขียนคอมไพเลอร์และมีโอกาสทดสอบรหัสนี้กับซีพียูระบบปฏิบัติการและคอมไพเลอร์มากมาย คอมไพเลอร์เดียวที่ฉันพบว่าไม่เพิ่มประสิทธิภาพเคส i ++ (อันที่จริงแล้วคอมไพเลอร์ที่นำสิ่งนี้มาให้ฉันสนใจอย่างมืออาชีพ) คือคอมไพเลอร์ Software Toolworks C80 โดย Walt Bilofsky คอมไพเลอร์นั้นสำหรับระบบ Intel 8080 CP / M มันปลอดภัยที่จะกล่าวว่าคอมไพเลอร์ใด ๆ ที่ไม่รวมการเพิ่มประสิทธิภาพนี้ไม่ได้มีไว้สำหรับการใช้งานทั่วไป
Mark Harrison

22
แม้ว่าจะแตกต่างประสิทธิภาพเป็นเล็กน้อยและเพิ่มประสิทธิภาพออกมาในหลายกรณี - จะทราบกรุณาว่ามันยังคงปฏิบัติที่ดีที่จะใช้แทน++i i++ไม่มีเหตุผลอะไรที่จะทำอย่างนั้นและหากซอฟต์แวร์ของคุณผ่าน toolchain ที่ไม่ได้ปรับให้เหมาะสมซอฟต์แวร์ของคุณจะมีประสิทธิภาพมากขึ้น เมื่อพิจารณาว่ามันเป็นเรื่องง่ายที่จะพิมพ์++iเหมือนกับการพิมพ์มันi++ไม่มีข้อแก้ตัวที่จะไม่ใช้++iในตอนแรก
ขาวดำ

7
@monokrome ในฐานะนักพัฒนาสามารถให้การดำเนินการของตัวเองสำหรับผู้ประกอบการส่วนนำหน้าและ postfix ในหลายภาษานี้อาจไม่ได้เป็นทางออกที่ยอมรับได้สำหรับคอมไพเลอร์ที่จะทำโดยไม่ต้องเปรียบเทียบฟังก์ชั่นเหล่านั้นเป็นครั้งแรก
pickypg

112

จากประสิทธิภาพกับเจตนาโดย Andrew Koenig:

อย่างแรกมันยังไม่ชัดเจนว่า++iมีประสิทธิภาพมากกว่าi++อย่างน้อยที่ตัวแปรจำนวนเต็มเกี่ยวข้อง

และ:

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

++iดังนั้นถ้าค่าที่เกิดขึ้นไม่ได้ใช้ผมจะใช้ แต่ไม่ใช่เพราะมันมีประสิทธิภาพมากกว่า: เพราะมันบอกความตั้งใจของฉันอย่างถูกต้อง


5
อย่าลืมว่าโอเปอเรเตอร์ unary อื่น ๆ เป็นส่วนนำหน้าด้วย ฉันคิดว่า ++ ฉันเป็นวิธี "ความหมาย" ในการใช้โอเปอเรเตอร์ unary ในขณะที่ i ++ อยู่ใกล้เพื่อให้เหมาะกับความต้องการเฉพาะ (การประเมินก่อนเพิ่มเติม)
TM

9
หากไม่ได้ใช้ค่าผลลัพธ์จะไม่มีความแตกต่างในความหมาย: นั่นคือไม่มีพื้นฐานสำหรับการเลือกโครงสร้างอย่างใดอย่างหนึ่ง
Eamon Nerbonne

3
ในฐานะผู้อ่านฉันเห็นความแตกต่าง ดังนั้นในฐานะนักเขียนฉันจะแสดงความตั้งใจของฉันโดยเลือกหนึ่งอย่าง มันเป็นเพียงแค่นิสัยฉันมีการพยายามที่จะสื่อสารกับเพื่อนโปรแกรมเมอร์ของฉันและเพื่อนร่วมทีมของรหัสผ่าน :)
Sébastien RoccaSerra

11
ปฏิบัติตามคำแนะนำของนิกฉันใช้รหัสi++ในลักษณะเดียวกับที่ฉันใช้รหัสi += nหรือi = i + nกล่าวคือในวัตถุกริยาเป้าหมาย แบบฟอร์มโดยมีตัวถูกดำเนินการเป้าหมายทางด้านซ้ายของโอเปอเรเตอร์คำกริยา ในกรณีของไม่มีวัตถุที่ถูกต้องแต่กฎยังคงใช้รักษาเป้าหมายทางด้านซ้ายของผู้ประกอบการกริยา i++
David R Tribble

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

46

คำตอบที่ดีกว่าคือ++iบางครั้งจะเร็วขึ้น แต่ไม่เคยช้าลง

ทุกคนน่าจะสมมติว่าเป็นปกติในตัวชนิดเช่นi intในกรณีนี้จะไม่มีความแตกต่างที่วัดได้

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

Foo Foo::operator++()
{
  Foo oldFoo = *this; // copy existing value - could be slow
  // yadda yadda, do increment
  return oldFoo;
}

ข้อแตกต่างก็คือเมื่อ++iคุณมีตัวเลือกในการคืนค่าการอ้างอิงแทนค่า อีกครั้งทั้งนี้ขึ้นอยู่กับสิ่งที่เกี่ยวข้องในการทำสำเนาวัตถุของคุณนี้อาจช้าลง

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


36
คำถามระบุอย่างชัดเจน C ไม่มีการอ้างอิงถึง C ++
Dan Cristoloveanu

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

-1 แม้ว่าสิ่งนี้จะเป็นคำแนะนำที่ดีสำหรับโปรแกรมเมอร์ C ++ แต่ก็ไม่ได้ตอบคำถามที่ติดแท็ก C อย่างน้อยที่สุด ใน C มันไม่สร้างความแตกต่างอย่างแน่นอนไม่ว่าคุณจะใช้คำนำหน้าหรือ postfix
Lundin

@Pacerier คำถามถูกติดแท็ก C และ C เท่านั้น ทำไมคุณคิดว่าพวกเขาไม่สนใจสิ่งนั้น เมื่อพิจารณาว่า SO ทำงานอย่างไรมันจะไม่ฉลาดกว่าที่จะคิดว่าพวกเขาสนใจเฉพาะ C แทนที่จะเป็น Java, C #, COBOL หรือภาษานอกหัวข้ออื่น ๆ
Lundin

18

คำตอบสั้น ๆ :

ไม่เคยมีความแตกต่างระหว่างi++และ++iในแง่ของความเร็ว คอมไพเลอร์ที่ดีไม่ควรสร้างรหัสที่แตกต่างกันในสองกรณี

คำตอบยาว:

สิ่งที่คำตอบอื่น ๆ ไม่สามารถพูดถึงได้คือความแตกต่างระหว่าง++iเมื่อเทียบกับi++เพียงทำให้รู้สึกได้ในการแสดงออกที่พบ

ในกรณีของfor(i=0; i<n; i++)การi++เป็นคนเดียวในการแสดงออกของตัวเอง: มีจุดลำดับก่อนหน้าi++และมีหนึ่งหลังจากนั้น ดังนั้นรหัสเครื่องเท่านั้นที่สร้างคือ "เพิ่มขึ้นiโดย1" และมันเป็นเรื่องที่ดีที่กำหนดวิธีการนี้จะติดใจในความสัมพันธ์กับส่วนที่เหลือของโปรแกรม ดังนั้นถ้าคุณจะเปลี่ยนคำนำหน้า++ก็จะไม่ว่าในน้อยคุณจะยังคงเป็นเพียงได้รับรหัสเครื่อง "เพิ่มขึ้นiโดย1"

ความแตกต่างระหว่าง++iและi++เรื่องเฉพาะในการแสดงออกเช่นเมื่อเทียบกับarray[i++] = x; array[++i] = x;บางคนอาจโต้แย้งและบอกว่า postfix จะช้าลงในการดำเนินการดังกล่าวเนื่องจากการลงทะเบียนที่iมีอยู่จะต้องโหลดใหม่ในภายหลัง แต่โปรดทราบว่าคอมไพเลอร์มีอิสระในการสั่งซื้อคำสั่งของคุณในทุกทางที่มันพอใจตราบใดที่มันไม่ "ทำลายพฤติกรรมของเครื่องนามธรรม" ตามที่มาตรฐาน C เรียกมัน

ดังนั้นในขณะที่คุณอาจสันนิษฐานarray[i++] = x;ได้ว่าแปลรหัสเครื่องเป็น:

  • มูลค่าร้านค้าของiin register A
  • ที่อยู่ร้านค้าของอาร์เรย์ในการลงทะเบียน B.
  • เพิ่ม A และ B จัดเก็บผลลัพธ์ใน A
  • ที่อยู่ใหม่นี้แสดงโดย A ให้เก็บค่าของ x
  • ค่าร้านค้าของiใน register A // inefficient เนื่องจากคำสั่งพิเศษที่นี่เราทำสิ่งนี้ครั้งเดียวแล้ว
  • การลงทะเบียนที่เพิ่มขึ้น A.
  • iร้านค้าลงทะเบียนใน

คอมไพเลอร์อาจสร้างโค้ดได้อย่างมีประสิทธิภาพมากขึ้นเช่น:

  • มูลค่าร้านค้าของiin register A
  • ที่อยู่ร้านค้าของอาร์เรย์ในการลงทะเบียน B.
  • เพิ่ม A และ B จัดเก็บผลลัพธ์เป็น B
  • การลงทะเบียนที่เพิ่มขึ้น A.
  • iร้านค้าลงทะเบียนใน
  • ... // รหัสที่เหลือ

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

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

"คำนำหน้า++เร็วกว่าเสมอ" เป็นหนึ่งในความเชื่อที่ผิดที่เป็นเรื่องธรรมดาในหมู่โปรแกรมเมอร์ซี


3
ไม่มีใครพูดว่า "คำนำหน้า ++ เร็วกว่าเสมอ" นั่นเป็นสิ่งที่ผิด สิ่งที่พวกเขาพูดคือ "Postfix ++ ไม่เคยเร็ว"
Pacerier

2
@Pierier ฉันไม่ได้อ้างถึงบุคคลใดบุคคลหนึ่ง แต่เป็นเพียงความเชื่อที่ไม่ถูกต้อง
Lundin

@rbaleksandar C ++! = C.
Lundin

@rbaleksandar คำถามและคำตอบทั้งคู่คุยกันเรื่อง C ++ ซึ่งจะแตกต่างกว่า C เนื่องจากมีการบรรทุกเกินพิกัดผู้ประกอบการและคัดลอกก่อสร้าง ฯลฯ
Lundin

3
@rbaleksandar c ++ == c && ++ c! = c
sadljkfhalskdjfh

17

การใบจากสกอตต์เมเยอร์สที่มีประสิทธิภาพมากกว่า C ++ รายการที่ 6: การแยกแยะความแตกต่างระหว่างคำนำหน้าและ postfix รูปแบบของการเพิ่มขึ้นและลดลงการดำเนินงาน

รุ่นคำนำหน้าเป็นที่ต้องการมากกว่า postfix ในเรื่องที่เกี่ยวกับวัตถุโดยเฉพาะในส่วนที่เกี่ยวกับตัววนซ้ำ

เหตุผลนี้ถ้าคุณดูรูปแบบการโทรของผู้ประกอบการ

// Prefix
Integer& Integer::operator++()
{
    *this += 1;
    return *this;
}

// Postfix
const Integer Integer::operator++(int)
{
    Integer oldValue = *this;
    ++(*this);
    return oldValue;
}

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

นี่คือเหตุผลที่เมื่อคุณเห็นตัวอย่างโดยใช้ตัววนซ้ำพวกมันจะใช้รุ่นนำหน้าเสมอ

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


4
ฉันคิดว่าคำถามของเขาถูกส่งตรงไปที่ C แต่สำหรับ C ++ คุณพูดถูกและคน C ควรนำมาใช้เพราะพวกเขาสามารถใช้กับ C ++ ได้เช่นกัน บ่อยครั้งที่ฉันเห็นว่าโปรแกรมเมอร์ C ใช้ไวยากรณ์ postfix ;-)
Anders Rune Jensen

-1 แม้ว่าสิ่งนี้จะเป็นคำแนะนำที่ดีสำหรับโปรแกรมเมอร์ C ++ แต่ก็ไม่ได้ตอบคำถามที่ติดแท็ก C อย่างน้อยที่สุด ใน C มันไม่สร้างความแตกต่างอย่างแน่นอนไม่ว่าคุณจะใช้คำนำหน้าหรือ postfix
Lundin

1
@Lundin คำนำหน้าและ postifx ทำเรื่องใน C ตามคำตอบโดยAndreas คุณไม่สามารถสรุปได้ว่าคอมไพเลอร์จะปรับให้เหมาะสมและมันก็เป็นแนวปฏิบัติที่ดีสำหรับภาษาใด ๆ สำหรับAlwas ที่ต้องการ ++ i มากกว่า i ++
JProgrammer

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

16

นี่คือข้อสังเกตเพิ่มเติมหากคุณกังวลเกี่ยวกับการเพิ่มประสิทธิภาพขนาดเล็ก การวนซ้ำที่ลดลงสามารถ 'มีประสิทธิภาพมากกว่าลูปที่เพิ่มขึ้น (ขึ้นอยู่กับสถาปัตยกรรมชุดคำสั่งเช่น ARM) ที่ได้รับ:

for (i = 0; i < 100; i++)

ในแต่ละวงคุณจะมีหนึ่งคำสั่งสำหรับ:

  1. เพิ่มไป 1i
  2. เปรียบเทียบว่าiมีค่าน้อยกว่า100หรือไม่
  3. สาขาเงื่อนไขถ้าเป็นน้อยกว่าi100

ในขณะที่วนซ้ำลดลง:

for (i = 100; i != 0; i--)

การวนซ้ำจะมีคำแนะนำสำหรับแต่ละ:

  1. ลดiลงตั้งค่าสถานะการลงทะเบียน CPU
  2. สาขาที่มีเงื่อนไขขึ้นอยู่กับสถานะการลงทะเบียน CPU ( Z==0)

แน่นอนว่ามันใช้งานได้ก็ต่อเมื่อลดลงเหลือศูนย์!

จำได้จากคู่มือนักพัฒนาระบบ ARM


สิ่งที่ดี. แต่สิ่งนี้จะไม่สร้างความนิยมแคชน้อยกว่าหรือไม่
AndreasT

มีเคล็ดลับแปลก ๆ จากหนังสือการเพิ่มประสิทธิภาพรหัสรวมข้อดีของสาขาทดสอบศูนย์กับที่อยู่เพิ่มขึ้น นี่คือตัวอย่างในภาษาระดับสูง (อาจไร้ประโยชน์เนื่องจากคอมไพเลอร์จำนวนมากฉลาดพอที่จะแทนที่ด้วยโค้ดลูปที่มีประสิทธิภาพน้อยกว่า แต่พบได้บ่อยกว่า): int a [N]; สำหรับ (i = -N; i; ++ i) a [N + i] + = 123;
noop

@noop คุณสามารถอธิบายอย่างละเอียดเกี่ยวกับหนังสือการเพิ่มประสิทธิภาพรหัสที่คุณอ้างถึงได้อย่างไร ฉันพยายามหาของดี
mezamorphic

7
คำตอบนี้ไม่ใช่คำตอบสำหรับคำถามเลย
ขาวดำ

@mezamorphic สำหรับแหล่ง x86 ของฉันคือ: คู่มือการเพิ่มประสิทธิภาพของ Intel, Agner Fog, Paul Hsieh, Michael Abrash
noop

11

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

ใช้วิธีการที่เหมาะสมที่สุดสำหรับมนุษย์ที่อ่านรหัส


3
ฉันเชื่อว่ามันผิดที่จะชอบการปรับปรุงการอ่านที่คลุมเครือเพื่อเพิ่มประสิทธิภาพที่แท้จริงและความชัดเจนโดยรวมของเจตนา
noop

4
คำว่า "เวลาในการอ่านของโปรแกรมเมอร์" นั้นคล้าย ๆ กับ "ความชัดเจนของเจตนา" "การเพิ่มประสิทธิภาพตามความเป็นจริง" มักจะวัดไม่ได้อยู่ใกล้พอที่จะเป็นศูนย์เพื่อเรียกพวกมันว่าเป็นศูนย์ ในกรณีของ OP ยกเว้นว่ามีการจัดทำรหัสเพื่อหาว่า ++ i เป็นคอขวดคำถามที่เร็วกว่านั้นคือการเสียเวลาและโปรแกรมเมอร์คิดว่าหน่วย
Andy Lester

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

คุณกำลังพูดในสิ่งที่ฉันพูด การแสดงเจตนาและปรับปรุงการอ่านนั้นสำคัญกว่าการกังวลเกี่ยวกับ "ประสิทธิภาพ"
Andy Lester

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

11

ก่อนอื่น: ความแตกต่างระหว่างi++และไม่++iสามารถปฏิเสธได้ใน C.


ต้องการรายละเอียด

1. ปัญหา C ++ ที่รู้จักกันดี: ++iเร็วขึ้น

ใน C ++ ++iจะมีประสิทธิภาพมากกว่า iff iเป็นวัตถุชนิดหนึ่งที่มีตัวดำเนินการเพิ่มที่มากเกินไป

ทำไม?
ใน++iวัตถุจะเพิ่มขึ้นเป็นครั้งแรกและในภายหลังสามารถส่งผ่านเป็นการอ้างอิง const ไปยังฟังก์ชั่นอื่น ๆ นี้เป็นไปไม่ได้ถ้าการแสดงออกเป็นfoo(i++)เพราะตอนนี้เพิ่มขึ้นจะต้องทำก่อนที่จะfoo()ถูกเรียกว่า foo()แต่ความต้องการค่าเก่าที่จะถูกส่งผ่านไป ดังนั้นคอมไพเลอร์ถูกบังคับให้ทำสำเนาiก่อนที่จะดำเนินการตัวดำเนินการที่เพิ่มขึ้นในต้นฉบับ การเรียก constructor / destructor เพิ่มเติมเป็นส่วนที่ไม่ดี

ตามที่ระบุไว้ข้างต้นสิ่งนี้ไม่สามารถใช้ได้กับประเภทพื้นฐาน

2. ความจริงที่รู้จักกันน้อย: i++ อาจเร็วกว่า

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

วิธีการอาจi++จะเร็วขึ้น?
จุดคือการพึ่งพาข้อมูล หากค่าจำเป็นต้องโหลดจากหน่วยความจำการดำเนินการที่ตามมาสองครั้งจะต้องดำเนินการกับมันเพิ่มขึ้นและใช้มัน ด้วย++iการเพิ่มจะต้องทำก่อนที่จะสามารถใช้ค่า ด้วยi++การใช้ไม่ขึ้นอยู่กับการเพิ่มขึ้นและ CPU อาจดำเนินการใช้งานควบคู่ไปกับการดำเนินการเพิ่มขึ้น ความแตกต่างคือรอบ CPU อย่างน้อยหนึ่งรอบดังนั้นจึงเป็นข้อเสียเปรียบจริง ๆ แต่มีอยู่ และมันเป็นวิธีอื่น ๆ ที่หลายคนคาดหวัง


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

1
@Shahbaz นั่นเป็นความจริงอย่างสมบูรณ์แบบ แต่ไม่ใช่ประเด็น 1) แม้ว่าซีแมนทิกส์จะแตกต่างกันทั้งสองi++และ++iสามารถใช้สลับกันได้ในเกือบทุกสถานการณ์ที่เป็นไปได้โดยการปรับค่าคงที่ของลูปโดยหนึ่งดังนั้นพวกเขาจึงใกล้เคียงกันในสิ่งที่พวกเขาทำสำหรับโปรแกรมเมอร์ 2) แม้ว่าทั้งคู่จะรวบรวมคำสั่งเดียวกันการดำเนินการของพวกเขาแตกต่างกันสำหรับ CPU ในกรณีของi++CPU สามารถคำนวณการเพิ่มขึ้นแบบขนานกับคำสั่งอื่น ๆ ที่ใช้ค่าเดียวกัน (CPU ทำสิ่งนี้จริง ๆ !) ในขณะ++iที่ CPU ต้องกำหนดเวลาคำสั่งอื่นหลังจากการเพิ่ม
cmaster - คืนสถานะโมนิกา

4
@Shahbaz เป็นตัวอย่าง: if(++foo == 7) bar();และif(foo++ == 6) bar();เทียบเท่ากับหน้าที่ อย่างไรก็ตามวินาทีนั้นอาจเร็วกว่าหนึ่งรอบเนื่องจากการเปรียบเทียบและการเพิ่มสามารถคำนวณได้ในแบบคู่ขนานโดยซีพียู ไม่ว่ารอบเดียวนี้สำคัญมาก แต่มีความแตกต่างอยู่
cmaster - คืนสถานะโมนิกา

1
จุดดี. ค่าคงที่แสดงมาก ( <เช่นตัวอย่าง vs <=) ที่++มักใช้ดังนั้นการแปลงระหว่างแม้ว่ามักจะเป็นไปได้ง่าย
Shahbaz

1
ฉันชอบจุดที่ 2 แต่นั่นก็ใช้ได้เฉพาะถ้าใช้ค่านี้เท่านั้น คำถามบอกว่า "ถ้าไม่ได้ใช้ค่าผลลัพธ์" ดังนั้นมันอาจสร้างความสับสน
jinawee

7

@ Mark ถึงแม้ว่าคอมไพเลอร์จะได้รับอนุญาตให้ปรับแต่งตัวแปรชั่วคราว (ตามสแต็ก) ของตัวแปรและ gcc (ในเวอร์ชันล่าสุด) กำลังทำเช่นนั้นไม่ได้หมายความว่าคอมไพเลอร์ทั้งหมดจะทำเช่นนั้นเสมอ

ฉันเพิ่งทดสอบกับคอมไพเลอร์ที่เราใช้ในโครงการปัจจุบันของเราและ 3 จาก 4 ไม่ปรับให้เหมาะสม

อย่าสันนิษฐานว่าคอมไพเลอร์ทำให้ถูกต้องโดยเฉพาะอย่างยิ่งหากโค้ดที่อาจเร็วกว่า แต่ไม่ช้ากว่าโค้ดที่อ่านง่าย

หากคุณไม่มีการนำโอเปอเรเตอร์หนึ่งในโค้ดของคุณไปใช้งี่เง่าจริงๆ:

ชอบมากกว่า ++ ฉันมากกว่า i ++


แค่อยากรู้อยากเห็น ... ทำไมคุณถึงใช้คอมไพเลอร์ C 4 ตัวในโครงการเดียว หรือในหนึ่งทีมหรือหนึ่ง บริษัท สำหรับเรื่องนั้น
Lawrence Dol

3
เมื่อสร้างเกมสำหรับคอนโซลทุกแพลตฟอร์มจะมีคอมไพเลอร์ / Toolchain ของตัวเอง ในโลกที่สมบูรณ์แบบเราสามารถใช้ gcc / clang / llvm สำหรับเป้าหมายทั้งหมด แต่ในโลกนี้เราต้องทนกับ Microsoft, Intel, Metroworks, Sony และอื่น ๆ
Andreas

5

ใน C คอมไพเลอร์สามารถปรับให้เหมาะสมโดยทั่วไปหากผลลัพธ์ไม่ได้ใช้

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


4

ฉันนึกถึงสถานการณ์ที่ postfix ช้ากว่าการเพิ่มคำนำหน้า:

ลองนึกภาพโปรเซสเซอร์ที่มี register Aใช้เป็นตัวสะสมและเป็น register เพียงตัวเดียวที่ใช้ในหลาย ๆ คำสั่ง (ไมโครคอนโทรลเลอร์ขนาดเล็กบางตัวเป็นแบบนี้)

ทีนี้ลองนึกถึงโปรแกรมต่อไปนี้และการแปลเป็นชุดประกอบสมมุติฐาน:

การเพิ่มคำนำหน้า:

a = ++b + c;

; increment b
LD    A, [&b]
INC   A
ST    A, [&b]

; add with c
ADD   A, [&c]

; store in a
ST    A, [&a]

การเพิ่มขึ้นของ Postfix:

a = b++ + c;

; load b
LD    A, [&b]

; add with c
ADD   A, [&c]

; store in a
ST    A, [&a]

; increment b
LD    A, [&b]
INC   A
ST    A, [&b]

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

แน่นอนว่าหากไม่ได้ใช้ค่าของการเพิ่มเช่นi++;คำสั่งเดียวคอมไพเลอร์สามารถ (และไม่) เพียงแค่สร้างคำสั่งที่เพิ่มขึ้นโดยไม่คำนึงถึงการใช้ postfix หรือคำนำหน้า


ในฐานะที่เป็นข้อความด้านข้างฉันต้องการพูดถึงว่านิพจน์ที่มีการb++แปลงไม่สามารถแปลงให้เป็นหนึ่งเดียวได้++bโดยไม่ต้องใช้ความพยายามเพิ่มเติมใด ๆ (ตัวอย่างเช่นโดยการเพิ่มก- 1) ดังนั้นการเปรียบเทียบทั้งสองหากพวกเขาเป็นส่วนหนึ่งของการแสดงออกบางอย่างไม่ถูกต้องจริงๆ บ่อยครั้งที่คุณใช้b++ภายในนิพจน์ที่คุณไม่สามารถใช้งาน++bได้แม้ว่า++bจะมีประสิทธิภาพมากกว่า แต่ก็อาจผิดพลาดได้ มีข้อยกเว้นแน่นอนถ้านิพจน์นั้นขอร้อง (ตัวอย่างเช่นa = b++ + 1;ซึ่งสามารถเปลี่ยนเป็นa = ++b;)


4

ฉันอ่านคำตอบส่วนใหญ่ที่นี่และความคิดเห็นจำนวนมากและฉันไม่เห็นการอ้างอิงใด ๆ กับตัวอย่างหนึ่งที่ฉันสามารถคิดได้ว่าที่i++ใดมีประสิทธิภาพมากกว่า++i(และบางที--i ก็น่าแปลกใจก็มีประสิทธิภาพมากกว่าi--) นั่นคือคอมไพเลอร์ C สำหรับ DEC PDP-11!

PDP-11 มีคำแนะนำการประกอบสำหรับการลดลงล่วงหน้าของการลงทะเบียนและการเพิ่มขึ้นภายหลัง แต่ไม่ใช่วิธีอื่น ๆ คำแนะนำอนุญาตให้ใช้การลงทะเบียน "จุดประสงค์ทั่วไป" ใด ๆ เพื่อใช้เป็นตัวชี้สแต็ก ดังนั้นหากคุณใช้บางอย่างเช่น*(i++)มันอาจจะรวบรวมเป็นคำสั่งการชุมนุมเดียวในขณะที่*(++i)ไม่สามารถ

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


2
ลึกลับมาก แต่น่าสนใจมาก!
Mark Harrison

@daShier +1 แม้ว่าฉันจะไม่เห็นด้วย แต่นี่ไม่ใช่ความลับหรืออย่างน้อยก็ไม่ควร C ได้รับการพัฒนาร่วมกับ Unix ที่ AT&T Bell Labs ในช่วงต้นยุค 70 เมื่อ PDP-11 เป็นโปรเซสเซอร์เป้าหมาย ในซอร์สโค้ด Unix จากการเพิ่มขึ้นของยุคโพสต์นี้ "i ++" เป็นที่แพร่หลายมากขึ้นบางส่วนเพราะนักพัฒนารู้ว่าเมื่อมีการกำหนดค่า "j = i ++" หรือใช้เป็นดัชนี "a [i ++] = n" รหัสจะเร็วขึ้นเล็กน้อย (และเล็กกว่า) ดูเหมือนว่าพวกเขาจะติดนิสัยการใช้การเพิ่มโพสต์เว้นแต่จะมีการกำหนดไว้ล่วงหน้า คนอื่น ๆ เรียนรู้จากการอ่านรหัสและหยิบนิสัยนี้ขึ้นมา
jimhark

68000 มีคุณสมบัติเดียวกันการเพิ่มขึ้นภายหลังและการลดลงล่วงหน้าได้รับการสนับสนุนในฮาร์ดแวร์ (เป็นโหมดการกำหนดแอดเดรส) แต่ไม่ใช่วิธีอื่น ทีม Motorola ได้รับแรงบันดาลใจจาก DEC PDP-11
jimhark

2
@jimhark ใช่ฉันเป็นหนึ่งในผู้ PDP-11 โปรแกรมเมอร์ที่เปลี่ยนไป 68000 และยังคงใช้และ--i i++
daShier

2

ฉันชอบการเพิ่มล่วงหน้าเสมอ ...

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

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

การให้ optmizer น้อยลงในการทำเช่นนั้นหมายความว่าคอมไพเลอร์ทำงานได้เร็วขึ้น


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

เขาระบุว่า "ถ้าฟังก์ชั่นอินไลน์" และนั่นทำให้การใช้เหตุผลของเขาถูกต้อง
Blaisorblade

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

0

My C เป็นสนิมเล็กน้อยดังนั้นฉันจึงขออภัยล่วงหน้า Speedwise ฉันเข้าใจผลลัพธ์ แต่ฉันสับสนว่าไฟล์ทั้งสองออกมาอย่างไรกับ MD5 แฮชเดียวกัน บางทีการวนซ้ำจะทำงานเหมือนกัน แต่รหัส 2 บรรทัดต่อไปนี้จะไม่สร้างแอสเซมบลีที่แตกต่างกันใช่หรือไม่

myArray[i++] = "hello";

VS

myArray[++i] = "hello";

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

แค่สองเซ็นต์ของฉัน


1
@Jason Z การเพิ่มประสิทธิภาพคอมไพเลอร์เกิดขึ้นก่อนที่การสร้างแอสเซมบลีจะเสร็จสิ้นจะเห็นว่าตัวแปร i ไม่ได้ถูกใช้ที่อื่นในบรรทัดเดียวกันดังนั้นการถือค่าของมันจะเป็นของเสีย แต่นั่นเป็นเพียงการคาดเดา ฉันไม่สามารถรอจนกว่าอาจารย์ผู้สอนคนใดคนหนึ่งของฉันจะพยายามบอกว่ามันเร็วขึ้นและฉันจะเป็นคนที่แก้ไขทฤษฎีด้วยหลักฐานเชิงปฏิบัติ ฉันเกือบจะรู้สึกถึงความเป็นปฏิปักษ์อยู่แล้ว ^ _ ^
Tarks

3
"ฉัน C เป็นสนิมเล็กน้อยดังนั้นฉันจึงขออภัยล่วงหน้า" ไม่มีอะไรผิดปกติกับ C ของคุณ แต่คุณไม่ได้อ่านคำถามเดิมทั้งหมด: "มีความแตกต่างของประสิทธิภาพระหว่าง i ++ และ ++ i หรือไม่หากค่าผลลัพธ์ไม่ได้ใช้? " หมายเหตุตัวเอียง ในตัวอย่างของคุณ 'ผลลัพธ์' ของ i ++ / ++ i ถูกใช้ แต่ในสำนวนสำหรับลูปจะไม่ใช้ 'ผลลัพธ์' ของตัวดำเนินการ pre / postincrement เพื่อให้คอมไพเลอร์สามารถทำสิ่งที่ชอบได้
Roddy

2
ในตัวอย่างของคุณรหัสจะแตกต่างกันเนื่องจากคุณกำลังใช้ค่า ตัวอย่างที่พวกเขาเหมือนกันคือการใช้ ++ สำหรับการเพิ่มขึ้นเท่านั้นและไม่ได้ใช้ค่าที่ส่งคืนโดยอย่างใดอย่างหนึ่ง
John Gardner

1
แก้ไข: "คอมไพเลอร์จะเห็นว่าไม่ได้ใช้ค่าส่งคืนของ i ++ ดังนั้นจึงเปลี่ยนเป็น i ++" สิ่งที่คุณเขียนนั้นผิดเพราะคุณไม่สามารถมี i ร่วมกับหนึ่งใน i ++, i ++ ในบรรทัดเดียวกัน (คำสั่ง) ผลลัพธ์นั้นไม่ได้กำหนดไว้
Blaisorblade

1
เปลี่ยนแปลง foo[i++]ไปfoo[++i]โดยไม่ต้องเปลี่ยนอะไรก็ได้ที่เห็นได้ชัดว่าจะเปลี่ยนความหมายโปรแกรม แต่ในการประมวลผลเมื่อใช้คอมไพเลอร์ที่ไม่มีตรรกะการเพิ่มประสิทธิภาพห่วง hoisting, การเพิ่มpและqครั้งเดียวแล้ววิ่งห่วงซึ่งดำเนินการเช่นจะเร็วกว่าการใช้ห่วงที่จะดำเนินการ*(p++)=*(q++); *(++pp)=*(++q);สำหรับลูปที่แน่นมากในโปรเซสเซอร์บางตัวความแตกต่างของความเร็วอาจมีนัยสำคัญ (มากกว่า 10%) แต่อาจเป็นกรณีเดียวใน C ที่การเพิ่มการโพสต์จะเร็วกว่าการเพิ่มล่วงหน้าอย่างมาก
supercat
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.