2013-04-22 4 views
7

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:

  1. 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).
  2. 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 ở đó.
  3. Nếu tôi xem nội dung của pData và nội dung của m_data khi gỡ lỗi, nó rõ ràng cho thấy rằng tất cả các bài tập trong if -branch có ảnh hưởng đến m_datam_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.

+0

Giảm số này thành [SSCCE] (http://sscce.org). – djechlin

+7

@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. –

+0

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

Trả lời

5

Dường như đây là lỗi của tôi. Nó tốt cho trình tối ưu hóa để loại bỏ sự chuyển hướng không cần thiết, nhưng nó không nên loại bỏ việc gán cho pData.

Tất nhiên, bạn có thể giải quyết vấn đề bằng cách gán cho pData trước vòng lặp (ít nhất trong ví dụ đơn giản này). Tôi thu thập rằng vấn đề trong mã thực tế của bạn không dễ giải quyết.

+1

Trên thực tế việc loại bỏ toàn bộ con trỏ là giải pháp trong mã thực tế. Mã này đã hơn 15 tuổi và hầu hết các lỗi như thế này đều dễ giải quyết vì mã chỉ đơn giản là cần tái cấu trúc đơn giản. Tìm lỗi là một câu chuyện hoàn toàn khác. – Excelcius

+0

Vì tôi khá chắc chắn rằng đây là lỗi trình biên dịch, tôi sẽ chấp nhận điều này làm câu trả lời. Ngay sau khi tôi đã đệ trình một bản báo cáo lỗi với microsoft, tôi sẽ thêm liên kết vào câu hỏi, vì vậy sẽ có một ví dụ có thể tái tạo thực sự có sẵn. Tôi nghĩ rằng đó là nhiều hơn một điều C + +/CLI mặc dù, trình biên dịch bình thường đã không mess up. – Excelcius

1

Tôi cũng bỏ phiếu cho một lỗi trình tối ưu hóa nếu nó thực sự có thể tái sản xuất trong ví dụ này. Để ghi đè trình tối ưu hóa, bạn có thể cố gắng khai báo pDatavolatile.