Có sự khác biệt về hiệu năng giữa i ++ và ++ i trong C không?


Câu trả lời:


397

Tóm tắt điều hành: Không.

i++có thể có khả năng chậm hơn ++i, vì giá trị cũ i có thể cần được lưu lại để sử dụng sau này, nhưng trong thực tế, tất cả các trình biên dịch hiện đại sẽ tối ưu hóa điều này.

Chúng ta có thể chứng minh điều này bằng cách xem mã cho hàm này, cả với ++ii++.

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

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

}

Các tệp giống nhau, ngoại trừ ++ii++:

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

Chúng tôi sẽ biên dịch chúng và cũng có được trình biên dịch được tạo:

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

Và chúng ta có thể thấy rằng cả các tệp đối tượng và trình biên dịch được tạo ra đều giống nhau.

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

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

9
Tôi biết câu hỏi này là về C, nhưng tôi muốn biết liệu trình duyệt có thể thực hiện tối ưu hóa này cho javascript hay không.
TM.

69
Vì vậy, "Không" là đúng đối với một trình biên dịch mà bạn đã thử nghiệm.
Andreas

173
@Andreas: Câu hỏi hay. Tôi từng là một người viết trình biên dịch và có cơ hội kiểm tra mã này trên nhiều CPU, hệ điều hành và trình biên dịch. Trình biên dịch duy nhất tôi thấy không tối ưu hóa trường hợp i ++ (trên thực tế, trình biên dịch khiến tôi chú ý một cách chuyên nghiệp) là trình biên dịch Software Toolworks C80 của Walt Bilofsky. Trình biên dịch đó dành cho các hệ thống Intel 8080 CP / M. Thật an toàn khi nói rằng bất kỳ trình biên dịch nào không bao gồm tối ưu hóa này không có nghĩa là sử dụng chung.
Đánh dấu Harrison

22
Mặc dù sự khác biệt về hiệu suất là không đáng kể và được tối ưu hóa trong nhiều trường hợp - vui lòng lưu ý rằng vẫn nên sử dụng ++ithay vì sử dụng tốt i++. Hoàn toàn không có lý do gì để không và nếu phần mềm của bạn đi qua một chuỗi công cụ không tối ưu hóa nó thì phần mềm của bạn sẽ hiệu quả hơn. Xem xét nó chỉ dễ gõ ++inhư nó là để gõ i++, thực sự không có lý do gì để không sử dụng ++iở nơi đầu tiên.
đơn sắc

7
@monokrome Vì các nhà phát triển có thể cung cấp triển khai riêng cho các toán tử tiền tố và hậu tố bằng nhiều ngôn ngữ, đây có thể không phải lúc nào cũng là một giải pháp chấp nhận được cho trình biên dịch mà không cần so sánh trước các hàm đó, có thể không tầm thường.
pickypg

112

Từ hiệu quả so với ý định của Andrew Koenig:

Đầu tiên, rõ ràng ++ilà hiệu quả hơn i++, ít nhất là khi các biến số nguyên có liên quan.

Và:

Vì vậy, câu hỏi người ta nên đặt ra không phải là hoạt động nào trong hai thao tác này nhanh hơn, mà là hoạt động nào trong hai thao tác này thể hiện chính xác hơn những gì bạn đang cố gắng thực hiện. Tôi gửi rằng nếu bạn không sử dụng giá trị của biểu thức, không bao giờ có lý do để sử dụng i++thay vì ++i, vì không bao giờ có lý do để sao chép giá trị của biến, tăng biến và sau đó ném bản sao đi.

Vì vậy, nếu giá trị kết quả không được sử dụng, tôi sẽ sử dụng ++i. Nhưng không phải vì nó hiệu quả hơn: bởi vì nó nói chính xác ý định của tôi.


5
Chúng ta đừng quên rằng các toán tử đơn nguyên khác cũng là tiền tố. Tôi nghĩ ++ i là cách "ngữ nghĩa" để sử dụng toán tử đơn nguyên, trong khi i ++ có mặt để phù hợp với một nhu cầu cụ thể (đánh giá trước khi bổ sung).
TM.

9
Nếu giá trị kết quả không được sử dụng, sẽ không có sự khác biệt về ngữ nghĩa: nghĩa là không có cơ sở để thích cái này hay cái kia.
Eamon Nerbonne

