X + = a có nhanh hơn x = x + a không?


84

Tôi đang đọc "Ngôn ngữ lập trình C ++" của Stroustrup, anh ấy nói rằng trong số hai cách để thêm thứ gì đó vào một biến

x = x + a;

x += a;

Anh ấy thích +=vì nó rất có thể được triển khai tốt hơn. Tôi nghĩ ý anh ấy là nó cũng hoạt động nhanh hơn.
Nhưng nó có thực sự không? Nếu nó phụ thuộc vào trình biên dịch và những thứ khác, làm cách nào để kiểm tra?


45
"Ngôn ngữ lập trình C ++" được xuất bản lần đầu tiên vào năm 1985. Phiên bản gần đây nhất được xuất bản vào năm 1997 và phiên bản đặc biệt của phiên bản 1997 được xuất bản vào năm 2000. Do đó, một số phần đã rất lỗi thời.
JoeG

5
Hai dòng có thể làm một cái gì đó hoàn toàn khác nhau. Bạn cần phải được cụ thể hơn.
Kerrek SB


26
Các trình biên dịch hiện đại đủ thông minh để những câu hỏi này được coi là 'lỗi thời'.
gd1 18/09/12

2
Đã mở lại cái này vì câu hỏi trùng lặp hỏi về C không phải C ++.
Kev

Câu trả lời:


212

