Tối ưu hóa Chung
Đây là một số tối ưu hóa yêu thích của tôi. Tôi đã thực sự tăng thời gian thực thi và giảm kích thước chương trình bằng cách sử dụng chúng.
Khai báo các hàm nhỏ dưới dạng inline
hoặc macro
Mỗi lệnh gọi đến một hàm (hoặc phương thức) phải chịu phí tổn, chẳng hạn như đẩy các biến vào ngăn xếp. Một số chức năng cũng có thể phát sinh chi phí khi hoàn vốn. Một hàm hoặc phương thức không hiệu quả có ít câu lệnh trong nội dung của nó hơn là tổng chi phí được kết hợp. Đây là những ứng cử viên tốt cho nội tuyến, cho dù nó là #define
macro hoặc inline
hàm. (Vâng, tôi biết inline
chỉ là một gợi ý, nhưng trong trường hợp này, tôi coi đó như một lời nhắc nhở cho trình biên dịch.)
Loại bỏ mã chết và mã thừa
Nếu mã không được sử dụng hoặc không đóng góp vào kết quả của chương trình, hãy loại bỏ nó.
Đơn giản hóa thiết kế các thuật toán
Tôi đã từng loại bỏ rất nhiều mã hợp ngữ và thời gian thực thi khỏi một chương trình bằng cách viết ra phương trình đại số mà nó đang tính toán và sau đó đơn giản hóa biểu thức đại số. Việc triển khai biểu thức đại số đơn giản chiếm ít không gian và thời gian hơn so với hàm ban đầu.
Mở vòng lặp
Mỗi vòng lặp có chi phí kiểm tra gia tăng và kết thúc. Để có được ước tính về hệ số hiệu suất, hãy đếm số lượng lệnh trong chi phí (tối thiểu 3: tăng, kiểm tra, bắt đầu vòng lặp) và chia cho số câu lệnh bên trong vòng lặp. Con số càng thấp thì càng tốt.
Chỉnh sửa: cung cấp một ví dụ về việc mở vòng lặp Trước khi:
unsigned int sum = 0;
for (size_t i; i < BYTES_TO_CHECKSUM; ++i)
{
sum += *buffer++;
}
Sau khi giải nén:
unsigned int sum = 0;
size_t i = 0;
**const size_t STATEMENTS_PER_LOOP = 8;**
for (i = 0; i < BYTES_TO_CHECKSUM; **i = i / STATEMENTS_PER_LOOP**)
{
sum += *buffer++; // 1
sum += *buffer++; // 2
sum += *buffer++; // 3
sum += *buffer++; // 4
sum += *buffer++; // 5
sum += *buffer++; // 6
sum += *buffer++; // 7
sum += *buffer++; // 8
}
// Handle the remainder:
for (; i < BYTES_TO_CHECKSUM; ++i)
{
sum += *buffer++;
}
Trong lợi thế này, một lợi ích thứ cấp đạt được: nhiều câu lệnh được thực thi hơn trước khi bộ xử lý phải tải lại bộ đệm lệnh.
Tôi đã có kết quả đáng kinh ngạc khi tôi mở vòng lặp đến 32 câu lệnh. Đây là một trong những điểm nghẽn vì chương trình phải tính toán tổng kiểm tra trên tệp 2GB. Việc tối ưu hóa này kết hợp với việc đọc khối đã cải thiện hiệu suất từ 1 giờ lên 5 phút. Việc giải nén vòng lặp cũng mang lại hiệu suất tuyệt vời trong hợp ngữ, của tôi memcpy
nhanh hơn rất nhiều so với trình biên dịch memcpy
. - TM
Giảm bớt các if
câu lệnh
Bộ xử lý ghét các nhánh hoặc nhảy, vì nó buộc bộ xử lý tải lại hàng đợi lệnh của nó.
Boolean Arithmetic ( Đã chỉnh sửa: áp dụng định dạng mã cho đoạn mã, thêm ví dụ)
Chuyển đổi if
câu lệnh thành phép gán boolean. Một số bộ xử lý có thể thực thi các lệnh một cách có điều kiện mà không cần phân nhánh:
bool status = true;
status = status && /* first test */;
status = status && /* second test */;
Các mạch ngắn của logic AND điều hành ( &&
) ngăn cản thực hiện các bài kiểm tra nếu status
là false
.
Thí dụ:
struct Reader_Interface
{
virtual bool write(unsigned int value) = 0;
};
struct Rectangle
{
unsigned int origin_x;
unsigned int origin_y;
unsigned int height;
unsigned int width;
bool write(Reader_Interface * p_reader)
{
bool status = false;
if (p_reader)
{
status = p_reader->write(origin_x);
status = status && p_reader->write(origin_y);
status = status && p_reader->write(height);
status = status && p_reader->write(width);
}
return status;
};
Phân bổ biến nhân tố bên ngoài vòng lặp
Nếu một biến được tạo nhanh chóng bên trong một vòng lặp, hãy di chuyển việc tạo / cấp phát đến trước vòng lặp. Trong hầu hết các trường hợp, biến không cần phải được cấp phát trong mỗi lần lặp.
Biểu thức hằng số thừa số bên ngoài vòng lặp
Nếu một phép tính hoặc giá trị biến không phụ thuộc vào chỉ số vòng lặp, hãy di chuyển nó ra bên ngoài (trước) vòng lặp.
I / O theo khối
Đọc và ghi dữ liệu trong các khối (khối) lớn. Càng to càng tốt. Ví dụ: đọc một octect tại một thời điểm kém hiệu quả hơn đọc 1024 octet với một lần đọc.
Thí dụ:
static const char Menu_Text[] = "\n"
"1) Print\n"
"2) Insert new customer\n"
"3) Destroy\n"
"4) Launch Nasal Demons\n"
"Enter selection: ";
static const size_t Menu_Text_Length = sizeof(Menu_Text) - sizeof('\0');
//...
std::cout.write(Menu_Text, Menu_Text_Length);
Hiệu quả của kỹ thuật này có thể được chứng minh trực quan. :-)
Không sử dụng printf
gia đình cho dữ liệu liên tục
Dữ liệu không đổi có thể được xuất ra bằng cách ghi khối. Ghi có định dạng sẽ lãng phí thời gian quét văn bản để định dạng ký tự hoặc xử lý lệnh định dạng. Xem ví dụ mã ở trên.
Định dạng vào bộ nhớ, sau đó ghi
Định dạng thành một char
mảng bằng cách sử dụng nhiều sprintf
, sau đó sử dụng fwrite
. Điều này cũng cho phép chia bố cục dữ liệu thành "phần không đổi" và phần biến. Hãy nghĩ đến tính năng trộn thư .
Khai báo văn bản không đổi (chuỗi ký tự) dưới dạng static const
Khi các biến được khai báo mà không có static
, một số trình biên dịch có thể phân bổ không gian trên ngăn xếp và sao chép dữ liệu từ ROM. Đây là hai thao tác không cần thiết. Điều này có thể được khắc phục bằng cách sử dụng static
tiền tố.
Cuối cùng, Mã giống như trình biên dịch sẽ
Đôi khi, trình biên dịch có thể tối ưu hóa một số câu lệnh nhỏ tốt hơn một phiên bản phức tạp. Ngoài ra, viết mã để giúp trình biên dịch tối ưu hóa cũng giúp ích. Nếu tôi muốn trình biên dịch sử dụng các hướng dẫn chuyển khối đặc biệt, tôi sẽ viết mã có vẻ như nó nên sử dụng các hướng dẫn đặc biệt.