3
Là một độc giả, tôi thấy một sự khác biệt. Vì vậy, với tư cách là một nhà văn, tôi sẽ thể hiện ý định của mình bằng cách chọn cái này qua cái khác. Đó chỉ là thói quen của tôi, để cố gắng giao tiếp với bạn bè và đồng đội lập trình viên của mình thông qua mã :)
Sébastien RoccaSerra

11
Theo lời khuyên của Koenig, tôi đang i++cùng một cách tôi muốn đang i += nhay i = i + n, ví dụ, dưới dạng mục tiêu động từ đối tượng , với mục tiêu toán hạng bên trái của động từ điều hành. Trong trường hợp i++, không có đối tượng bên phải , nhưng quy tắc vẫn được áp dụng, giữ mục tiêu ở bên trái của toán tử động từ .
David R Tribble

4
Nếu bạn đang cố gắng tăng i, thì cả i ++ và ++ i đều thể hiện chính xác ý định của bạn. Không có lý do để thích cái này hơn cái kia. Là một người đọc tôi thấy không có gì khác biệt, các đồng nghiệp của bạn có bị nhầm lẫn khi nhìn thấy i ++ và nghĩ, có lẽ đây là một lỗi đánh máy và anh ta không có ý định tăng i?
dan carter

46

Một câu trả lời tốt hơn là ++iđôi khi sẽ nhanh hơn nhưng không bao giờ chậm hơn.

Mọi người dường như đang cho rằng đó ilà một loại tích hợp thông thường như int. Trong trường hợp này sẽ không có sự khác biệt có thể đo lường được.

Tuy nhiên nếu ilà loại phức tạp thì bạn cũng có thể tìm thấy một sự khác biệt có thể đo lường được. Đối với i++bạn phải tạo một bản sao của lớp học của bạn trước khi tăng nó. Tùy thuộc vào những gì liên quan đến một bản sao, nó thực sự có thể chậm hơn vì với ++itbạn chỉ có thể trả về giá trị cuối cùng.

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

Một sự khác biệt nữa là với ++ibạn có tùy chọn trả về một tham chiếu thay vì một giá trị. Một lần nữa, tùy thuộc vào những gì liên quan đến việc tạo một bản sao của đối tượng của bạn, điều này có thể chậm hơn.

Một ví dụ trong thế giới thực về nơi điều này có thể xảy ra sẽ là việc sử dụng các trình vòng lặp. Sao chép một trình vòng lặp có thể không phải là một cổ chai trong ứng dụng của bạn, nhưng vẫn nên thực hành thói quen sử dụng ++ithay vì i++nơi mà kết quả không bị ảnh hưởng.


36
Câu hỏi nêu rõ C, không liên quan đến C ++.
Dan Cristoloveanu