Bất kỳ trình biên dịch trị giá muối của nó sẽ tạo ra một cách chính xác các chuỗi ngôn ngữ máy tương tự cho cả hai cấu trúc đối với bất kỳ built-in loại ( int, float, vv) miễn là báo cáo kết quả thực sự là đơn giản như x = x + a; và tối ưu hóa được kích hoạt . (Đáng chú ý, GCC's -O0, là chế độ mặc định, thực hiện chống tối ưu hóa , chẳng hạn như chèn các kho lưu trữ hoàn toàn không cần thiết vào bộ nhớ, để đảm bảo rằng trình gỡ lỗi luôn có thể tìm thấy các giá trị thay đổi.)

Tuy nhiên, nếu câu lệnh phức tạp hơn, chúng có thể khác nhau. Giả sử flà một hàm trả về một con trỏ, thì

*f() += a;

fchỉ gọi một lần, trong khi

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

gọi nó hai lần. Nếu fcó tác dụng phụ, một trong hai sẽ sai (có thể là sau này). Ngay cả khi fkhông có tác dụng phụ, trình biên dịch có thể không loại bỏ được cuộc gọi thứ hai, vì vậy cuộc gọi thứ hai thực sự có thể chậm hơn.

Và vì chúng ta đang nói về C ++ ở đây, tình hình hoàn toàn khác đối với các loại lớp quá tải operator+operator+=. Nếu xlà một loại như vậy, thì - trước khi tối ưu hóa - x += adịch thành

x.operator+=(a);

trong khi x = x + adịch sang

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

Bây giờ, nếu lớp được viết đúng cách trình tối ưu hóa của trình biên dịch đủ tốt, cả hai sẽ kết thúc việc tạo ra cùng một ngôn ngữ máy, nhưng điều đó không chắc chắn như đối với các loại tích hợp sẵn. Đây có lẽ là những gì Stroustrup nghĩ đến khi ông khuyến khích sử dụng +=.


14
Ngoài ra còn có một khía cạnh khác - khả năng đọc . Thành ngữ C ++ để thêm exprvào varvar+=exprvà viết nó theo cách khác sẽ khiến người đọc bối rối.
Tadeusz Kopec

21
Nếu bạn thấy mình viết *f() = *f() + a;bạn có thể muốn có một cái nhìn cứng tốt vào những gì bạn đang thực sự cố gắng để đạt được ...
Adam Davis

3
Và nếu var = var + expr khiến bạn bối rối, nhưng var + = expr thì không, bạn là kỹ sư phần mềm kỳ lạ nhất mà tôi từng gặp. Có thể đọc được; chỉ cần đảm bảo bạn đang ổn định (và tất cả chúng ta sử dụng op =, vì vậy tranh luận của nó anyway = P)
WhozCraig

7
@PiotrDobrogost: Có gì sai khi trả lời câu hỏi? Trong mọi trường hợp, người hỏi phải là người kiểm tra các bản sao.
Gorpik

4
@PiotrDobrogost đối với tôi có vẻ như bạn hơi ... ghen tị ... Nếu bạn muốn đi xung quanh để tìm bản sao, hãy đi tìm nó. Đối với một người, tôi thích trả lời các câu hỏi hơn là tìm kiếm các bản lừa đảo (trừ khi đó là một câu hỏi mà tôi đặc biệt nhớ lại đã từng thấy trước đây). Đôi khi nó có thể nhanh hơn, và công việc giúp người đặt câu hỏi nhanh hơn. Ngoài ra, lưu ý rằng đây thậm chí không phải là một vòng lặp. 1là một hằng số, acó thể là một kiểu biến động, một kiểu do người dùng xác định hoặc bất cứ điều gì. Hoàn toàn khác biệt. Trong thực tế, tôi không hiểu làm thế nào mà điều này lại được kết thúc.
Luchian Grigore

56

Bạn có thể kiểm tra bằng cách nhìn vào phần tháo lắp, sẽ giống nhau.

Đối với các loại cơ bản , cả hai đều nhanh như nhau.

Đây là đầu ra được tạo bởi một bản dựng gỡ lỗi (tức là không có tối ưu hóa):

    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  

Đối với các kiểu do người dùng xác định , nơi bạn có thể quá tải operator +operator +=tùy thuộc vào việc triển khai tương ứng của chúng.


1
Không đúng trong mọi trường hợp. Tôi nhận thấy có thể nhanh hơn khi tải một địa chỉ bộ nhớ vào một thanh ghi, tăng nó và ghi lại nó hơn là tăng vị trí bộ nhớ trực tiếp (không sử dụng nguyên tử). Tôi sẽ thử đọc lại mã ...
James

Làm thế nào về các loại do người dùng xác định? Các trình biên dịch tốt sẽ tạo ra assembly tương đương, nhưng không có gì đảm bảo như vậy.
mfontanini

1
@LuchianGrigore Không, nếu alà 1 và xvolatiletrình biên dịch có thể tạo inc DWORD PTR [x]. Điều này là chậm.
James

1
@Chiffa không phụ thuộc vào trình biên dịch mà phụ thuộc vào nhà phát triển. Bạn có thể thực hiện operator +không cần làm gì và operator +=để tính số nguyên tố thứ 100000 rồi trả về. Tất nhiên, đó sẽ là một điều ngớ ngẩn để làm, nhưng nó hoàn toàn có thể.
Luchian Grigore

3
@ James: nếu chương trình của bạn là hợp lý với phần chênh lệch hiệu suất giữa ++xtemp = x + 1; x = temp;, sau đó có lẽ hầu hết nó phải được viết bằng lắp ráp hơn là c ++ ...
EmirCalabuch

11

Đúng! Nó nhanh hơn để viết, nhanh hơn để đọc và nhanh hơn để tìm ra, cho phần sau trong trường hợp xcó thể có tác dụng phụ. Vì vậy, nó nói chung nhanh hơn cho con người. Thời gian của con người nói chung đắt hơn nhiều so với thời gian của máy tính, vì vậy đó hẳn là những gì bạn đang thắc mắc. Đúng?


8

Nó thực sự phụ thuộc vào kiểu của x và a và việc thực hiện +. Đối với

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

trình biên dịch phải tạo một chữ T tạm thời để chứa giá trị của x + a trong khi nó đánh giá giá trị đó, sau đó nó có thể gán cho x. (Nó không thể sử dụng x hoặc a làm không gian làm việc trong quá trình hoạt động này).

Đối với x + = a, nó không cần tạm thời.

Đối với các loại tầm thường, không có sự khác biệt.


8

Sự khác biệt giữa x = x + ax += asố lượng công việc mà máy phải trải qua - một số trình biên dịch có thể (và thường làm) tối ưu hóa nó đi, nhưng thông thường, nếu chúng ta bỏ qua tối ưu hóa trong một thời gian, điều xảy ra là trong đoạn mã cũ, máy phải tra cứu giá trị chox hai lần, trong khi ở máy sau, việc tra cứu này chỉ cần thực hiện một lần.

Tuy nhiên, như tôi đã đề cập, ngày nay hầu hết các trình biên dịch đủ thông minh để phân tích lệnh và giảm bớt các lệnh máy cần thiết.

Tái bút: Câu trả lời đầu tiên về Stack Overflow!


6

Vì bạn đã gắn nhãn C ++ này, không có cách nào để biết từ hai câu lệnh bạn đã đăng. Bạn cần biết 'x' là gì (nó hơi giống câu trả lời '42'). Nếu xlà POD, thì nó sẽ không thực sự tạo ra nhiều khác biệt. Tuy nhiên, nếu xlà một lớp, có thể có quá tải chooperator +operator +=phương thức có thể có các hành vi khác nhau dẫn đến thời gian thực thi rất khác nhau.


6

Nếu bạn nói rằng +=bạn đang làm cho cuộc sống của trình biên dịch dễ dàng hơn rất nhiều. Để trình biên dịch nhận ra điều đó x = x+agiống với x += a, trình biên dịch phải

  • phân tích vế trái ( x) để đảm bảo nó không có tác dụng phụ và luôn tham chiếu đến cùng một giá trị l. Ví dụ, nó có thể là z[i], và nó phải đảm bảo rằng cả hai zikhông thay đổi.

  • phân tích vế phải ( x+a) và đảm bảo rằng nó là một phép tính tổng, và vế trái xảy ra một lần và chỉ một lần ở vế phải, mặc dù nó có thể được biến đổi, như trong z[i] = a + *(z+2*0+i).

Nếu ý bạn muốn thêm avào x, người viết trình biên dịch đánh giá cao điều đó khi bạn chỉ nói ý của bạn. Bằng cách đó, bạn không thực hiện phần trình biên dịch mà người viết của nó hy vọng rằng anh ấy / cô ấy đã loại bỏ được tất cả các lỗi và điều đó thực sự không làm cho cuộc sống của bạn dễ dàng hơn chút nào, trừ khi bạn thực sự không thể thoát ra của chế độ Fortran.


5

Đối với một ví dụ cụ thể, hãy tưởng tượng một kiểu số phức đơn giản:

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 */
}

