Trình biên dịch C ++ có loại bỏ / tối ưu hóa các dấu ngoặc đơn vô dụng không?


19

Mã sẽ

int a = ((1 + 2) + 3); // Easy to read

chạy chậm hơn

int a = 1 + 2 + 3; // (Barely) Not quite so easy to read

hoặc là các trình biên dịch hiện đại đủ thông minh để loại bỏ / tối ưu hóa các dấu ngoặc đơn "vô dụng".

Nó có vẻ như là một mối quan tâm tối ưu hóa rất nhỏ, nhưng chọn C ++ trên C # / Java / ... là tất cả về tối ưu hóa (IMHO).


9
Tôi nghĩ C # và Java cũng sẽ tối ưu hóa điều đó. Tôi tin rằng khi họ phân tích cú pháp và tạo AST, họ sẽ loại bỏ những thứ vô dụng rõ ràng như thế.
Farid Nouri Neshat

5
Tất cả mọi thứ tôi đã đọc đều chỉ ra việc biên dịch JIT dễ dàng đưa ra quá trình biên dịch trước thời hạn để kiếm tiền, vì vậy, đó không phải là một cuộc tranh luận rất hấp dẫn. Bạn đưa lên lập trình trò chơi - lý do thực sự để ưu tiên biên dịch trước thời hạn là điều đó có thể dự đoán được - với trình biên dịch JIT bạn không bao giờ biết khi nào trình biên dịch sẽ khởi động và thử bắt đầu biên dịch mã. Nhưng tôi sẽ lưu ý rằng việc biên dịch trước thời gian cho mã gốc không loại trừ lẫn nhau với bộ sưu tập rác, xem ví dụ Standard ML và D. Và tôi đã thấy các lập luận thuyết phục rằng việc thu gom rác hiệu quả hơn ...
Doval

7
... Hơn RAII và các con trỏ thông minh, do đó, vấn đề đi theo con đường được đánh bại tốt (C ++) so với con đường tương đối chưa đọc về lập trình trò chơi bằng các ngôn ngữ đó. Tôi cũng sẽ lưu ý rằng việc lo lắng về dấu ngoặc đơn là điên rồ - Tôi thấy bạn đến từ đâu, nhưng đó là một tối ưu hóa vi mô lố bịch. Sự lựa chọn cấu trúc dữ liệu và thuật toán trong chương trình của bạn chắc chắn sẽ chi phối hiệu suất, chứ không phải tầm thường như vậy.
Doval

6
Ừm ... Chính xác thì bạn đang mong đợi loại tối ưu nào? Nếu bạn đang nói về phân tích tĩnh, trong hầu hết các ngôn ngữ tôi biết, điều này sẽ được thay thế bằng kết quả được biết đến tĩnh (triển khai dựa trên LLVM thậm chí thực thi điều này, AFAIK). Nếu bạn đang nói về thứ tự thực hiện, điều đó không thành vấn đề, vì đó là hoạt động tương tự và không có tác dụng phụ. Ngoài ra cần hai toán hạng. Và nếu bạn đang sử dụng điều này để so sánh C ++, Java và C # về hiệu suất, có vẻ như bạn không có ý tưởng rõ ràng về tối ưu hóa là gì và cách chúng hoạt động, vì vậy bạn nên tập trung vào việc học thay thế.
Theodoros Chatzigiannakis

5
Tôi tự hỏi tại sao a) bạn coi biểu thức được ngoặc đơn dễ đọc hơn (với tôi nó trông xấu xí, dễ gây hiểu lầm (tại sao họ nhấn mạnh thứ tự đặc biệt này? Không nên nhận thức ở đây?) Và vụng về) b) tại sao bạn nghĩ mà không có Dấu ngoặc đơn có thể hoạt động tốt hơn (phân tích cú pháp rõ ràng dễ dàng hơn cho máy hơn là lý do về sự cố định của người vận hành. Như Marc van Leuwen nói mặc dù, điều này hoàn toàn không ảnh hưởng đến thời gian chạy).
rẽ trái

Câu trả lời:


87

Trình biên dịch không thực sự bao giờ chèn hoặc loại bỏ dấu ngoặc; nó chỉ tạo một cây phân tích cú pháp (trong đó không có dấu ngoặc đơn) tương ứng với biểu thức của bạn và khi làm như vậy nó phải tôn trọng dấu ngoặc đơn bạn đã viết. Nếu bạn hoàn toàn ngoặc đơn biểu hiện của bạn thì nó cũng sẽ ngay lập tức rõ ràng cho người đọc con người mà cây phân tích đó là gì; Nếu bạn đi đến cực đoan trong việc đặt dấu ngoặc đơn dư thừa một cách trắng trợn như int a = (((0)));sau đó bạn sẽ gây ra một số căng thẳng vô ích đối với các tế bào thần kinh của người đọc trong khi cũng lãng phí một số chu kỳ trong trình phân tích cú pháp, mà không thay đổi cây phân tích kết quả (và do đó mã được tạo ) một chút nhỏ nhất.

