2009-07-13 58 views
9

Dưới đây là một số mã (chương trình đầy đủ sau sau trong câu hỏi):Tối ưu hóa bộ phận trong gcc

template <typename T> 
T fizzbuzz(T n) { 
    T count(0); 
    #if CONST 
     const T div(3); 
    #else 
     T div(3); 
    #endif 
    for (T i(0); i <= n; ++i) { 
     if (i % div == T(0)) count += i; 
    } 
    return count; 
} 

Bây giờ, nếu tôi gọi mẫu chức năng này với int, sau đó tôi nhận được một yếu tố của sự khác biệt 6 hiệu suất theo liệu tôi xác định cONST hay không:

$ gcc --version 
gcc (GCC) 3.4.4 (cygming special, gdc 0.12, using dmd 0.125) 

$ make -B wrappedint CPPFLAGS="-O3 -Wall -Werror -DWRAP=0 -DCONST=0" && 
time ./wrappedint 
g++ -O3 -Wall -Werror -DWRAP=0 -DCONST=0 wrappedint.cpp -o wrappedi 
nt 
484573652 

real 0m2.543s 
user 0m2.059s 
sys  0m0.046s 

$ make -B wrappedint CPPFLAGS="-O3 -Wall -Werror -DWRAP=0 -DCONST=1" && 
time ./wrappedint 
g++ -O3 -Wall -Werror -DWRAP=0 -DCONST=1 wrappedint.cpp -o wrappedi 
nt 
484573652 

real 0m0.655s 
user 0m0.327s 
sys  0m0.046s 

Kiểm tra tháo gỡ cho thấy rằng trong (const) nhanh vụ án, modulo đã được biến thành một nhân và thay đổi loại điều, trong khi ở chậm (không const) trường hợp nó đang sử dụng idivl.

Thậm chí tệ hơn, nếu tôi cố gắng bọc số nguyên trong một lớp, thì tối ưu hóa không xảy ra cho dù tôi có sử dụng const hay không. Mã này luôn luôn sử dụng idivl và chạy chậm:

#include <iostream> 

struct WrappedInt { 
    int v; 
    explicit WrappedInt(const int &val) : v(val) {} 
    bool operator<=(const WrappedInt &rhs) const { return v <= rhs.v; } 
    bool operator==(const WrappedInt &rhs) const { return v == rhs.v; } 
    WrappedInt &operator++() { ++v; return *this; } 
    WrappedInt &operator+=(const WrappedInt &rhs) { v += rhs.v; return *this; } 
    WrappedInt operator%(const WrappedInt &rhs) const 
     { return WrappedInt(v%rhs.v); } 
}; 

std::ostream &operator<<(std::ostream &s, WrappedInt w) { 
    return s << w.v; 
} 

template <typename T> 
T fizzbuzz(T n) { 
    T count(0); 
    #if CONST 
     const T div(3); 
    #else 
     T div(3); 
    #endif 
    for (T i(0); i <= n; ++i) { 
     if (i % div == T(0)) count += i; 
    } 
    return count; 
} 

int main() { 
    #if WRAP 
     WrappedInt w(123456789); 
     std::cout << fizzbuzz(w) << "\n"; 
    #else 
     std::cout << fizzbuzz<int>(123456789) << "\n"; 
    #endif 
} 

Câu hỏi của tôi là:

1) Có một nguyên tắc đơn giản của C++ chính nó, hoặc tối ưu hóa gcc, mà giải thích lý do tại sao điều này xảy ra, hoặc là nó chỉ là một trường hợp của "heuristics chạy khác nhau, đây là mã bạn nhận được"?

2) Có cách nào để làm cho trình biên dịch nhận ra rằng trình bao bọc địa phương được khai báo và không bao giờ tham chiếu const WrappedInt của tôi có thể được coi là giá trị const thời gian biên dịch không? Tôi muốn điều này là một thay thế thẳng cho int trong mẫu.

3) Có cách nào được biết để gói một int sao cho trình biên dịch có thể hủy gói khi tối ưu hóa không? Mục tiêu là WrappedInt sẽ là một mẫu dựa trên chính sách. Nhưng nếu một chính sách "không làm gì" dẫn đến các hình phạt 6x tùy ý về tốc độ tùy ý trên int, thì tốt hơn là nên loại bỏ đặc biệt tình huống đó và sử dụng trực tiếp int.

+0

Trong trường hợp nó gây ra bất kỳ sự nhầm lẫn nào - tôi có lẽ đã đổi tên hàm "fizz" khi tôi xóa "|| i% 5" ;-) –

Trả lời

6

Tôi đoán nó chỉ là phiên bản GCC nghiêm trọng cũ mà bạn đang chạy. Trình biên dịch cũ nhất tôi có trên máy của tôi - gcc-4.1.2, thực hiện một cách nhanh chóng với cả hai phiên bản không phải là const và các gói (và chỉ thực hiện tại -O1).