5
Câu hỏi (đã được thừa nhận cũ này là về C, không phải C ++, nhưng tôi cho rằng cũng đáng nói rằng, trong C ++, ngay cả khi một lớp thực hiện các toán tử sửa lỗi trước và sửa lỗi, chúng thậm chí không nhất thiết phải liên quan. Ví dụ: thanh ++ có thể tăng một thành viên dữ liệu, trong khi thanh ++ có thể tăng một thành viên dữ liệu khác và trong trường hợp này, bạn sẽ không có tùy chọn để sử dụng, vì ngữ nghĩa là khác nhau.
Kevin

-1 Mặc dù đây là lời khuyên tốt cho các lập trình viên C ++, nhưng nó không trả lời câu hỏi, được gắn thẻ C, dù chỉ một chút. Trong C, nó hoàn toàn không có sự khác biệt cho dù bạn sử dụng tiền tố hay hậu tố.
Lundin

@Pacerier Câu hỏi chỉ được gắn thẻ C và C. Tại sao bạn cho rằng họ không quan tâm đến điều đó? Dựa vào cách SO hoạt động, sẽ không thông minh hơn khi cho rằng họ chỉ quan tâm đến C, hơn là Java, C #, COBOL hoặc bất kỳ ngôn ngữ ngoài chủ đề nào khác?
Lundin

18

Câu trả lời ngắn:

Không bao giờ có bất kỳ sự khác biệt giữa i++++ivề tốc độ. Một trình biên dịch tốt không nên tạo mã khác nhau trong hai trường hợp.

Câu trả lời dài:

Điều mà mọi câu trả lời khác không đề cập đến là sự khác biệt giữa ++iso với i++chỉ có ý nghĩa trong biểu thức mà nó được tìm thấy.

Trong trường hợp for(i=0; i<n; i++), i++một mình trong biểu thức riêng của nó: có một điểm thứ tự trước i++và có một điểm sau nó. Do đó, mã máy duy nhất được tạo là "tăng itheo 1" và được xác định rõ cách thức điều này được giải trình tự liên quan đến phần còn lại của chương trình. Vì vậy, nếu bạn muốn thay đổi nó thành tiền tố ++, nó sẽ không quan trọng dù chỉ một chút, bạn sẽ vẫn chỉ lấy mã máy "tăng ibởi 1".

Sự khác biệt giữa ++ii++chỉ quan trọng trong các biểu thức như array[i++] = x;so với array[++i] = x;. Một số người có thể lập luận và nói rằng hậu tố sẽ chậm hơn trong các hoạt động như vậy bởi vì thanh ghi nơi icư trú phải được tải lại sau đó. Nhưng sau đó lưu ý rằng trình biên dịch có thể tự do đặt hàng các hướng dẫn của bạn theo bất kỳ cách nào nó thích, miễn là nó không "phá vỡ hành vi của máy trừu tượng" như tiêu chuẩn C gọi nó.

Vì vậy, trong khi bạn có thể cho rằng array[i++] = x;được dịch sang mã máy là:

  • Lưu trữ giá trị của itrong thanh ghi A.
  • Lưu địa chỉ của mảng trong thanh ghi B.
  • Thêm A và B, lưu kết quả vào A.
  • Tại địa chỉ mới này được đại diện bởi A, lưu trữ giá trị của x.
  • Lưu trữ giá trị của itrong thanh ghi A // không hiệu quả vì hướng dẫn thêm ở đây, chúng tôi đã thực hiện điều này một lần.
  • Đăng ký gia tăng A.
  • Cửa hàng đăng ký A trong i.

trình biên dịch cũng có thể tạo mã hiệu quả hơn, chẳng hạn như:

  • Lưu trữ giá trị của itrong thanh ghi A.
  • Lưu địa chỉ của mảng trong thanh ghi B.
  • Thêm A và B, lưu kết quả vào B.
  • Đăng ký gia tăng A.
  • Cửa hàng đăng ký A trong i.
  • ... // phần còn lại của mã.

Chỉ vì bạn là một lập trình viên C được đào tạo để nghĩ rằng hậu tố ++xảy ra ở cuối, mã máy không phải được đặt hàng theo cách đó.

Vì vậy, không có sự khác biệt giữa tiền tố và hậu tố ++trong C. Bây giờ, những gì bạn với tư cách là một lập trình viên C nên khác nhau, là những người không nhất quán sử dụng tiền tố trong một số trường hợp và hậu tố trong các trường hợp khác, mà không có lý do tại sao. Điều này cho thấy rằng họ không chắc chắn về cách C hoạt động hoặc họ có kiến ​​thức không chính xác về ngôn ngữ. Đây luôn là một dấu hiệu xấu, đến lượt nó cho thấy rằng họ đang đưa ra các quyết định đáng ngờ khác trong chương trình của họ, dựa trên sự mê tín hoặc "giáo điều tôn giáo".

"Tiền tố ++luôn luôn nhanh hơn" thực sự là một giáo điều sai lầm phổ biến như vậy đối với các lập trình viên C sẽ là.


3
Không ai nói "Tiền tố ++ luôn nhanh hơn". Đó là trích dẫn sai. Điều họ nói là "Postfix ++ không bao giờ nhanh hơn".
Pacerier 17/2/2015

2
@Pacerier Tôi không trích dẫn bất kỳ người cụ thể nào, nhưng chỉ là một niềm tin rộng rãi, không chính xác.
Lundin

@rbaleksandar C ++! = C.
Lundin

@rbaleksandar Câu hỏi và câu trả lời đều nói về C ++. Khác với C, vì nó có quá tải toán tử và sao chép hàm tạo, v.v.
Lundin

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

17

Lấy một lá từ Scott Meyers, Hiệu quả hơn c ++ Mục 6: Phân biệt giữa các hình thức tiền tố và hậu tố của các hoạt động tăng và giảm .

Phiên bản tiền tố luôn được ưu tiên hơn tiền tố liên quan đến các đối tượng, đặc biệt là liên quan đến các trình vòng lặp.

Lý do cho điều này nếu bạn nhìn vào mẫu cuộc gọi của các nhà khai thác.

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

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

Nhìn vào ví dụ này, thật dễ dàng để thấy toán tử tiền tố sẽ luôn hiệu quả hơn so với hậu tố. Do sự cần thiết của một đối tượng tạm thời trong việc sử dụng hậu tố.

Đây là lý do tại sao khi bạn thấy các ví dụ sử dụng các trình vòng lặp, chúng luôn sử dụng phiên bản tiền tố.

Nhưng như bạn chỉ ra cho int thì thực sự không có gì khác biệt vì tối ưu hóa trình biên dịch có thể diễn ra.


4
Tôi nghĩ rằng câu hỏi của anh ấy đã nhắm vào C, nhưng đối với C ++ thì bạn hoàn toàn đúng, và hơn nữa mọi người C nên chấp nhận điều này vì họ cũng có thể sử dụng nó cho C ++. Tôi thường thấy các lập trình viên C sử dụng cú pháp postfix ;-)
Anders Rune Jensen