Đối với trường hợp a = a + b, nó phải tạo thêm một biến tạm thời và sau đó sao chép nó.


Đây là một ví dụ rất hay, cho thấy cách thực hiện của 2 toán tử.
Grijesh Chauhan

5

Bạn đang hỏi sai câu hỏi.

Điều này không có khả năng thúc đẩy hiệu suất của một ứng dụng hoặc tính năng. Ngay cả khi nó là như vậy, cách để tìm hiểu là lập hồ sơ mã và biết nó ảnh hưởng đến bạn như thế nào. Thay vì lo lắng ở cấp độ này là nhanh hơn, điều quan trọng hơn là phải suy nghĩ về sự rõ ràng, đúng đắn và dễ đọc.

Điều này đặc biệt đúng khi bạn xem xét rằng, ngay cả khi đây là một yếu tố hiệu suất quan trọng, các trình biên dịch sẽ phát triển theo thời gian. Ai đó có thể tìm ra một cách tối ưu hóa mới và câu trả lời đúng hôm nay có thể trở thành sai vào ngày mai. Đó là một trường hợp kinh điển của việc tối ưu hóa quá sớm.

Điều này không có nghĩa là hiệu suất không quan trọng chút nào ... Chỉ là đó là cách tiếp cận sai lầm để đạt được mục tiêu hiệu quả của bạn. Cách tiếp cận đúng là sử dụng các công cụ lập hồ sơ để tìm hiểu xem mã của bạn đang thực sự sử dụng thời gian của nó ở đâu và do đó, bạn nên tập trung nỗ lực vào đâu.


Đúng vậy, nhưng nó chỉ là một câu hỏi cấp thấp, không phải là một bức tranh lớn "Khi nào tôi nên cân nhắc sự khác biệt như vậy".
Chiffa

1
Câu hỏi của OP là hoàn toàn chính đáng, thể hiện qua câu trả lời của những người khác (và số phiếu ủng hộ). Mặc dù chúng tôi nhận được quan điểm của bạn (hồ sơ trước, v.v.), nhưng chắc chắn là rất thú vị khi biết loại nội dung này - bạn có thực sự định hồ sơ từng tuyên bố nhỏ nhặt mà bạn viết, hồ sơ kết quả của mỗi quyết định bạn thực hiện không? Kể cả khi có những người trên SO đã nghiên cứu, lập hồ sơ, bóc tách vụ việc và có câu trả lời chung chung để cung cấp?

4

Tôi nghĩ rằng điều đó sẽ phụ thuộc vào máy và kiến ​​trúc của nó. Nếu kiến ​​trúc của nó cho phép định địa chỉ bộ nhớ gián tiếp, trình biên dịch MIGHT chỉ cần sử dụng mã này thay thế (để tối ưu hóa):

mov $[y],$ACC

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

Trong khi, i = i + ycó thể được dịch sang (không cần tối ưu hóa):

mov $[i],$ACC

mov $[y],$B 

iadd $ACC,$B

mov $B,[i]


Điều đó nói rằng, các biến chứng khác như if ilà một con trỏ trả về hàm, v.v. cũng nên được nghĩ đến. Hầu hết các trình biên dịch cấp sản xuất, bao gồm cả GCC, tạo ra cùng một mã cho cả hai câu lệnh (nếu chúng là số nguyên).


2

Không, cả hai cách đều giống nhau.


10
Không nếu đó là kiểu do người dùng xác định với các toán tử quá tải.
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.