Nếu bạn không viết bất kỳ dấu ngoặc đơn nào, thì trình phân tích cú pháp vẫn phải thực hiện công việc tạo cây phân tích cú pháp và các quy tắc về quyền ưu tiên của toán tử và tính kết hợp cho nó biết chính xác cây phân tích mà nó phải xây dựng. Bạn có thể xem các quy tắc đó như nói với trình biên dịch (dấu ngoặc) dấu ngoặc đơn cần chèn vào mã của bạn, mặc dù trình phân tích cú pháp thực sự không bao giờ xử lý dấu ngoặc trong trường hợp này: nó chỉ được xây dựng để tạo cùng một cây phân tích như thể dấu ngoặc đơn đã có mặt ở một số nơi. Nếu bạn đặt dấu ngoặc đơn ở chính xác những vị trí đó, như trong int a = (1+2)+3;(tính kết hợp của +bên trái) thì trình phân tích cú pháp sẽ đến cùng một kết quả bằng một tuyến đường hơi khác. Nếu bạn đặt trong ngoặc đơn khác nhau như trongint a = 1+(2+3);sau đó, bạn đang buộc một cây phân tích khác nhau, điều này có thể sẽ tạo ra các mã khác nhau (mặc dù có thể không, vì trình biên dịch có thể áp dụng các phép biến đổi sau khi xây dựng cây phân tích, miễn là hiệu ứng thực thi mã kết quả sẽ không bao giờ khác nó). Giả sử có một sự khác biệt trong mã khởi động lại, nói chung không có gì có thể nói là hiệu quả hơn; Tất nhiên, điểm quan trọng nhất là hầu hết các cây phân tích cú pháp không cho các biểu thức tương đương về mặt toán học, vì vậy việc so sánh tốc độ thực hiện của chúng nằm bên cạnh điểm: người ta chỉ nên viết biểu thức cho kết quả đúng.

Vì vậy, kết quả cuối cùng là: sử dụng dấu ngoặc đơn khi cần cho tính chính xác và như mong muốn cho khả năng đọc; nếu dư thừa chúng hoàn toàn không ảnh hưởng đến tốc độ thực hiện (và ảnh hưởng không đáng kể đến thời gian biên dịch).

Và không ai trong số này có bất cứ điều gì liên quan đến tối ưu hóa , xuất hiện sau khi cây phân tích được xây dựng, vì vậy nó không thể biết cây phân tích được xây dựng như thế nào. Điều này áp dụng mà không thay đổi từ trình biên dịch lâu đời nhất và ngu ngốc nhất sang trình biên dịch thông minh nhất và hiện đại nhất. Chỉ trong một ngôn ngữ được giải thích (trong đó "thời gian biên dịch" và "thời gian thực hiện" là trùng khớp) mới có thể có một hình phạt cho dấu ngoặc thừa, nhưng ngay cả khi đó tôi nghĩ rằng hầu hết các ngôn ngữ như vậy được tổ chức để ít nhất là giai đoạn phân tích cú pháp chỉ được thực hiện một lần cho mỗi câu lệnh (lưu trữ một số dạng được phân tích trước của nó để thực thi).


s / oldes / cũ nhất /. Câu trả lời hay, +1.
David Conrad

23
Tổng số cảnh báo của nitpick: "Câu hỏi không được đặt nhiều lắm." - Tôi không đồng ý, lấy "well put" có nghĩa là "gợi ý rõ ràng khoảng trống kiến ​​thức nào mà người hỏi muốn điền vào". Về bản chất, câu hỏi là "tối ưu hóa cho X, chọn A hoặc B, và tại sao? Điều gì xảy ra bên dưới?", Mà ít nhất với tôi gợi ý rất rõ khoảng cách kiến ​​thức là gì. Lỗ hổng trong câu hỏi, mà bạn chỉ ra và giải quyết rất đúng, là nó được xây dựng trên một mô hình tinh thần bị lỗi.
Jonas Kölker

Đối với a = b + c * d;, a = b + (c * d);sẽ là [vô hại] dấu ngoặc đơn thừa. Nếu họ giúp bạn làm cho mã dễ đọc hơn, tốt thôi. a = (b + c) * d;sẽ là dấu ngoặc đơn không thừa - chúng thực sự thay đổi cây phân tích kết quả và đưa ra một kết quả khác. Hoàn toàn hợp pháp để làm (trên thực tế, cần thiết), nhưng chúng không giống như nhóm mặc định ngụ ý.
Phil Perry

1
@OrangeDog đúng, nhận xét xấu hổ của Ben đã đưa ra một vài người thích tuyên bố VM nhanh hơn bản địa.
gbjbaanb

1
@ JonasKölker: Câu mở đầu của tôi thực sự đề cập đến câu hỏi như được đặt trong tiêu đề: người ta không thể thực sự trả lời một câu hỏi về việc trình biên dịch chèn hoặc loại bỏ dấu ngoặc đơn, vì đó là dựa trên quan niệm sai về cách trình biên dịch hoạt động. Nhưng tôi đồng ý nó khá rõ ràng khoảng cách kiến ​​thức cần được giải quyết.
Marc van Leeuwen