-1 Mặc dù đây là lời khuyên tốt cho các lập trình viên C ++, nhưng nó không trả lời câu hỏi, được gắn thẻ C, dù chỉ một chút. Trong C, nó hoàn toàn không có sự khác biệt cho dù bạn sử dụng tiền tố hay hậu tố.
Lundin

1
Tiền tố @Lundin và postifx có vấn đề trong C theo câu trả lời của Andreas . Bạn không thể cho rằng trình biên dịch sẽ tối ưu hóa và nó chỉ là cách thực hành tốt cho mọi ngôn ngữ đối với Alwas thích ++ i hơn i ++
JProgrammer

@JProgrammer Họ không quan trọng theo câu trả lời của bạn một cách chân thành :) Nếu bạn thấy rằng họ mang lại mã khác nhau, đó là do bạn không thể kích hoạt tối ưu hóa hoặc do trình biên dịch bị lỗi. Dù sao, câu trả lời của bạn không có chủ đề vì câu hỏi là về C.
Lundin

16

Đây là một quan sát bổ sung nếu bạn lo lắng về tối ưu hóa vi mô. Các vòng lặp giảm dần có thể 'có thể' hiệu quả hơn các vòng lặp tăng dần (tùy thuộc vào kiến ​​trúc tập lệnh, ví dụ ARM), được đưa ra:

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

Trên mỗi vòng lặp, bạn sẽ có một hướng dẫn cho:

  1. Thêm 1vào i.
  2. So sánh xem icó nhỏ hơn a không 100.
  3. Một nhánh có điều kiện nếu inhỏ hơn a 100.

Trong khi đó một vòng lặp giảm dần:

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

Vòng lặp sẽ có một hướng dẫn cho mỗi:

  1. Giảm i, thiết lập cờ trạng thái thanh ghi CPU.
  2. Một nhánh có điều kiện tùy thuộc vào trạng thái thanh ghi CPU ( Z==0).

Tất nhiên điều này chỉ hoạt động khi giảm xuống không!

Ghi nhớ từ Hướng dẫn dành cho nhà phát triển hệ thống ARM.


Tốt một. Nhưng điều này không tạo ra ít lượt truy cập bộ đệm hơn?
AndreasT

Có một thủ thuật kỳ lạ cũ từ sách tối ưu hóa mã, kết hợp lợi thế của nhánh kiểm tra không với địa chỉ tăng dần. Đây là ví dụ trong ngôn ngữ cấp cao (có thể là vô dụng, vì nhiều trình biên dịch đủ thông minh để thay thế nó bằng mã vòng lặp kém hiệu quả hơn nhưng phổ biến hơn): int a [N]; cho (i = -N; i; ++ i) a [N + i] + = 123;
noop

@noop bạn có thể giải thích về những cuốn sách tối ưu hóa mã nào bạn tham khảo không? Tôi đã đấu tranh để tìm những người tốt.
mezamorphic

7
Câu trả lời này không phải là một câu trả lời cho câu hỏi nào cả.
đơn sắc

@mezamorphic Đối với x86, các nguồn của tôi là: Hướng dẫn tối ưu hóa Intel, Sương mù Agner, Paul Hsieh, Michael Abrash.
noop

11

Xin đừng để câu hỏi "cái nào nhanh hơn" là yếu tố quyết định nên sử dụng cái nào. Có thể bạn sẽ không bao giờ quan tâm đến điều đó, và bên cạnh đó, thời gian đọc của lập trình viên đắt hơn nhiều so với thời gian của máy.

Sử dụng bất cứ điều gì có ý nghĩa nhất đối với con người đọc mã.


3
Tôi tin rằng thật sai lầm khi thích cải thiện khả năng đọc mơ hồ để đạt được hiệu quả thực tế và sự rõ ràng tổng thể của ý định.
noop

