Mẹo tối ưu hóa cấp thấp C ++ [đã đóng]


79

Giả sử bạn đã có thuật toán lựa chọn tốt nhất, bạn có thể đưa ra những giải pháp cấp thấp nào để vắt vài giọt tốc độ khung hình ngọt ngào cuối cùng ra khỏi mã C ++?

Không cần phải nói rằng những lời khuyên này chỉ áp dụng cho phần mã quan trọng mà bạn đã nêu trong trình hồ sơ của bạn, nhưng chúng nên là những cải tiến phi cấu trúc cấp thấp. Tôi đã gieo một ví dụ.


1
Điều gì làm cho điều này trở thành một câu hỏi phát triển trò chơi và không phải là một câu hỏi lập trình chung như thế này: stackoverflow.com/search?q=c%2B%2B+optimization
Danny Varod

@Danny - Đây có lẽ có thể là một câu hỏi lập trình chung. Nó cũng chắc chắn là một câu hỏi liên quan đến lập trình trò chơi. Tôi nghĩ đó là một câu hỏi khả thi trên cả hai trang web.
Smashery

@Smashery Sự khác biệt duy nhất giữa hai là lập trình trò chơi có thể yêu cầu tối ưu hóa mức công cụ đồ họa cụ thể hoặc tối ưu hóa trình mã hóa shader, phần C ++ là như nhau.
Daniel Varod

@Danny - Đúng, một số câu hỏi sẽ "liên quan" hơn trên trang này hay trang kia; nhưng tôi sẽ không muốn từ chối bất kỳ câu hỏi có liên quan chỉ vì chúng cũng có thể được hỏi trên một trang web khác.
Smashery

Câu trả lời:


76

Tối ưu hóa bố cục dữ liệu của bạn! (Điều này áp dụng cho nhiều ngôn ngữ hơn là chỉ C ++)

Bạn có thể đi sâu hơn để thực hiện điều này đặc biệt cho dữ liệu của bạn, bộ xử lý của bạn, xử lý đa lõi độc đáo, v.v. Nhưng khái niệm cơ bản là:

Khi bạn đang xử lý mọi thứ trong một vòng lặp chặt chẽ, bạn muốn làm cho dữ liệu cho mỗi lần lặp càng nhỏ càng tốt và càng gần nhau càng tốt trong bộ nhớ. Điều đó có nghĩa là lý tưởng là một mảng hoặc vectơ của các đối tượng (không phải con trỏ) chỉ chứa dữ liệu cần thiết cho phép tính.

Theo cách này, khi CPU tìm nạp dữ liệu cho lần lặp đầu tiên của vòng lặp của bạn, một số lần lặp lại giá trị dữ liệu tiếp theo sẽ được tải vào bộ đệm với nó.

Thực sự CPU là nhanh và trình biên dịch là tốt. Bạn thực sự không thể làm gì nhiều với việc sử dụng các hướng dẫn ít hơn và nhanh hơn. Sự kết hợp bộ đệm là nơi nó diễn ra (đó là một bài viết ngẫu nhiên mà tôi đã viết - nó chứa một ví dụ hay về việc kết hợp bộ nhớ cache cho một thuật toán không đơn giản chạy qua dữ liệu tuyến tính).


Thật đáng để thử ví dụ C trong trang kết hợp Cache được liên kết. Khi tôi lần đầu tiên phát hiện ra điều này, tôi đã bị sốc về mức độ khác biệt của nó.
Neel

9
Xem thêm những cạm bẫy tuyệt vời của Object Oriented Programming trình bày (Sony R & D) ( research.scee.net/files/presentations/gcapaustralia09/... ) - và các bài viết CellPerformance cáu kỉnh nhưng hấp dẫn bởi Mike Acton ( cellperformance.beyond3d.com/articles/ chỉ mục.html ). Blog của Noel Llopis từ Inside Inside cũng thường xuyên chạm vào chủ đề này ( gamesfromwithin.com ). Tôi không thể khuyên các cạm bẫy trượt đủ ...
leander

2
Tôi chỉ cảnh báo về việc "làm cho dữ liệu cho mỗi lần lặp càng nhỏ càng tốt và càng gần nhau càng tốt trong bộ nhớ" . Truy cập dữ liệu không liên kết có thể làm cho mọi thứ chậm hơn; trong trường hợp đệm sẽ cho hiệu suất tốt hơn. Thứ tự của dữ liệu cũng quan trọng, vì dữ liệu được sắp xếp tốt có thể dẫn đến việc đệm ít hơn. Scott Mayers có thể giải thích điều này tốt hơn tôi có thể :)
Jonathan Connell