46

Các dấu ngoặc đơn ở đó chỉ vì lợi ích của bạn - không phải trình biên dịch. Trình biên dịch sẽ tạo mã máy chính xác để thể hiện câu lệnh của bạn.

FYI, trình biên dịch đủ thông minh để tối ưu hóa nó hoàn toàn nếu có thể. Trong ví dụ của bạn, điều này sẽ được chuyển thành int a = 6;thời gian biên dịch.


9
Hoàn toàn - gắn bao nhiêu dấu ngoặc đơn tùy thích và để trình biên dịch thực hiện công việc khó khăn để tìm ra những gì bạn muốn :)
gbjbaanb

23
@Serge lập trình thực sự thiên về khả năng đọc mã của bạn hơn là về hiệu suất. Bạn sẽ ghét chính mình vào năm tới khi bạn cần gỡ lỗi một sự cố và bạn chỉ có mã "tối ưu hóa" để đi qua.
ratchet freak

1
@ratchetfreak, bạn nói đúng nhưng tôi cũng biết cách nhận xét mã của mình. int a = 6; // = (1 + 2) + 3
Serge

20
@Serge Tôi biết rằng sẽ không giữ được sau một năm điều chỉnh, trong thời gian bình luận và mã sẽ không đồng bộ và sau đó bạn kết thúc vớiint a = 8;// = 2*3 + 5
ratchet freak

21
hoặc từ www.thed int five = 7; //HR made us change this to six...
Dailywtf.com

23

Câu trả lời cho câu hỏi bạn thực sự hỏi là không, nhưng câu trả lời cho câu hỏi bạn muốn hỏi là có. Thêm dấu ngoặc đơn không làm chậm mã.

Bạn đã hỏi một câu hỏi về tối ưu hóa, nhưng dấu ngoặc đơn không liên quan gì đến tối ưu hóa. Trình biên dịch áp dụng nhiều kỹ thuật tối ưu hóa với mục đích cải thiện kích thước hoặc tốc độ của mã được tạo (đôi khi cả hai). Ví dụ: nó có thể lấy biểu thức A ^ 2 (A bình phương) và thay thế nó bằng A x A (A nhân với chính nó) nếu điều đó nhanh hơn. Câu trả lời ở đây là không, trình biên dịch không có gì khác trong giai đoạn tối ưu hóa tùy thuộc vào việc có dấu ngoặc đơn hay không.

Tôi nghĩ bạn muốn hỏi liệu trình biên dịch có còn tạo cùng một mã hay không nếu bạn thêm các dấu ngoặc đơn không cần thiết vào một biểu thức, ở những nơi mà bạn nghĩ có thể cải thiện khả năng đọc. Nói cách khác, nếu bạn thêm dấu ngoặc đơn là trình biên dịch đủ thông minh để đưa chúng ra ngoài một lần nữa thay vì bằng cách nào đó tạo ra mã kém hơn. Câu trả lời là có, luôn luôn.

Hãy để tôi nói điều đó một cách cẩn thận. Nếu bạn thêm dấu ngoặc đơn vào một biểu thức hoàn toàn không cần thiết (không ảnh hưởng gì đến ý nghĩa hoặc thứ tự đánh giá của một biểu thức), trình biên dịch sẽ âm thầm loại bỏ chúng và tạo cùng một mã.

Tuy nhiên, tồn tại một số biểu thức nhất định trong đó các dấu ngoặc đơn không cần thiết thực sự sẽ thay đổi thứ tự đánh giá của một biểu thức và trong trường hợp đó, trình biên dịch sẽ tạo mã để thực hiện những gì bạn thực sự đã viết, có thể khác với những gì bạn dự định. Đây là một ví dụ. Đừng làm điều này!

short int a = 30001, b = 30002, c = 30003;
int d = -a + b + c;    // ok
int d = (-a + b) + c;  // ok, same code
int d = (-a + b + c);  // ok, same code
int d = ((((-a + b)) + c));  // ok, same code
int d = -a + (b + c);  // undefined behaviour, different code

Vì vậy, hãy thêm dấu ngoặc đơn nếu bạn muốn, nhưng hãy chắc chắn rằng chúng thực sự không cần thiết!

Tôi chưa bao giờ làm. Có nguy cơ lỗi không có lợi ích thực sự.


Lưu ý: hành vi không dấu xảy ra khi một biểu thức số nguyên đã ký ước tính giá trị nằm ngoài phạm vi mà nó có thể biểu thị, trong trường hợp này là -32767 đến +32767. Đây là một chủ đề phức tạp, nằm ngoài phạm vi cho câu trả lời này.


Hành vi không xác định trong dòng cuối cùng là bởi vì một ký hiệu ngắn chỉ có 15 bit sau dấu hiệu, vì vậy kích thước tối đa 32767, phải không? Trong ví dụ tầm thường đó, trình biên dịch sẽ cảnh báo về lỗi tràn, phải không? +1 cho một ví dụ truy cập, một trong hai cách. Nếu chúng là tham số cho hàm, bạn sẽ không nhận được cảnh báo. Ngoài ra, nếu athực sự có thể không được ký, dẫn đến tính toán của bạn -a + bcũng có thể dễ dàng bị tràn, nếu alà âm và bdương.
Patrick M