4
Thuật ngữ "thời gian đọc lập trình viên" của tôi gần giống với "sự rõ ràng của ý định". "Hiệu quả đạt được thực tế" thường rất lớn, đủ gần bằng 0 để gọi chúng là số không. Trong trường hợp của OP, trừ khi mã đã được định hình để thấy rằng ++ i là một nút cổ chai, câu hỏi cái nào nhanh hơn là lãng phí thời gian và các đơn vị suy nghĩ của lập trình viên.
Andy Lester

2
Sự khác biệt về khả năng đọc giữa ++ i và i ++ chỉ là vấn đề sở thích cá nhân, nhưng ++ i rõ ràng ngụ ý hoạt động đơn giản hơn i ++, mặc dù kết quả tương đương với các trường hợp tầm thường và các loại dữ liệu đơn giản khi tối ưu hóa trình biên dịch. Do đó ++ i là người chiến thắng đối với tôi, khi các thuộc tính cụ thể của tăng sau là không cần thiết.
noop

Bạn đang nói những gì tôi đang nói. Điều quan trọng là thể hiện ý định và cải thiện khả năng đọc hơn là lo lắng về "hiệu quả".
Andy Lester

2
Tôi vẫn không thể đồng ý. Nếu khả năng đọc đứng cao hơn trong danh sách ưu tiên của bạn thì có thể sự lựa chọn ngôn ngữ lập trình của bạn là sai. Mục đích chính của C / C ++ là viết mã hiệu quả.
noop

11

Trước hết: Sự khác biệt giữa i++và không thể phủ nhận ++iở C.


Để các chi tiết.

1. Vấn đề C ++ nổi tiếng: ++inhanh hơn

Trong C ++, ++iiff hiệu quả hơn ilà một loại đối tượng có toán tử gia tăng quá tải.

Tại sao?
Trong ++i, đối tượng được tăng đầu tiên và sau đó có thể được chuyển dưới dạng tham chiếu const cho bất kỳ chức năng nào khác. Điều này là không thể nếu biểu thức là foo(i++)bởi vì bây giờ việc tăng cần phải được thực hiện trước đó foo()được gọi, nhưng giá trị cũ cần được truyền vào foo(). Do đó, trình biên dịch buộc phải tạo một bản sao itrước khi nó thực thi toán tử gia tăng trên bản gốc. Các lệnh gọi hàm tạo / hàm hủy bổ sung là phần xấu.

Như đã lưu ý ở trên, điều này không áp dụng cho các loại cơ bản.

2. Thực tế ít được biết đến: i++ thể nhanh hơn

Nếu không có hàm tạo / hàm hủy nào cần được gọi, đó luôn là trường hợp trong C, ++ii++phải nhanh như nhau, phải không? Không. Chúng hầu như nhanh như nhau, nhưng có thể có những khác biệt nhỏ, mà hầu hết những người trả lời khác đã hiểu sai.

Làm thế nào có i++thể nhanh hơn?
Điểm quan trọng là phụ thuộc dữ liệu. Nếu giá trị cần được tải từ bộ nhớ, hai thao tác tiếp theo cần được thực hiện với nó, tăng nó và sử dụng nó. Với ++i, việc tăng cần phải được thực hiện trước khi giá trị có thể được sử dụng. Với i++, việc sử dụng không phụ thuộc vào mức tăng và CPU có thể thực hiện thao tác sử dụng song song với thao tác tăng. Sự khác biệt là nhiều nhất là một chu kỳ CPU, vì vậy nó thực sự không thể tin được, nhưng nó ở đó. Và đó là cách khác để nhiều người mong đợi.


Về điểm 2 của bạn: Nếu ++ihoặc i++được sử dụng trong một biểu thức khác, việc thay đổi giữa chúng sẽ thay đổi ngữ nghĩa của biểu thức, do đó, bất kỳ khả năng tăng / giảm hiệu suất nào có thể xảy ra. Nếu chúng là độc lập, nghĩa là, kết quả của hoạt động không được sử dụng ngay lập tức, thì bất kỳ trình biên dịch tử tế nào cũng sẽ biên dịch nó thành cùng một thứ, ví dụ như một INClệnh lắp ráp.
Shahbaz