+1 cho bài thuyết trình của Sony. Tôi đã đọc cái đó trước đây và nó thực sự có ý nghĩa về cách tối ưu hóa dữ liệu ở cấp độ nền tảng, với việc xem xét chia dữ liệu thành các khối và sắp xếp nó đúng cách.
ChrisC

84

Một mẹo rất, rất thấp, nhưng một mẹo có thể có ích:

Hầu hết các trình biên dịch hỗ trợ một số hình thức gợi ý có điều kiện rõ ràng. GCC có một hàm gọi là __builtin_Exect cho phép bạn thông báo cho trình biên dịch giá trị của kết quả có thể là gì. GCC có thể sử dụng dữ liệu đó để tối ưu hóa các điều kiện để thực hiện nhanh nhất có thể trong trường hợp dự kiến, với việc thực hiện chậm hơn một chút trong trường hợp không mong muốn.

if(__builtin_expect(entity->extremely_unlikely_flag, 0)) {
  // code that is rarely run
}

Tôi đã thấy tăng tốc 10-20% với việc sử dụng đúng cách này.


1
Tôi sẽ bỏ phiếu hai lần nếu tôi có thể.
tenpn

10
+1, Nhân Linux sử dụng rộng rãi điều này cho các vi mô hóa trong mã lập lịch và nó tạo ra sự khác biệt đáng kể trong các đường dẫn mã nhất định.
greyfade

2
Thật không may, dường như không có tương đương tốt trong Visual Studio. stackoverflow.com/questions/1440570/
trộm

1
Vậy ở tần số nào, giá trị mong đợi thường phải là giá trị chính xác để đạt được hiệu suất? 49/50 lần? Hoặc 999999/1000000 lần?
Douglas

36

Điều đầu tiên bạn cần hiểu là phần cứng bạn đang chạy. Làm thế nào để nó xử lý phân nhánh? Còn bộ nhớ đệm thì sao? Nó có một tập lệnh SIMD không? Nó có thể sử dụng bao nhiêu bộ xử lý? Nó có phải chia sẻ thời gian xử lý với bất cứ điều gì khác?

Bạn có thể giải quyết cùng một vấn đề theo những cách rất khác nhau - ngay cả sự lựa chọn thuật toán của bạn cũng phải phụ thuộc vào phần cứng. Trong một số trường hợp O (N) có thể chạy chậm hơn O (NlogN) (tùy thuộc vào việc thực hiện).

Là một tổng quan thô sơ về tối ưu hóa, điều đầu tiên tôi sẽ làm là xem xét chính xác vấn đề nào và dữ liệu nào bạn đang cố gắng giải quyết. Sau đó tối ưu hóa cho điều đó. Nếu bạn muốn hiệu suất cao thì hãy quên các giải pháp chung chung - bạn có thể đặc biệt mọi thứ không phù hợp với trường hợp được sử dụng nhiều nhất của bạn.

Sau đó hồ sơ. Hồ sơ, hồ sơ, hồ sơ. Nhìn vào việc sử dụng bộ nhớ, nhìn vào các hình phạt phân nhánh, Nhìn vào chức năng gọi qua chức năng, xem việc sử dụng đường ống. Tìm ra những gì đang làm chậm mã của bạn. Đó có thể là quyền truy cập dữ liệu (Tôi đã viết một bài báo có tên "Con voi trễ" về chi phí truy cập dữ liệu - google nó. Tôi không thể đăng 2 liên kết ở đây vì tôi không có đủ "danh tiếng"), vì vậy hãy kiểm tra chặt chẽ và sau đó tối ưu hóa bố cục dữ liệu của bạn ( mảng đồng nhất phẳng lớn đẹp tuyệt vời ) và truy cập dữ liệu (tìm nạp trước nếu có thể).