@PatrickM: xem chỉnh sửa. Hành vi không xác định có nghĩa là trình biên dịch có thể làm những gì nó thích, bao gồm đưa ra cảnh báo hoặc không. Số học không được chỉ định không tạo ra UB, nhưng bị giảm modulo, sức mạnh cao hơn tiếp theo của hai.
david.pfx

Biểu thức (b+c)trong dòng cuối cùng sẽ thúc đẩy các đối số của nó int, vì vậy trừ khi trình biên dịch xác định intlà 16 bit (vì nó cổ hoặc nhắm vào một vi điều khiển nhỏ) thì dòng cuối cùng sẽ hoàn toàn hợp pháp.
supercat

@supercat: Tôi không nghĩ vậy. Loại phổ biến và loại kết quả nên ngắn int. Nếu đó không phải là điều gì đó từng được hỏi, có lẽ bạn muốn đăng câu hỏi?
david.pfx

@ david.pfx: Các quy tắc của quảng cáo số học C khá rõ ràng: mọi thứ nhỏ hơn intđược thăng cấp inttrừ khi loại đó không thể biểu thị tất cả các giá trị của nó trong trường hợp được quảng cáo unsigned int. Trình biên dịch có thể bỏ qua các chương trình khuyến mãi nếu tất cả các hành vi được xác định sẽ giống như khi bao gồm các chương trình khuyến mãi . Trên máy có các loại 16 bit hoạt động như một vòng đại số trừu tượng, (a + b) + c và a + (b + c) sẽ tương đương. intTuy nhiên, nếu là loại 16 bit bị kẹt khi tràn, thì sẽ có trường hợp một trong các biểu thức ...
supercat

7

Chân đế chỉ có ở đó để bạn thao tác thứ tự ưu tiên toán tử. Sau khi được biên dịch, dấu ngoặc không còn tồn tại vì thời gian chạy không cần chúng. Quá trình biên dịch sẽ loại bỏ tất cả các dấu ngoặc, dấu cách và đường cú pháp khác mà bạn và tôi cần và thay đổi tất cả các toán tử thành một thứ đơn giản hơn nhiều để máy tính thực thi.

Vì vậy, nơi bạn và tôi có thể thấy ...

  • "int a = ((1 + 2) + 3);"

... một trình biên dịch có thể phát ra thứ gì đó giống như thế này:

  • Char [1] :: "a"
  • Int32 :: DeclareStackVariable ()
  • Int32 :: 0x00000001
  • Int32 :: 0x00000002
  • Int32 :: Thêm ()
  • Int32 :: 0x00000003
  • Int32 :: Thêm ()
  • Int32 :: AssignToVariable ()
  • void :: DiscardResult ()

Chương trình được thực hiện bằng cách bắt đầu từ đầu và thực hiện lần lượt từng lệnh.
Ưu tiên của nhà khai thác bây giờ là "đến trước, phục vụ trước".
Tất cả mọi thứ được gõ mạnh, bởi vì trình biên dịch đã làm việc tất cả những điều đó trong khi nó đang xé cú pháp ban đầu.

OK, không có gì giống như những thứ mà bạn và tôi đối phó, nhưng sau đó chúng tôi sẽ không chạy nó!


4
Không có một trình biên dịch C ++ nào sẽ tạo ra bất cứ thứ gì thậm chí từ xa như thế này. Nói chung, họ sản xuất mã CPU thực tế và lắp ráp cũng không giống như vậy.
MSalters

3
mục đích ở đây là để chứng minh sự khác biệt về cấu trúc giữa mã như được viết và đầu ra được biên dịch. ngay cả ở đây, hầu hết mọi người sẽ không thể đọc được mã máy hoặc lắp ráp thực tế
DHall

@MSalters Nitpicking clang sẽ phát ra 'một cái gì đó tương tự' nếu bạn coi LLVM ISA là 'một cái gì đó tương tự' (đó là SSA không dựa trên stack). Vì có thể viết phụ trợ JVM cho LLVM và JVM ISA (AFAIK) là clang dựa trên ngăn xếp-> llvm-> JVM sẽ trông rất giống nhau.
Maciej Piechotka

Tôi không nghĩ rằng LLVM ISA có khả năng xác định các biến ngăn xếp tên bằng cách sử dụng chuỗi ký tự thời gian chạy (chỉ hai hướng dẫn đầu tiên). Đây là nghiêm túc trộn lẫn giữa thời gian chạy và thời gian biên dịch. Sự khác biệt quan trọng bởi vì câu hỏi này là chính xác về sự nhầm lẫn đó.
MSalters

6

