Trước hết: tôi biết rằng hầu hết các lỗi tối ưu hóa là do lỗi lập trình hoặc dựa vào các sự kiện có thể thay đổi tùy thuộc vào cài đặt tối ưu hóa (giá trị dấu phẩy động, các vấn đề đa luồng, ...).Lỗi trình tối ưu hóa hoặc lỗi lập trình?
Tuy nhiên, tôi gặp khó khăn khi tìm lỗi và có phần không chắc chắn nếu có cách nào để ngăn chặn các loại lỗi này xảy ra mà không tắt tối ưu hóa. Tui bỏ lỡ điều gì vậy? Có thể đây thực sự là lỗi của trình tối ưu hóa không? Dưới đây là ví dụ đơn giản:
struct Data {
int a;
int b;
double c;
};
struct Test {
void optimizeMe();
Data m_data;
};
void Test::optimizeMe() {
Data * pData; // Note that this pointer is not initialized!
bool first = true;
for (int i = 0; i < 3; ++i) {
if (first) {
first = false;
pData = &m_data;
pData->a = i * 10;
pData->b = i * pData->a;
pData->c = pData->b/2;
} else {
pData->a = ++i;
} // end if
} // end for
};
int main(int argc, char *argv[]) {
Test test;
test.optimizeMe();
return 0;
}
Chương trình thực tế tất nhiên có nhiều việc phải làm hơn thế này. Nhưng tất cả đều tóm tắt thực tế là thay vì truy cập vào m_data trực tiếp, một con trỏ (trước đây được đơn vị hóa) đang được sử dụng. Ngay sau khi tôi thêm đủ báo cáo với if (first)
-part, tôi ưu hoa dường như thay đổi mã để một cái gì đó dọc theo những dòng:
if (first) {
first = false;
// pData-assignment has been removed!
m_data.a = i * 10;
m_data.b = i * m_data.a;
m_data.c = m_data.b/m_data.a;
} else {
pData->a = ++i; // This will crash - pData is not set yet.
} // end if
Như bạn thấy, nó sẽ thay thế con trỏ dereference không cần thiết với một ghi trực tiếp đến cấu trúc thành viên. Tuy nhiên, nó không làm điều này trong trang trại else
. Nó cũng loại bỏ các pData
-khớp. Vì con trỏ bây giờ vẫn được đơn vị hóa, chương trình sẽ bị lỗi trong trang else
.
Tất nhiên có những điều khác nhau mà có thể được cải thiện ở đây, vì vậy bạn có thể đổ lỗi cho các lập trình viên:
- Hãy quên đi con trỏ và làm những gì tôi ưu hoa không - sử dụng
m_data
trực tiếp. - Khởi tạo pData thành nullptr - theo cách đó trình tối ưu hóa biết rằng trang
else
sẽ không thành công nếu con trỏ không bao giờ được gán. Ít nhất nó dường như giải quyết vấn đề trong môi trường thử nghiệm của tôi. - Di chuyển chỉ định con trỏ ở phía trước vòng lặp (khởi tạo hiệu quả
pData
với&m_data
, sau đó cũng có thể là tham chiếu thay vì con trỏ (để đo tốt). Điều này có ý nghĩa vì pData là cần thiết trong mọi trường hợp nên không có lý do để làm điều này bên trong vòng lặp
Mã rõ ràng là có mùi, để nói rằng ít nhất, và tôi không cố gắng "đổ lỗi" cho trình tối ưu hóa để thực hiện việc này. Nhưng tôi hỏi: Tôi làm sai? Chương trình có thể xấu xí, nhưng đó là mã hợp lệ ...
Tôi nên thêm rằng tôi đang sử dụng VS2012 với C + +/CLI và v110_xp-Toolset. Tối ưu hóa được đặt thành/O2. Cũng xin lưu ý rằng nếu bạn thực sự muốn tái tạo vấn đề (đó không thực sự là điểm của câu hỏi này), bạn cần phải chơi xung quanh với sự phức tạp của chương trình. Đây là một ví dụ rất đơn giản và trình tối ưu hóa đôi khi không loại bỏ việc gán con trỏ. Ẩn &m_data
đằng sau một hàm có vẻ là "trợ giúp".
EDIT:
Q: Làm thế nào để tôi biết rằng trình biên dịch là tối ưu hóa nó để cái gì đó như ví dụ được cung cấp?
A: Tôi không giỏi đọc lắp ráp, tôi đã nhìn nó tuy nhiên và đã thực hiện 3 quan sát mà làm cho tôi tin rằng nó hoạt động theo cách này:
- Ngay sau khi đá tối ưu hóa trong (thêm nhiều nhiệm vụ hơn thường làm các trick) việc phân công con trỏ không có tuyên bố lắp ráp liên quan. Nó cũng đã không được chuyển đến tuyên bố, vì vậy nó thực sự trái uninitialized có vẻ như (ít nhất là với tôi).
- Trong trường hợp chương trình gặp sự cố, trình gỡ lỗi sẽ bỏ qua câu lệnh gán. Trong trường hợp chương trình chạy mà không có sự cố, trình gỡ lỗi dừng lại ở đó.
- Nếu tôi xem nội dung của
pData
và nội dung củam_data
khi gỡ lỗi, nó rõ ràng cho thấy rằng tất cả các bài tập trongif
-branch có ảnh hưởng đếnm_data
vàm_data
nhận các giá trị chính xác. Con trỏ chính nó vẫn trỏ đến cùng một giá trị chưa được khởi tạo từ đầu. Vì vậy, tôi phải thừa nhận rằng thực tế là không sử dụng con trỏ để thực hiện các bài tập.
Q: Nó có phải làm gì với i (Loop unrolling) không?
A: Không, chương trình thực sự sử dụng lệnh {{}} trong khi() để lặp qua kết quả SELECT SQL để số lần lặp hoàn toàn theo thời gian cụ thể và không thể được trình biên dịch xác định trước.
Giảm số này thành [SSCCE] (http://sscce.org). – djechlin
@djechlin: Lỗi bị ảnh hưởng bởi việc tối ưu hóa có thể khó giảm đối với các mẫu mã nhỏ. Câu hỏi nêu rõ đây là một ví dụ đơn giản. –
không thể giải thích rõ ràng một ý tưởng nào được nêu ra, nhưng nó có hoạt động theo cách tương tự nếu bạn tạo lớp thay vì struct? – evilruff