Khi bạn đã giảm thiểu chi phí hoạt động của hệ thống con bộ nhớ, hãy thử và xác định xem các hướng dẫn có phải là nút cổ chai không (hy vọng là chúng), sau đó xem các triển khai SIMD của thuật toán của bạn - Việc triển khai Cấu trúc (SoA) có thể rất dữ liệu và lệnh cache hiệu quả. Nếu SIMD không phù hợp với vấn đề của bạn thì có thể cần mã hóa cấp độ trình biên dịch và trình biên dịch.

Nếu bạn vẫn cần thêm tốc độ thì hãy đi song song. Nếu bạn có lợi ích khi chạy trên PS3 thì SPU là bạn của bạn. Sử dụng chúng, yêu chúng. Nếu bạn đã viết một giải pháp SIMD thì bạn sẽ nhận được một lợi ích lớn khi chuyển sang SPU.

Và sau đó, hồ sơ một số chi tiết. Thử nghiệm trong các kịch bản trò chơi - mã này có còn là nút cổ chai không? Bạn có thể thay đổi cách sử dụng mã này ở mức cao hơn để giảm thiểu việc sử dụng mã này không (thực ra, đây phải là bước đầu tiên của bạn)? Bạn có thể trì hoãn tính toán trên nhiều khung không?

Dù bạn đang sử dụng nền tảng nào, hãy tìm hiểu càng nhiều càng tốt về phần cứng và trình biên dịch có sẵn. Đừng cho rằng bạn biết nút cổ chai là gì - hãy tìm nó với trình hồ sơ của bạn. Và chắc chắn rằng bạn có một heuristic để xác định xem bạn đã thực sự làm cho trò chơi của bạn đi nhanh hơn.

Và sau đó hồ sơ nó một lần nữa.


31

Bước đầu tiên: Suy nghĩ cẩn thận về dữ liệu của bạn liên quan đến các thuật toán của bạn. O (log n) không phải lúc nào cũng nhanh hơn O (n). Ví dụ đơn giản: Một bảng băm chỉ có một vài khóa thường được thay thế tốt hơn bằng tìm kiếm tuyến tính.

Bước thứ hai: Nhìn vào lắp ráp được tạo ra. C ++ mang lại rất nhiều thế hệ mã ẩn cho bảng. Đôi khi, nó lẻn vào bạn mà bạn không biết.

Nhưng giả sử đó là thời gian thực sự đạp xe: Hồ sơ. Nghiêm túc. Áp dụng ngẫu nhiên "các thủ thuật thực hiện" có khả năng gây tổn thương cũng như giúp đỡ.

Sau đó, mọi thứ phụ thuộc vào những gì tắc nghẽn của bạn là gì.

bộ nhớ cache dữ liệu => tối ưu hóa bố cục dữ liệu của bạn. Đây là một điểm khởi đầu tốt: http://gamesfromwithin.com/data-oriented-design

mã bộ nhớ cache bị mất => Nhìn vào các cuộc gọi chức năng ảo, độ sâu của cuộc gọi quá mức, v.v ... Một nguyên nhân phổ biến cho hiệu năng kém là niềm tin sai lầm rằng các lớp cơ sở phải là ảo.

Các hiệu suất chìm C ++ phổ biến khác:

  • Phân bổ / thỏa thuận quá mức. Nếu đó là hiệu suất quan trọng, đừng gọi vào thời gian chạy. Không bao giờ.
  • Sao chép thi công. Tránh bất cứ nơi nào bạn có thể. Nếu nó có thể là một tài liệu tham khảo const, làm cho nó một.

Tất cả những điều trên là rõ ràng ngay lập tức khi bạn nhìn vào hội đồng, vì vậy hãy xem ở trên;)


19

Loại bỏ các nhánh không cần thiết

Trên một số nền tảng và với một số trình biên dịch, các nhánh có thể vứt bỏ toàn bộ đường ống của bạn, do đó, thậm chí không đáng kể nếu các khối () có thể đắt tiền.

Kiến trúc PowerPC (PS3 / x360) cung cấp hướng dẫn chọn dấu phẩy động , fsel. Điều này có thể được sử dụng ở vị trí của một nhánh nếu các khối là các bài tập đơn giản:

float result = 0;
if (foo > bar) { result = 2.0f; }
else { result = 1.0f; }

Trở thành:

float result = fsel(foo-bar, 2.0f, 1.0f);

Khi tham số thứ nhất lớn hơn hoặc bằng 0, tham số thứ hai được trả về, khác với tham số thứ ba.