Phụ thuộc nếu nó là dấu phẩy động hay không:

  • Trong phép cộng số học dấu phẩy động không liên kết nên trình tối ưu hóa không thể sắp xếp lại các hoạt động (trừ khi bạn thêm công tắc trình biên dịch fastmath).

  • Trong các hoạt động số nguyên họ có thể được sắp xếp lại.

Trong ví dụ của bạn, cả hai sẽ chạy cùng một lúc bởi vì chúng sẽ biên dịch thành cùng một mã chính xác (bổ sung được đánh giá từ trái sang phải).

tuy nhiên ngay cả java và C # cũng có thể tối ưu hóa nó, họ sẽ chỉ làm điều đó khi chạy.


+1 để đưa lên rằng các hoạt động điểm nổi không liên quan.
Doval

Trong ví dụ của câu hỏi, dấu ngoặc đơn không làm thay đổi tính kết hợp mặc định (bên trái), vì vậy điểm này là moot.
Marc van Leeuwen

1
Về câu cuối cùng, tôi không nghĩ vậy. Trong cả java a và c #, trình biên dịch sẽ tạo ra mã byte / IL được tối ưu hóa. Thời gian chạy không bị ảnh hưởng.
Stefano Altieri

IL không hoạt động trên các biểu thức của loại này, các hướng dẫn sẽ lấy một số giá trị nhất định từ ngăn xếp và trả về một số giá trị nhất định (thường là 0 hoặc 1) cho ngăn xếp. Nói về loại điều này được tối ưu hóa trong thời gian chạy trong C # là vô nghĩa.
Jon Hanna

6

Trình biên dịch C ++ điển hình dịch sang mã máy chứ không phải chính C ++ . Nó loại bỏ các parens vô dụng, vâng, bởi vì tại thời điểm nó được thực hiện, không có parens nào cả. Mã máy không hoạt động theo cách đó.



1

Không, nhưng có, nhưng có thể, nhưng có thể theo cách khác, nhưng không.

