Chào mừng đến với thế giới của điểm nổi bất thường ! Họ có thể tàn phá hiệu suất !!!
Các số bất thường (hoặc không bình thường) là một loại hack để có được một số giá trị bổ sung rất gần với số 0 trong biểu diễn dấu phẩy động. Các thao tác trên điểm nổi không chuẩn hóa có thể chậm hơn hàng chục đến hàng trăm lần so với điểm nổi chuẩn hóa. Điều này là do nhiều bộ xử lý không thể xử lý chúng trực tiếp và phải bẫy và giải quyết chúng bằng microcode.
Nếu bạn in ra những con số sau 10.000 lần lặp lại, bạn sẽ thấy rằng họ đã hội tụ những giá trị khác nhau tùy thuộc vào việc 0
hay 0.1
được sử dụng.
Đây là mã kiểm tra được biên dịch trên x64:
int main() {
double start = omp_get_wtime();
const float x[16]={1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2.0,2.1,2.2,2.3,2.4,2.5,2.6};
const float z[16]={1.123,1.234,1.345,156.467,1.578,1.689,1.790,1.812,1.923,2.034,2.145,2.256,2.367,2.478,2.589,2.690};
float y[16];
for(int i=0;i<16;i++)
{
y[i]=x[i];
}
for(int j=0;j<9000000;j++)
{
for(int i=0;i<16;i++)
{
y[i]*=x[i];
y[i]/=z[i];
#ifdef FLOATING
y[i]=y[i]+0.1f;
y[i]=y[i]-0.1f;
#else
y[i]=y[i]+0;
y[i]=y[i]-0;
#endif
if (j > 10000)
cout << y[i] << " ";
}
if (j > 10000)
cout << endl;
}
double end = omp_get_wtime();
cout << end - start << endl;
system("pause");
return 0;
}
Đầu ra:
#define FLOATING
1.78814e-007 1.3411e-007 1.04308e-007 0 7.45058e-008 6.70552e-008 6.70552e-008 5.58794e-007 3.05474e-007 2.16067e-007 1.71363e-007 1.49012e-007 1.2666e-007 1.11759e-007 1.04308e-007 1.04308e-007
1.78814e-007 1.3411e-007 1.04308e-007 0 7.45058e-008 6.70552e-008 6.70552e-008 5.58794e-007 3.05474e-007 2.16067e-007 1.71363e-007 1.49012e-007 1.2666e-007 1.11759e-007 1.04308e-007 1.04308e-007
//#define FLOATING
6.30584e-044 3.92364e-044 3.08286e-044 0 1.82169e-044 1.54143e-044 2.10195e-044 2.46842e-029 7.56701e-044 4.06377e-044 3.92364e-044 3.22299e-044 3.08286e-044 2.66247e-044 2.66247e-044 2.24208e-044
6.30584e-044 3.92364e-044 3.08286e-044 0 1.82169e-044 1.54143e-044 2.10195e-044 2.45208e-029 7.56701e-044 4.06377e-044 3.92364e-044 3.22299e-044 3.08286e-044 2.66247e-044 2.66247e-044 2.24208e-044
Lưu ý làm thế nào trong lần chạy thứ hai, các số rất gần với không.
Các số không chuẩn hóa thường rất hiếm và do đó hầu hết các bộ xử lý không cố gắng xử lý chúng một cách hiệu quả.
Để chứng minh rằng điều này có mọi thứ để làm với các số không chuẩn hóa, nếu chúng ta xóa các biến số thành 0 bằng cách thêm mã này vào đầu mã:
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
Sau đó, phiên bản với 0
không còn chậm hơn 10 lần và thực sự trở nên nhanh hơn. (Điều này yêu cầu mã được biên dịch với SSE được bật.)
Điều này có nghĩa là thay vì sử dụng các giá trị độ chính xác gần như thấp bằng 0 này, chúng ta chỉ làm tròn thành 0.
Thời gian: Core i7 920 @ 3.5 GHz:
// Don't flush denormals to zero.
0.1f: 0.564067
0 : 26.7669
// Flush denormals to zero.
0.1f: 0.587117
0 : 0.341406
Cuối cùng, điều này thực sự không liên quan gì đến việc đó là số nguyên hay dấu phẩy động. Các 0
hoặc 0.1f
được chuyển đổi / lưu trữ vào một bên ngoài đăng ký của cả hai vòng. Vì vậy, không có ảnh hưởng đến hiệu suất.