1
@Shahbaz Điều đó hoàn toàn đúng, nhưng không phải là vấn đề. 1) Mặc dù ngữ nghĩa là khác nhau, cả hai i++++icó thể được sử dụng thay thế cho nhau trong hầu hết mọi tình huống có thể bằng cách điều chỉnh các hằng số vòng lặp bởi một, vì vậy chúng gần tương đương với những gì chúng làm cho lập trình viên. 2) Mặc dù cả hai đều biên dịch theo cùng một lệnh, việc thực thi chúng khác nhau đối với CPU. Trong trường hợp i++, CPU có thể tính toán mức tăng song song với một số lệnh khác sử dụng cùng một giá trị (CPU thực sự làm điều này!), Trong khi với ++iCPU phải lên lịch cho lệnh khác sau khi tăng.
cmaster - phục hồi monica

4
@Shahbaz Ví dụ: if(++foo == 7) bar();if(foo++ == 6) bar();tương đương về chức năng. Tuy nhiên, lần thứ hai có thể nhanh hơn một chu kỳ, bởi vì sự so sánh và gia tăng có thể được tính toán song song bởi CPU. Không phải là chu kỳ duy nhất này quan trọng nhiều, nhưng sự khác biệt là có.
cmaster - phục hồi monica

1
Điểm tốt. Các hằng số hiển thị rất nhiều (cũng như <ví dụ vs <=) ++thường được sử dụng, do đó, việc chuyển đổi giữa mặc dù thường dễ dàng có thể.
Shahbaz

1
Tôi thích điểm 2, nhưng điều đó chỉ áp dụng nếu giá trị được sử dụng, phải không? Câu hỏi cho biết "nếu giá trị kết quả không được sử dụng?", Vì vậy nó có thể gây nhầm lẫn.
jinawee

7

@Mark Mặc dù trình biên dịch được phép tối ưu hóa bản sao tạm thời (dựa trên ngăn xếp) của biến và gcc (trong các phiên bản gần đây) đang làm như vậy, không có nghĩa là tất cả các trình biên dịch sẽ luôn làm như vậy.

Tôi vừa thử nghiệm nó với các trình biên dịch mà chúng tôi sử dụng trong dự án hiện tại của chúng tôi và 3 trong số 4 không tối ưu hóa nó.

Không bao giờ giả sử trình biên dịch làm cho đúng, đặc biệt là nếu mã có thể nhanh hơn, nhưng không bao giờ chậm hơn là dễ đọc.

Nếu bạn không có triển khai thực sự ngu ngốc đối với một trong các toán tử trong mã của mình:

Alwas thích ++ i hơn i ++.


Chỉ tò mò ... tại sao bạn sử dụng 4 trình biên dịch C khác nhau trong một dự án? Hoặc trong một nhóm hoặc một công ty, cho vấn đề đó?
Lawrence Dol

3
Khi tạo trò chơi cho bảng điều khiển, mọi nền tảng đều mang đến trình biên dịch / toolchain riêng. Trong một thế giới hoàn hảo, chúng ta có thể sử dụng gcc / clang / llvm cho tất cả các mục tiêu, nhưng trong thế giới này, chúng ta phải theo kịp Microsoft, Intel, Metrowork, Sony, v.v.
Andreas

5

Trong C, trình biên dịch nói chung có thể tối ưu hóa chúng giống nhau nếu kết quả không được sử dụng.

Tuy nhiên, trong C ++ nếu sử dụng các loại khác cung cấp toán tử ++ của riêng họ, phiên bản tiền tố có thể sẽ nhanh hơn phiên bản hậu tố. Vì vậy, nếu bạn không cần ngữ nghĩa hậu tố, tốt hơn là sử dụng toán tử tiền tố.


4

Tôi có thể nghĩ về một tình huống mà postfix chậm hơn so với gia tăng tiền tố:

Hãy tưởng tượng một bộ xử lý với thanh ghi Ađược sử dụng làm bộ tích lũy và đó là thanh ghi duy nhất được sử dụng trong nhiều hướng dẫn (một số bộ vi điều khiển nhỏ thực sự như thế này).

Bây giờ hãy tưởng tượng chương trình sau đây và bản dịch của chúng thành một hội nghị giả định:

Gia tăng tiền tố:

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]

Gia tăng hậu tố:

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]

Lưu ý cách giá trị của bbị buộc phải tải lại. Với gia tăng tiền tố, trình biên dịch chỉ có thể tăng giá trị và tiếp tục sử dụng nó, có thể tránh tải lại vì giá trị mong muốn đã có trong thanh ghi sau khi tăng. Tuy nhiên, với gia tăng postfix, trình biên dịch phải xử lý hai giá trị, một giá trị cũ và một giá trị tăng dần mà tôi hiển thị ở trên dẫn đến kết quả là có thêm một lần truy cập bộ nhớ.