Cái giá của việc mất nhánh là cả khối if {} và khối {} khác sẽ được thực thi, vì vậy nếu một thao tác đắt tiền hoặc hủy đăng ký thì một con trỏ NULL tối ưu hóa này không phù hợp.

Đôi khi trình biên dịch của bạn đã thực hiện công việc này, vì vậy hãy kiểm tra lắp ráp của bạn trước.

Dưới đây là thông tin thêm về phân nhánh và fsel:

http://assinstallrequired.crashworks.org/tag/intrinsics/


kết quả nổi = (foo> bar)? 2.f: 1.f
hiệp sĩ666

3
@ hiệp sĩ: Điều đó vẫn sẽ tạo ra một chi nhánh ở bất cứ nơi nào mà "nếu" đã làm được. Tôi nói như vậy bởi vì trên ARM, ít nhất, các chuỗi nhỏ như thế có thể được thực hiện với các hướng dẫn có điều kiện không yêu cầu phân nhánh.
chrisbtoo

1
@ Knight666 nếu bạn may mắn, trình biên dịch có thể biến nó thành một fsel, nhưng nó không chắc chắn. FWIW, tôi thường sẽ viết đoạn trích đó với một toán tử bậc ba, và sau đó sẽ tối ưu hóa thành fsel nếu trình lược tả đồng ý.
tenpn

Trên IA32 bạn đã có CMOVcc thay thế.
Skizz

Xem thêm blueraja.com/blog/285/ (lưu ý rằng trong trường hợp này, nếu trình biên dịch tốt, nó sẽ có thể tự tối ưu hóa điều này, vì vậy đó không phải là điều bạn thường phải lo lắng)
BlueRaja - Danny Pflughoeft

16

Tránh truy cập bộ nhớ và đặc biệt là những người ngẫu nhiên bằng mọi giá.

Đó là điều quan trọng nhất để tối ưu hóa cho các CPU hiện đại. Bạn có thể thực hiện một số lượng nhỏ số học và thậm chí rất nhiều nhánh dự đoán sai trong thời gian bạn chờ dữ liệu từ RAM.

Bạn cũng có thể đọc quy tắc này theo cách khác: Thực hiện càng nhiều phép tính càng tốt giữa các lần truy cập bộ nhớ.



11

Loại bỏ các cuộc gọi chức năng ảo không cần thiết

Việc gửi một chức năng ảo có thể rất chậm. Bài viết này cung cấp một giải thích tốt về lý do tại sao. Nếu có thể, đối với các hàm được gọi nhiều lần trên mỗi khung, hãy tránh chúng.

Bạn có thể làm điều này theo một vài cách. Đôi khi bạn chỉ có thể viết lại các lớp để không cần kế thừa - có thể hóa ra MachineGun là lớp con duy nhất của Vũ khí và bạn có thể hợp nhất chúng.

Bạn có thể sử dụng các mẫu để thay thế đa hình thời gian chạy bằng đa hình thời gian biên dịch. Điều này chỉ hoạt động nếu bạn biết kiểu con của các đối tượng của bạn trong thời gian chạy và có thể là một bản viết lại chính.


9

Nguyên tắc cơ bản của tôi là: không làm bất cứ điều gì không cần thiết .

Nếu bạn thấy rằng một chức năng cụ thể là một nút cổ chai, bạn có thể tối ưu hóa chức năng - hoặc bạn có thể cố gắng giữ cho nó không bị gọi ở vị trí đầu tiên.

Điều này không nhất thiết có nghĩa là bạn đang sử dụng một thuật toán xấu. Điều đó có thể có nghĩa là bạn đang chạy tính toán mọi khung hình có thể được lưu trong bộ nhớ cache trong một thời gian ngắn (hoặc hoàn toàn được tính trước), chẳng hạn.

Tôi luôn thử phương pháp này trước mọi nỗ lực tối ưu hóa ở mức độ thấp.


2
Câu hỏi này giả định rằng bạn đã thực hiện tất cả các công cụ cấu trúc bạn có thể.
tenpn

2
Nó làm. Nhưng thường thì bạn cho rằng bạn có, và bạn thì không. Vì vậy, thực sự, mỗi khi một chức năng đắt tiền cần được tối ưu hóa, hãy tự hỏi nếu bạn cần gọi chức năng đó.
Rachel Blum