+0

Đó là tin tốt, cảm ơn. –

+0

Bất kỳ lý do cụ thể nào mà bạn chưa nâng cấp trong 4 năm? –

+0

Cygwin mặc định là 3.4. Tôi cho rằng họ vẫn đang sử dụng nó để xây dựng Cygwin chính nó, hoặc một cái gì đó. Nó có một gói gcc-4, vì vậy, tôi sẽ cố gắng đó. Không xấu như 12 tháng + phiên bản Cygwin của gnupg chứa một lỗ hổng bảo mật quan trọng cố định ngược dòng. –

1

Hãy thử kết hợp const int v trong lớp WrappedInt của bạn với const T trong hàm fizzbuzz của bạn và xem trình biên dịch có thể tối ưu hóa điều đó không.

Bằng cách khai báo const int bạn đã tạo một trường hợp đặc biệt - một hằng số thời gian biên dịch. Trình biên dịch biết giá trị là gì và có thể tối ưu hóa nó nhiều hơn một giá trị có thể thay đổi trong suốt quá trình chạy chương trình.

+0

Thật không may 'WrappedInt' có hai toán tử sửa đổi' v'. Nhưng giới thiệu một lớp mới, 'WrappedConstInt' với chúng được loại bỏ, thêm' toán tử% 'thích hợp và làm cho' div' thành 'const WrappedConstInt', không kích hoạt tối ưu hóa. Nó chạy chậm. –

+0

Và tôi hiểu rằng "const int" là đặc biệt. Tuy nhiên, ngay cả khi không-const, trình biên dịch sẽ kéo giá trị vào thanh ghi, và thanh ghi không bao giờ được sửa đổi trong vòng lặp. Vì vậy, tôi đã hy vọng (a) rằng một số DFA sẽ cho phép nó thấy rằng một int nội bộ mà không bao giờ được sửa đổi và không có tham chiếu được thực hiện, là tốt như const, và cung cấp cho nó điều trị, và/hoặc (b) rằng một const struct có chứa một int chỉ là const như một 'const int'. –

+0

Vì vậy, tôi đoán câu hỏi sẽ trở thành, "có một lý do mạnh mẽ tại sao không (a) cũng không (b) xảy ra, hoặc là nó chỉ là gcc không quản lý nó?" –

0

Có cách nào để bao bọc một int sao cho trình biên dịch có thể hủy gói khi tối ưu hóa không?

Thử qua WrappedInt theo giá trị. Sau đó, WrappedInt s có thể được chuyển vào sổ đăng ký. Tham chiếu pass-by-const đôi khi buộc gcc rơi trở lại ngăn xếp để chuyển đối số.

Giới thiệu về số intconst int bị chậm lại, tôi chỉ có thể suy đoán rằng GCC đang cố gắng phát an toàn khi đối mặt với răng cưa. Về cơ bản, nếu nó không thể chứng minh rằng div không phải là một bí danh cho biến khác, biến dễ tiếp cận hơn, nó không thể biến nó thành một hằng số. Nếu bạn khai báo nó const, GCC giả định rằng nó không phải là bí danh và thực hiện chuyển đổi thành một hằng số. Ngoài số idivl, bạn cũng sẽ thấy tìm nạp bộ nhớ, một lần, khi nhập hàm, trái ngược với các giá trị ngay lập tức được sử dụng cho trường hợp const.

+0

Trong cả hai trường hợp bọc và không const int, div được khởi tạo với "' movl $ 3,% edi' "và không bao giờ chạm vào bộ nhớ. Và việc thay đổi tất cả các toán tử của WrappedInt (cũng như hàm tạo) để truyền theo giá trị không cho phép tối ưu hóa. –

0

Sự khác biệt về tốc độ là do trình biên dịch không biết liệu "div" có thay đổi giá trị hay không. Khi nó không phải là const, nó xử lý nó như một biến được truyền vào. Nó có thể là bất cứ thứ gì, và do đó trình biên dịch sẽ sử dụng một lệnh chia hai biến - idivl. Khi bạn nói rằng đó là const, trình biên dịch là miễn phí để đối xử với nó một cách chính xác như khi bạn đã gõ:

 
if (i % 3 == 0) 

tôi là loại ngạc nhiên rằng nó không sử dụng toán tử AND (&).

WrappedInt không được tối ưu hóa bởi vì, tốt, nó không phải là một int. Một lớp của nó.

Thứ mà bạn có thể làm là kết hợp fizzbuzz vào WrappedInt.

+1

Chỉ modulo quyền hạn của hai có thể được biểu diễn bằng cách sử dụng AND. –

Các vấn đề liên quan