Tất nhiên, nếu giá trị của gia số không được sử dụng, chẳng hạn như một i++;câu lệnh đơn , trình biên dịch có thể (và không) chỉ đơn giản tạo ra một lệnh tăng bất kể sử dụng tiền tố hay tiền tố.


Là một lưu ý phụ, tôi muốn đề cập rằng một biểu thức trong đó b++không thể chuyển đổi thành biểu thức ++bmà không cần bất kỳ nỗ lực bổ sung nào (ví dụ: bằng cách thêm a - 1). Vì vậy, so sánh hai nếu chúng là một phần của một số biểu thức là không thực sự hợp lệ. Thông thường, nơi bạn sử dụng b++bên trong một biểu thức bạn không thể sử dụng ++b, do đó, ngay cả khi ++bcó khả năng hiệu quả hơn, điều đó chỉ đơn giản là sai. Tất nhiên ngoại lệ là nếu biểu thức đang cầu xin nó (ví dụ a = b++ + 1;có thể thay đổi thành a = ++b;).


4

Tôi đã đọc qua hầu hết các câu trả lời ở đây và nhiều ý kiến, và tôi không thấy bất kỳ tài liệu tham khảo nào về một trường hợp mà tôi có thể nghĩ về nơi nào i++hiệu quả hơn ++i(và có lẽ đáng ngạc nhiên --i hiệu quả hơn i--). Đó là cho trình biên dịch C cho DEC PDP-11!

PDP-11 có các hướng dẫn lắp ráp để giảm trước đăng ký và tăng sau, nhưng không phải là cách khác. Các hướng dẫn cho phép bất kỳ đăng ký "mục đích chung" nào được sử dụng như một con trỏ ngăn xếp. Vì vậy, nếu bạn sử dụng một cái gì đó giống như *(i++)nó có thể được biên dịch thành một hướng dẫn lắp ráp duy nhất, trong khi *(++i)không thể.

Đây rõ ràng là một ví dụ rất bí truyền, nhưng nó cung cấp ngoại lệ khi tăng sau hiệu quả hơn (hoặc tôi nên nói , vì không có nhiều nhu cầu về mã PDP-11 C ngày nay).


2
Rất bí truyền, nhưng rất thú vị!
Đánh dấu Harrison

@daShier. +1, mặc dù tôi không đồng ý, nhưng đây không phải là bí truyền, hoặc ít nhất là không nên. C được hợp tác phát triển với Unix tại AT & T Bell Labs vào đầu những năm 70 khi PDP-11 là bộ xử lý mục tiêu. Trong mã nguồn Unix từ gia tăng bài đăng thời đại này, "i ++", phổ biến hơn một phần vì các nhà phát triển biết khi nào giá trị được gán, "j = i ++" hoặc được sử dụng làm chỉ mục, "a [i ++] = n", mã sẽ nhanh hơn một chút (và nhỏ hơn). Có vẻ như họ có thói quen sử dụng bài tăng trừ khi được yêu cầu trước. Những người khác học bằng cách đọc mã của họ và cũng chọn thói quen này.
jimhark

68000 có tính năng tương tự, tăng sau và giảm trước được hỗ trợ trong phần cứng (dưới dạng chế độ địa chỉ), nhưng không phải là cách khác. Nhóm Motorola, ừm, lấy cảm hứng từ DEC PDP-11.
jimhark

2
@jimhark, vâng, tôi là một trong những lập trình viên PDP-11 đã chuyển sang 68000 và vẫn sử dụng --ii++.
daShier

2

Tôi luôn thích tăng trước, tuy nhiên ...

Tôi muốn chỉ ra rằng ngay cả trong trường hợp gọi hàm toán tử ++, trình biên dịch sẽ có thể tối ưu hóa tạm thời nếu hàm được nội tuyến. Vì toán tử ++ thường ngắn và thường được triển khai trong tiêu đề, nên nó có khả năng được nội tuyến.

Vì vậy, đối với các mục đích thực tế, có thể không có nhiều sự khác biệt giữa hiệu suất của hai hình thức. Tuy nhiên, tôi luôn thích tăng trước vì có vẻ tốt hơn để thể hiện trực tiếp những gì tôi đang cố gắng nói, thay vì dựa vào trình tối ưu hóa để tìm ra nó.

Ngoài ra, cung cấp cho trình tối ưu hóa ít hơn để thực hiện có nghĩa là trình biên dịch chạy nhanh hơn.