2
... nhưng đôi khi thực sự có thể nhanh hơn để thực hiện phép tính ngay cả khi bạn sẽ vứt bỏ kết quả sau đó, thay vì chi nhánh.
tenpn


6

Giảm thiểu các chuỗi phụ thuộc để sử dụng tốt hơn các dòng CPU.

Trong các trường hợp đơn giản, trình biên dịch có thể làm điều này cho bạn nếu bạn kích hoạt hủy đăng ký vòng lặp. Tuy nhiên, nó thường sẽ không làm điều đó, đặc biệt là khi có các float liên quan đến việc sắp xếp lại các biểu thức thay đổi kết quả.

Thí dụ:

float *data = ...;
int length = ...;

// Slow version
float total = 0.0f;
int i;
for (i=0; i < length; i++)
{
  total += data[i]
}

// Fast version
float total1, total2, total3, total4;
for (i=0; i < length-3; i += 4)
{
  total1 += data[i];
  total2 += data[i+1];
  total3 += data[i+2];
  total4 += data[i+3];
}
for (; i < length; i++)
{
  total += data[i]
}
total += (total1 + total2) + (total3 + total4);

4

Đừng bỏ qua trình biên dịch của bạn - ví dụ: nếu bạn đang sử dụng gcc trên Intel, bạn có thể dễ dàng đạt được hiệu suất bằng cách chuyển sang Trình biên dịch Intel C / C ++. Nếu bạn đang nhắm mục tiêu một nền tảng ARM, hãy xem trình biên dịch thương mại của ARM. Nếu bạn đang dùng iPhone, Apple chỉ cho phép Clang được sử dụng bắt đầu với SDK iOS 4.0.

Một vấn đề mà bạn có thể sẽ gặp phải khi tối ưu hóa, đặc biệt là trên x86, đó là rất nhiều thứ trực quan cuối cùng sẽ chống lại bạn trong việc triển khai CPU hiện đại. Thật không may cho hầu hết chúng ta, khả năng tối ưu hóa trình biên dịch đã mất từ ​​lâu. Trình biên dịch có thể lên lịch các hướng dẫn trong luồng dựa trên kiến ​​thức bên trong của CPU. Ngoài ra, CPU cũng có thể lên lịch lại các hướng dẫn dựa trên nhu cầu của chính nó. Ngay cả khi bạn nghĩ ra một cách tối ưu để sắp xếp một phương thức, rất có thể trình biên dịch hoặc CPU đã tự mình đưa ra phương pháp đó và đã thực hiện tối ưu hóa đó.

Lời khuyên tốt nhất của tôi sẽ là bỏ qua các tối ưu hóa cấp thấp và tập trung vào các cấp độ cao hơn. Trình biên dịch và CPU không thể thay đổi thuật toán của bạn từ thuật toán O (n ^ 2) sang thuật toán O (1), bất kể chúng có tốt đến đâu. Điều đó sẽ yêu cầu bạn nhìn vào chính xác những gì bạn đang cố gắng làm và tìm ra cách tốt hơn để làm điều đó. Hãy để trình biên dịch và CPU lo lắng về mức thấp và bạn tập trung vào mức trung bình đến cao.


Tôi thấy những gì bạn đang nói, nhưng có một điểm khi bạn đạt đến O (logN) và bạn sẽ không nhận được bất kỳ thay đổi cấu trúc nào nữa, nơi các tối ưu hóa cấp thấp có thể phát huy tác dụng thêm nửa mili giây.
tenpn

1
Xem câu trả lời của tôi lại: O (log n). Ngoài ra, nếu bạn tìm kiếm trong nửa phần nghìn giây, bạn có thể cần phải xem cấp độ cao hơn. Đó là 3% thời gian khung hình của bạn!
Rachel Blum

4

Các giới hạn từ khóa là có tiềm năng tiện dụng, đặc biệt là trong trường hợp bạn cần phải thao tác đối tượng với con trỏ. Nó cho phép trình biên dịch giả định rằng đối tượng trỏ sẽ không bị sửa đổi theo bất kỳ cách nào khác, điều này cho phép nó thực hiện tối ưu hóa mạnh mẽ hơn như giữ các phần của đối tượng trong các thanh ghi hoặc sắp xếp lại đọc và ghi hiệu quả hơn.