Như mọi người đã chỉ ra, (giả sử một ngôn ngữ trong đó phép cộng là liên kết trái, chẳng hạn như C, C ++, C # hoặc Java) thì biểu thức ((1 + 2) + 3)hoàn toàn tương đương với 1 + 2 + 3. Chúng là những cách khác nhau để viết một cái gì đó trong mã nguồn, điều đó sẽ không ảnh hưởng đến mã máy hoặc mã byte kết quả.

Dù bằng cách nào, kết quả sẽ là một hướng dẫn, ví dụ như thêm hai thanh ghi và sau đó thêm một thứ ba hoặc lấy hai giá trị từ một ngăn xếp, thêm nó, đẩy nó trở lại, sau đó lấy nó và một cái khác và thêm chúng, hoặc thêm ba thanh ghi vào một thao tác đơn lẻ hoặc một số cách khác để tính tổng ba số tùy thuộc vào mức nào hợp lý nhất ở cấp độ tiếp theo (mã máy hoặc mã byte). Trong trường hợp mã byte, đến lượt nó sẽ có khả năng tái cấu trúc tương tự trong đó, ví dụ như IL tương đương với điều này (sẽ là một loạt các tải cho một ngăn xếp và xuất hiện các cặp để thêm và sau đó đẩy lùi kết quả) sẽ không dẫn đến một bản sao trực tiếp của logic đó ở cấp mã máy, nhưng một cái gì đó hợp lý hơn cho máy đang đề cập.

Nhưng có một cái gì đó nhiều hơn cho câu hỏi của bạn.

Trong trường hợp của bất kỳ trình biên dịch C, C ++, Java hoặc C # lành mạnh nào, tôi đều mong đợi kết quả của cả hai câu lệnh bạn đưa ra đều có kết quả chính xác như sau:

int a = 6;

Tại sao mã kết quả phải lãng phí thời gian làm toán trên chữ? Không có thay đổi nào về trạng thái của chương trình sẽ ngăn chặn kết quả của 1 + 2 + 3sự tồn tại 6, vì vậy đó là những gì sẽ xảy ra trong mã đang được thực thi. Thật vậy, có lẽ thậm chí là không (tùy thuộc vào những gì bạn làm với 6 điều đó, có lẽ chúng ta có thể vứt bỏ toàn bộ; và thậm chí C # với triết lý "không tối ưu hóa nhiều, vì dù sao thì jitter cũng sẽ tối ưu hóa điều này" tương đương int a = 6hoặc chỉ cần ném toàn bộ đi là không cần thiết).

Điều này mặc dù dẫn chúng tôi đến một phần mở rộng có thể của câu hỏi của bạn mặc dù. Hãy xem xét những điều sau đây:

int a = (b - 2) / 2;
/* or */
int a = (b / 2)--;

int c;
if(d < 100)
  c = 0;
else
  c = d * 31;
/* or */
int c = d < 100 ? 0 : d * 32 - d
/* or */
int c = d < 100 && d * 32 - d;
/* or */
int c = (d < 100) * (d * 32 - d);

(Lưu ý, hai ví dụ cuối cùng này không hợp lệ C #, trong khi mọi thứ khác ở đây là và chúng hợp lệ trong C, C ++ và Java.)

Ở đây một lần nữa, chúng tôi chính xác mã tương đương về đầu ra. Vì chúng không phải là biểu thức không đổi, chúng sẽ không được tính vào thời gian biên dịch. Có thể là một hình thức nhanh hơn một hình thức khác. Cái nào nhanh hơn? Điều đó phụ thuộc vào bộ xử lý và có lẽ vào một số khác biệt khá tùy tiện về trạng thái (đặc biệt là nếu nhanh hơn, nó không có khả năng nhanh hơn nhiều).

Và chúng không hoàn toàn không liên quan đến câu hỏi của bạn, vì chúng chủ yếu là về sự khác biệt theo thứ tự mà một cái gì đó được thực hiện về mặt khái niệm .

Trong mỗi người trong số họ, có một lý do để nghi ngờ rằng người này có thể nhanh hơn người kia. Các phân rã đơn có thể có một hướng dẫn chuyên biệt, vì vậy (b / 2)--thực sự có thể nhanh hơn (b - 2) / 2. d * 32có lẽ có thể được sản xuất nhanh hơn bằng cách biến nó thành d << 5quá d * 32 - dnhanh hơn d * 31. Sự khác biệt giữa hai cuối cùng là đặc biệt thú vị; một cho phép một số xử lý được bỏ qua trong một số trường hợp, nhưng cái khác tránh khả năng dự đoán sai nhánh.

Vì vậy, điều này khiến chúng ta có hai câu hỏi: 1. Cái này có thực sự nhanh hơn cái kia không? 2. Trình biên dịch sẽ chuyển đổi chậm hơn thành nhanh hơn?

Và câu trả lời là 1. Nó phụ thuộc. 2. Có thể.

Hoặc để mở rộng, nó phụ thuộc bởi vì nó phụ thuộc vào bộ xử lý được đề cập. Chắc chắn đã có các bộ xử lý tồn tại trong đó mã máy tương đương với mã này sẽ nhanh hơn mã máy tương đương với mã máy khác. Trong suốt lịch sử của điện toán điện tử, cũng không có cái nào luôn nhanh hơn cả (yếu tố dự đoán sai nhánh nói riêng không liên quan đến nhiều người khi CPU không có đường ống phổ biến hơn).

Và có thể, vì có một loạt các tối ưu hóa khác nhau mà trình biên dịch (và jitter, và script-engine) sẽ làm, và trong khi một số có thể được ủy quyền trong một số trường hợp nhất định, chúng ta thường có thể tìm thấy một số đoạn mã tương đương logic mà ngay cả trình biên dịch ngây thơ nhất cũng có kết quả chính xác và một số đoạn mã tương đương logic, trong đó ngay cả trình biên dịch phức tạp nhất cũng tạo ra mã nhanh hơn cho mã kia (ngay cả khi chúng ta phải viết một cái gì đó hoàn toàn bệnh hoạn chỉ để chứng minh quan điểm của chúng tôi).

Nó có vẻ như là một mối quan tâm tối ưu hóa rất nhỏ,

Không. Ngay cả với những khác biệt phức tạp hơn những gì tôi đưa ra ở đây, có vẻ như một mối quan tâm hoàn toàn trong vài phút không liên quan gì đến tối ưu hóa. Nếu bất cứ điều gì, đó là một vấn đề bi quan vì bạn nghi ngờ rằng khó đọc hơn ((1 + 2) + 3có thể chậm hơn dễ đọc hơn 1 + 2 + 3.

nhưng chọn C ++ trên C # / Java / ... tất cả là về tối ưu hóa (IMHO).

Nếu đó thực sự là việc chọn C ++ trên C # hoặc Java là "tất cả về" thì tôi nói mọi người nên ghi bản sao Stroustrup và ISO / IEC 14882 của họ và giải phóng không gian của trình biên dịch C ++ của họ để dành chỗ cho một số MP3 hoặc thứ gì đó.

Các ngôn ngữ này có lợi thế khác nhau so với nhau.

Một trong số đó là C ++ vẫn thường nhanh hơn và nhẹ hơn khi sử dụng bộ nhớ. Vâng, có những ví dụ trong đó C # và / hoặc Java nhanh hơn và / hoặc sử dụng bộ nhớ suốt đời ứng dụng tốt hơn và chúng trở nên phổ biến hơn khi các công nghệ liên quan cải thiện, nhưng chúng ta vẫn có thể mong đợi chương trình trung bình được viết bằng C ++ một tệp thực thi nhỏ hơn thực hiện công việc của nó nhanh hơn và sử dụng ít bộ nhớ hơn tương đương với một trong hai ngôn ngữ đó.

Đây không phải là tối ưu hóa.

Tối ưu hóa đôi khi được sử dụng để có nghĩa là "làm cho mọi thứ đi nhanh hơn". Điều đó có thể hiểu được, bởi vì thường khi chúng ta thực sự nói về "tối ưu hóa", chúng ta thực sự đang nói về việc làm cho mọi thứ trở nên nhanh hơn, và vì vậy người ta đã trở thành một người viết tắt cho người khác và tôi thừa nhận tôi đã sử dụng sai từ đó theo cách của mình.

Từ chính xác để "làm mọi thứ nhanh hơn" không phải là tối ưu hóa . Từ chính xác ở đây là cải tiến . Nếu bạn thực hiện một thay đổi cho một chương trình và sự khác biệt có ý nghĩa duy nhất là bây giờ nó nhanh hơn, nó không được tối ưu hóa theo bất kỳ cách nào, nó chỉ tốt hơn.

Tối ưu hóa là khi chúng tôi thực hiện một cải tiến liên quan đến một khía cạnh cụ thể và / hoặc trường hợp cụ thể. Ví dụ phổ biến là:

  1. Bây giờ nhanh hơn cho một trường hợp sử dụng, nhưng chậm hơn cho một trường hợp khác.
  2. Bây giờ nhanh hơn, nhưng sử dụng nhiều bộ nhớ hơn.
  3. Bây giờ nó nhẹ hơn về bộ nhớ, nhưng chậm hơn.
  4. Bây giờ nhanh hơn, nhưng khó bảo trì hơn.
  5. Bây giờ dễ dàng hơn để duy trì, nhưng chậm hơn.

Những trường hợp như vậy sẽ được biện minh nếu, ví dụ:

  1. Trường hợp sử dụng nhanh hơn là phổ biến hơn hoặc bị cản trở nghiêm trọng hơn để bắt đầu.
  2. Chương trình chậm đến mức không thể chấp nhận được và chúng tôi có rất nhiều RAM miễn phí.
  3. Chương trình bị đình trệ vì sử dụng quá nhiều RAM nên đã mất nhiều thời gian hơn so với thực hiện xử lý siêu nhanh.
  4. Chương trình chậm đến mức không thể chấp nhận được, và mã khó hiểu hơn được ghi lại tốt và tương đối ổn định.
  5. Chương trình vẫn nhanh chóng chấp nhận được và cơ sở mã dễ hiểu hơn rẻ hơn để duy trì và cho phép các cải tiến khác được thực hiện dễ dàng hơn.

Nhưng, những trường hợp như vậy cũng sẽ không được chứng minh trong các tình huống khác: Mã không được cải thiện bằng một thước đo chất lượng không thể sai lầm tuyệt đối, nó được làm tốt hơn trong một khía cạnh cụ thể giúp nó phù hợp hơn cho việc sử dụng cụ thể; tối ưu hóa.

Và sự lựa chọn ngôn ngữ có ảnh hưởng ở đây, bởi vì tốc độ, việc sử dụng bộ nhớ và khả năng đọc đều có thể bị ảnh hưởng bởi nó, nhưng vì vậy có thể tương thích với các hệ thống khác, tính sẵn có của thư viện, thời gian chạy, sự trưởng thành của các thời gian chạy trên một hệ điều hành nhất định (vì tội lỗi của tôi, bằng cách nào đó tôi đã kết thúc việc có Linux và Android là hệ điều hành yêu thích và C # là ngôn ngữ yêu thích của tôi, và trong khi Mono thì tuyệt vời, nhưng tôi vẫn gặp phải vấn đề này khá nhiều).

Nói "chọn C ++ trên C # / Java / ... là tất cả về tối ưu hóa" chỉ có ý nghĩa nếu bạn nghĩ C ++ thực sự hấp dẫn, bởi vì tối ưu hóa là về "tốt hơn mặc dù ..." không "tốt hơn". Nếu bạn nghĩ rằng C ++ tốt hơn bất chấp chính nó, thì điều cuối cùng bạn cần là lo lắng về những phút cực nhỏ có thể xảy ra. Thật vậy, có lẽ bạn tốt hơn hết nên từ bỏ nó; tin tặc hạnh phúc là một chất lượng để tối ưu hóa quá!

Tuy nhiên, nếu bạn có xu hướng nói "Tôi yêu C ++ và một trong những điều tôi yêu thích về nó là vắt kiệt thêm chu kỳ", thì đó lại là một vấn đề khác. Vẫn còn một trường hợp rằng các vi lệnh chỉ có giá trị nếu chúng có thể là một thói quen phản xạ (nghĩa là cách bạn có xu hướng mã hóa tự nhiên sẽ nhanh hơn thường xuyên hơn là chậm hơn). Mặt khác, chúng thậm chí không được tối ưu hóa sớm, chúng là sự bi quan sớm khiến mọi thứ trở nên tồi tệ hơn.


0

Các dấu ngoặc đơn ở đó để báo cho trình biên dịch trong đó các biểu thức thứ tự sẽ được ước tính. Đôi khi chúng vô dụng (ngoại trừ việc chúng cải thiện hoặc làm xấu đi khả năng đọc), bởi vì chúng chỉ định thứ tự sẽ được sử dụng bằng mọi cách. Đôi khi họ thay đổi thứ tự. Trong

int a = 1 + 2 + 3;

thực tế mọi ngôn ngữ tồn tại đều có một quy tắc rằng tổng được đánh giá bằng cách thêm 1 + 2, sau đó thêm kết quả cộng 3. Nếu bạn đã viết

int a = 1 + (2 + 3);

sau đó dấu ngoặc đơn sẽ buộc một thứ tự khác: Đầu tiên thêm 2 + 3, sau đó thêm 1 cộng với kết quả. Ví dụ về dấu ngoặc đơn của bạn tạo ra cùng một thứ tự sẽ được tạo ra bằng mọi cách. Bây giờ trong ví dụ này, thứ tự các hoạt động hơi khác nhau, nhưng cách thức bổ sung số nguyên hoạt động, kết quả là như nhau. Trong

int a = 10 - (5 - 4);

các dấu ngoặc là quan trọng; để chúng ra sẽ thay đổi kết quả từ 9 thành 1.

Sau khi trình biên dịch đã xác định các hoạt động được thực hiện theo thứ tự nào, dấu ngoặc đơn hoàn toàn bị lãng quên. Tất cả những gì trình biên dịch ghi nhớ tại thời điểm này là hoạt động để thực hiện theo thứ tự nào. Vì vậy, thực sự không có gì mà trình biên dịch có thể tối ưu hóa ở đây, các dấu ngoặc đơn đã biến mất .


practically every language in existence; ngoại trừ APL: Thử (tại đây) [tryapl.org] nhập (1-2)+3(2), 1-(2+3)(-4) và 1-2+3(cũng -4).
tomsmeding

0

Tôi đồng ý với hầu hết những gì đã nói, tuy nhiên, điều vượt trội ở đây là các dấu ngoặc đơn ở đó để ép buộc thứ tự hoạt động của điều mà trình biên dịch hoàn toàn làm. Vâng, nó tạo ra mã máy, nhưng đó không phải là vấn đề và không phải là những gì đang được yêu cầu.

Các dấu ngoặc đơn thực sự đã biến mất: như đã nói, chúng không phải là một phần của mã máy, là số và không phải là một thứ khác. Mã hội không phải là mã máy, nó có thể đọc được nửa người và chứa các hướng dẫn theo tên - không phải opcode. Máy chạy những gì được gọi là opcodes - biểu diễn số của ngôn ngữ lắp ráp.

Các ngôn ngữ như Java rơi vào khu vực giữa khi chúng chỉ biên dịch một phần trên máy tạo ra chúng. Chúng được biên dịch thành mã cụ thể của máy trên máy chạy chúng, nhưng điều đó không tạo ra sự khác biệt cho câu hỏi này - dấu ngoặc đơn vẫn không còn sau lần biên dịch đầu tiên.


1
Tôi không chắc chắn rằng điều này trả lời câu hỏi. Các đoạn hỗ trợ gây nhầm lẫn nhiều hơn là hữu ích. Trình biên dịch Java có liên quan đến trình biên dịch C ++ như thế nào?
Adam Zuckerman

OP hỏi rằng các dấu ngoặc đơn đã biến mất, tôi nói rằng chúng đã được giải thích thêm rằng mã thực thi chỉ là các số đại diện cho các mã. Java đã được đưa ra trong một câu trả lời khác. Tôi nghĩ rằng nó trả lời câu hỏi tốt, nhưng đó chỉ là ý kiến ​​của tôi. Cảm ơn vì đã trả lời.
jinzai

3
Dấu ngoặc đơn không bắt buộc "thứ tự hoạt động". Họ thay đổi quyền ưu tiên. Vì vậy, trong a = f() + (g() + h());, trình biên dịch là miễn phí để gọi f, ghtheo thứ tự đó (hoặc trong bất kỳ thứ tự nó vui lòng).
Alok

Tôi có một số bất đồng với tuyên bố đó, bạn hoàn toàn có thể ép buộc lệnh hoạt động với dấu ngoặc đơn.
jinzai

0

Trình biên dịch, bất kể ngôn ngữ, dịch tất cả toán học infix sang postfix. Nói cách khác, khi trình biên dịch thấy một cái gì đó như:

((a+b)+c)

nó dịch nó thành cái này:

 a b + c +

Điều này được thực hiện bởi vì trong khi ký hiệu infix dễ đọc hơn, ký hiệu hậu tố gần với các bước thực tế mà máy tính phải thực hiện để hoàn thành công việc (và vì đã có thuật toán phát triển tốt cho nó.) định nghĩa, postfix đã loại bỏ tất cả các vấn đề với thứ tự hoạt động hoặc dấu ngoặc đơn, điều này tự nhiên làm cho mọi thứ dễ dàng hơn nhiều khi thực sự viết mã máy.

Tôi giới thiệu bài viết trên wikipedia về Ký hiệu đảo ngược Ba Lan để biết thêm thông tin về chủ đề này.


5
Đó là một giả định không chính xác về cách trình biên dịch dịch các hoạt động. Bạn đang giả sử một máy stack ở đây. Điều gì nếu bạn có một bộ xử lý vector? Điều gì nếu bạn có một máy với một số lượng lớn các thanh ghi?
Ahmed Masud
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.