Bài đăng của bạn là C ++ - cụ thể trong khi câu hỏi là về C. Trong mọi trường hợp, câu trả lời của bạn là sai: đối với các toán tử tăng sau tùy chỉnh, trình biên dịch nói chung sẽ không thể tạo mã hiệu quả.
Konrad Rudolph

Anh ta nói "nếu chức năng được nội tuyến", và điều đó làm cho lý luận của anh ta đúng.
Blaisorblade

Vì C không cung cấp quá tải toán tử, nên câu hỏi trước và sau phần lớn không thú vị. Trình biên dịch có thể tối ưu hóa một temp không sử dụng bằng cách sử dụng cùng logic được áp dụng cho bất kỳ giá trị được tính toán nguyên thủy, chưa sử dụng nào khác. Xem câu trả lời được chọn cho một mẫu.

0

C của tôi là một chút gỉ, vì vậy tôi xin lỗi trước. Tốc độ, tôi có thể hiểu kết quả. Nhưng, tôi bối rối không biết làm thế nào cả hai tệp xuất hiện cùng một hàm băm MD5. Có thể một vòng lặp for chạy giống nhau, nhưng 2 dòng mã sau đây sẽ tạo ra sự lắp ráp khác nhau?

myArray[i++] = "hello";

đấu với

myArray[++i] = "hello";

Cái đầu tiên ghi giá trị vào mảng, sau đó tăng i. Gia số thứ hai sau đó tôi ghi vào mảng. Tôi không phải là chuyên gia lắp ráp, nhưng tôi không thấy cách thực thi giống nhau sẽ được tạo ra bởi 2 dòng mã khác nhau này.

Chỉ hai xu của tôi.


1
@Jason Z Việc tối ưu hóa trình biên dịch xảy ra trước khi quá trình lắp ráp kết thúc được tạo ra, nó sẽ thấy rằng biến i không được sử dụng ở bất kỳ nơi nào khác trên cùng một dòng, vì vậy việc giữ giá trị của nó sẽ là một sự lãng phí, có lẽ nó sẽ chuyển sang i ++ một cách hiệu quả. Nhưng đó chỉ là dự đoán. Tôi không thể đợi cho đến khi một trong những giảng viên của tôi cố gắng nói nhanh hơn và tôi trở thành người sửa chữa một lý thuyết với bằng chứng thực tế. Tôi gần như có thể cảm nhận được sự thù hằn rồi ^ _ ^
Tarks

3
"C của tôi hơi rỉ sét, vì vậy tôi xin lỗi trước." Không có gì sai với C của bạn, nhưng bạn đã không đọc đầy đủ câu hỏi ban đầu: "Có sự khác biệt về hiệu suất giữa i ++ và ++ i nếu giá trị kết quả không được sử dụng không? " Lưu ý chữ nghiêng. Trong ví dụ của bạn, 'kết quả' của i ++ / ++ i được sử dụng, nhưng trong vòng lặp thành ngữ, 'kết quả' của toán tử pre / postincrement không được sử dụng để trình biên dịch có thể làm những gì nó thích.
Roddy

2
trong ví dụ của bạn, mã sẽ khác, bởi vì bạn đang sử dụng giá trị. ví dụ trong đó chúng giống nhau chỉ sử dụng ++ cho gia số và không sử dụng giá trị được trả về.
John Gardner

1
Khắc phục: "trình biên dịch sẽ thấy rằng giá trị trả về của i ++ không được sử dụng, vì vậy nó sẽ lật nó sang ++ i". Những gì bạn viết cũng sai vì bạn không thể có i cùng với một trong số i ++, i ++ trên cùng một dòng (câu lệnh), kết quả đó không được xác định.
Blaisorblade

1
Thay đổi foo[i++]thành foo[++i]không thay đổi bất cứ điều gì khác rõ ràng sẽ thay đổi ngữ nghĩa chương trình, nhưng trên một số bộ xử lý khi sử dụng trình biên dịch không có logic tối ưu hóa vòng lặp, tăng pqmột lần rồi chạy một vòng lặp thực hiện, ví dụ như *(p++)=*(q++);sẽ nhanh hơn sử dụng vòng lặp thực hiện *(++pp)=*(++q);. Đối với các vòng lặp rất chặt chẽ trên một số bộ xử lý, chênh lệch tốc độ có thể là đáng kể (hơn 10%), nhưng đó có lẽ là trường hợp duy nhất trong C khi tăng sau nhanh hơn về mặt vật chất so với tăng trước.
supercat
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.