Một điều tốt về từ khóa là đó là một gợi ý mà bạn có thể áp dụng một lần và thấy được lợi ích mà không cần sắp xếp lại thuật toán của mình. Mặt xấu là nếu bạn sử dụng sai vị trí, bạn có thể thấy hỏng dữ liệu. Nhưng thông thường, thật dễ dàng để nhận ra nơi nào hợp pháp để sử dụng nó - đó là một trong số ít ví dụ mà người lập trình có thể mong đợi biết nhiều hơn trình biên dịch có thể giả định một cách an toàn, đó là lý do tại sao từ khóa được giới thiệu.

Về mặt kỹ thuật, 'hạn chế' không tồn tại trong C ++ tiêu chuẩn, nhưng tương đương với nền tảng cụ thể có sẵn cho hầu hết các trình biên dịch C ++, vì vậy nó đáng để xem xét.

Xem thêm: http://cellperformance.beyond3d.com/articles/2006/05/demystifying-the-restrict-keyword.html


2

Const tất cả mọi thứ!

Càng nhiều thông tin bạn cung cấp cho trình biên dịch về dữ liệu thì việc tối ưu hóa càng tốt (ít nhất là theo kinh nghiệm của tôi).

void foo(Bar * x) {...;}

trở thành;

void foo(const Bar * const x) {...;}

Bây giờ trình biên dịch biết rằng con trỏ x sẽ không thay đổi và dữ liệu mà nó trỏ tới cũng sẽ không thay đổi.

Lợi ích bổ sung khác là bạn có thể giảm số lượng lỗi vô ý, ngăn chặn bản thân (hoặc người khác) sửa đổi những thứ mà họ không nên.


Và bạn thân mã của bạn sẽ yêu bạn!
tenpn

4
constkhông cải thiện tối ưu hóa trình biên dịch. Đúng trình biên dịch có thể tạo mã tốt hơn nếu nó biết một biến sẽ không thay đổi, nhưng constkhông cung cấp một đảm bảo đủ mạnh.
deft_code

3
Không. 'Hạn chế' hữu ích hơn nhiều so với 'const'. Xem gamedev.stackexchange.com/questions/853/ từ
Justicle

+1 ppl nói rằng không thể giúp đỡ là sai ... infoq.com/presentations/kixeye-scalability
NoSenseEtAl

2

Thông thường, cách tốt nhất để đạt được hiệu suất là thay đổi thuật toán của bạn. Việc thực hiện càng ít nói chung, bạn càng có thể tiếp cận với kim loại.

Giả sử đã được thực hiện ....

Nếu đó thực sự là mã quan trọng, hãy cố gắng tránh đọc bộ nhớ, cố gắng tránh tính toán những thứ có thể được tính toán trước (mặc dù không có bảng tra cứu vì chúng vi phạm quy tắc số 1). Biết những gì thuật toán của bạn làm và viết nó theo cách mà trình biên dịch cũng biết nó. Kiểm tra lắp ráp để chắc chắn nó làm.

Tránh bỏ lỡ bộ nhớ cache. Xử lý hàng loạt càng nhiều càng tốt. Tránh các chức năng ảo và các chỉ dẫn khác.

Cuối cùng, đo lường mọi thứ. Các quy tắc thay đổi tất cả các thời gian. Những gì được sử dụng để tăng tốc mã 3 năm trước bây giờ làm chậm nó. Một ví dụ điển hình là 'sử dụng các hàm toán học kép thay vì các phiên bản float'. Tôi sẽ không nhận ra rằng nếu tôi không đọc nó.

Tôi đã quên - không có các hàm tạo mặc định xác định các biến của bạn hoặc nếu bạn khăng khăng, ít nhất cũng tạo các hàm tạo không. Hãy nhận biết những điều không hiển thị trong hồ sơ. Khi bạn mất một chu kỳ không cần thiết cho mỗi dòng mã, sẽ không có gì hiển thị trong hồ sơ của bạn, nhưng bạn sẽ mất toàn bộ rất nhiều chu kỳ. Một lần nữa, biết mã của bạn đang làm gì. Làm cho chức năng cốt lõi của bạn nạc thay vì hoàn hảo. Phiên bản Foolproof có thể được gọi nếu cần, nhưng không phải lúc nào cũng cần thiết. Tính linh hoạt có giá - hiệu suất là một.

Được chỉnh sửa để giải thích tại sao không có khởi tạo mặc định: Rất nhiều mã nói: Vector3 bla; bla = DoS Something ();

Việc định hướng trong hàm tạo bị lãng phí thời gian. Ngoài ra, trong trường hợp này, thời gian lãng phí là nhỏ (có thể xóa vectơ), tuy nhiên nếu các lập trình viên của bạn làm điều này theo thói quen thì nó sẽ tăng lên. Ngoài ra, rất nhiều hàm tạo ra một toán tử tạm thời (nghĩ rằng quá tải), được khởi tạo về 0 và được gán ngay sau đó. Các chu kỳ bị mất ẩn quá nhỏ để thấy sự tăng đột biến trong trình lược tả của bạn, nhưng các chu kỳ bị chảy máu trên khắp cơ sở mã của bạn. Ngoài ra, một số người làm nhiều hơn trong các nhà xây dựng (rõ ràng là không có). Tôi đã thấy mức tăng nhiều mili giây từ một biến không được sử dụng trong đó hàm tạo tình cờ hơi nặng nề. Ngay khi hàm tạo gây ra tác dụng phụ, trình biên dịch sẽ không thể tối ưu hóa nó, vì vậy trừ khi bạn không bao giờ sử dụng mã ở trên, tôi thích một hàm tạo không khởi tạo, hoặc, như tôi đã nói,

Vector3 bla (noInit); bla = doS Something ();


/ Đừng / khởi tạo thành viên của bạn trong constructor? Điều đó giúp gì?
tenpn

Xem bài đã được chỉnh sửa. Không vừa trong hộp bình luận.
Kaj

const Vector3 = doSomething()? Sau đó, tối ưu hóa giá trị trả về có thể khởi động và có thể tạo ra một hoặc hai nhiệm vụ.
tenpn

1

Giảm đánh giá biểu thức boolean

Điều này thực sự tuyệt vọng, vì nó là một thay đổi rất tinh vi nhưng nguy hiểm cho mã của bạn. Tuy nhiên, nếu bạn có một điều kiện được đánh giá số lần không phù hợp, bạn có thể giảm chi phí đánh giá boolean bằng cách sử dụng các toán tử bitwise thay thế. Vì thế:

if ((foo && bar) || blah) { ... } 

Trở thành:

if ((foo & bar) | blah) { ... }

Sử dụng số học số nguyên thay thế. Nếu foos và bar của bạn là hằng số hoặc được đánh giá trước if (), thì điều này có thể nhanh hơn phiên bản boolean bình thường.

Là một phần thưởng, phiên bản số học có ít nhánh hơn phiên bản boolean thông thường. Đó là một cách khác để tối ưu hóa .

Nhược điểm lớn là bạn mất đánh giá lười biếng - toàn bộ khối được đánh giá, vì vậy bạn không thể làm được foo != NULL & foo->dereference(). Bởi vì điều này, người ta cho rằng điều này khó duy trì, và vì vậy sự đánh đổi có thể quá lớn.


1
Đó là một sự đánh đổi khá lớn vì lợi ích của hiệu suất, chủ yếu là vì nó không rõ ràng ngay lập tức mà nó được dự định.
Bob Bolog

Tôi gần như hoàn toàn đồng ý với bạn. Tôi đã nói rằng nó là tuyệt vọng!
tenpn

3
Điều này cũng không phá vỡ ngắn mạch và làm cho dự đoán nhánh không đáng tin cậy hơn?
Egon

1
Nếu foo là 2 và thanh là 1 thì mã không hoạt động theo cùng một cách. Điều đó, và không đánh giá sớm, là nhược điểm lớn nhất tôi nghĩ.

1
Thông thường, các booleans trong C ++ được bảo đảm là 0 hoặc 1, miễn là bạn chỉ làm điều này với các bool bạn an toàn. Thêm: altdevblogaday.org/2011/04/18/under Hiểu
tenpn

1

Theo dõi việc sử dụng ngăn xếp của bạn

Tất cả mọi thứ bạn thêm vào ngăn xếp là một cú đẩy và xây dựng thêm khi một hàm được gọi. Khi cần một lượng lớn không gian ngăn xếp, đôi khi có thể có ích khi phân bổ bộ nhớ làm việc trước thời hạn và nếu nền tảng bạn đang làm việc có sẵn RAM nhanh để sử dụng - thì tốt hơn!

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.