Tại sao Clang tối ưu hóa đi x * 1.0 nhưng KHÔNG x + 0,0?


125

Tại sao Clang tối ưu hóa vòng lặp trong mã này

#include <time.h>
#include <stdio.h>

static size_t const N = 1 << 27;
static double arr[N] = { /* initialize to zero */ };

int main()
{
    clock_t const start = clock();
    for (int i = 0; i < N; ++i) { arr[i] *= 1.0; }
    printf("%u ms\n", (unsigned)(clock() - start) * 1000 / CLOCKS_PER_SEC);
}

Nhưng không phải là vòng lặp trong mã này?

#include <time.h>
#include <stdio.h>

static size_t const N = 1 << 27;
static double arr[N] = { /* initialize to zero */ };

int main()
{
    clock_t const start = clock();
    for (int i = 0; i < N; ++i) { arr[i] += 0.0; }
    printf("%u ms\n", (unsigned)(clock() - start) * 1000 / CLOCKS_PER_SEC);
}

(Gắn thẻ là cả C và C ++ vì tôi muốn biết câu trả lời có khác nhau không.)


2
Những cờ tối ưu hóa hiện đang hoạt động?
Idillotexist Idillotexist

1
@IwillnotexistIdonotexist: Tôi mới sử dụng -O3, tôi không biết cách kiểm tra những gì kích hoạt mặc dù.
dùng541686

2
Sẽ rất thú vị để xem điều gì sẽ xảy ra nếu bạn thêm -ffast-math vào dòng lệnh.
cắm vào

static double arr[N]không được phép trong C; constcác biến không được tính là biểu thức không đổi trong ngôn ngữ đó
MM

1
[Chèn nhận xét
lén lút

Câu trả lời:


164

Tiêu chuẩn IEEE 754-2008 về Số học dấu phẩy động và Tiêu chuẩn số học độc lập ngôn ngữ (LIA) ISO / IEC 10967, Phần 1 trả lời tại sao lại như vậy.

IEEE 754 § 6.3 Bit dấu

Khi một đầu vào hoặc kết quả là NaN, tiêu chuẩn này không diễn giải dấu hiệu của NaN. Tuy nhiên, lưu ý rằng các hoạt động trên chuỗi bit - copy, negate, abs, copySign - chỉ định bit dấu của kết quả NaN, đôi khi dựa trên bit dấu của toán hạng NaN. Tổng số vị từ logic cũng bị ảnh hưởng bởi bit dấu của toán hạng NaN. Đối với tất cả các hoạt động khác, tiêu chuẩn này không chỉ định bit dấu của kết quả NaN, ngay cả khi chỉ có một NaN đầu vào hoặc khi NaN được tạo ra từ một hoạt động không hợp lệ.

Khi cả đầu vào và kết quả đều không phải là NaN, dấu hiệu của sản phẩm hoặc thương số là HOẶC độc quyền của dấu hiệu toán hạng; dấu của một tổng, hoặc của một sự khác biệt x - y được coi là một tổng x + (y), khác với nhiều nhất là một trong các dấu phụ; và dấu hiệu của kết quả của các chuyển đổi, hoạt động lượng tử hóa, các hoạt động RoundTo-Integral và roundToIntegralExact (xem 5.3.1) là dấu hiệu của toán hạng đầu tiên hoặc duy nhất. Các quy tắc này sẽ được áp dụng ngay cả khi toán hạng hoặc kết quả bằng 0 hoặc vô hạn.

Khi tổng của hai toán hạng có dấu ngược nhau (hoặc chênh lệch của hai toán hạng có dấu giống nhau) chính xác bằng 0, dấu của tổng đó (hoặc chênh lệch) sẽ là +0 trong tất cả các thuộc tính hướng làm tròn trừ roundTowardNegative; trong thuộc tính đó, dấu của tổng bằng 0 (hoặc chênh lệch) chính xác sẽ là −0. Tuy nhiên, x + x = x - (x) vẫn giữ nguyên dấu như x ngay cả khi x bằng không.

Trường hợp bổ sung

Dưới chế độ làm tròn mặc định (Round-to-gần, Ties-to-Thậm chí) , chúng ta thấy rằng x+0.0sản xuất x, trừ khi xđược -0.0: Trong trường hợp đó chúng ta có một tổng của hai toán hạng có dấu hiệu ngược lại có tổng bằng không, và §6.3 đoạn 3 quy tắc bổ sung này tạo ra +0.0.

Kể từ khi +0.0không Bitwise giống hệt với bản gốc -0.0, và đó -0.0là một giá trị hợp pháp có thể xảy ra như là đầu vào, trình biên dịch có nghĩa vụ để đưa vào mã mà sẽ làm thay đổi số không tiêu cực tiềm năng +0.0.

Tóm tắt: Trong chế độ làm tròn mặc định, trong x+0.0, nếux

  • là không -0.0 , sau đó xbản thân nó là một giá trị đầu ra chấp nhận được.
  • -0.0 , sau đó giá trị đầu ra phải là +0.0 , không giống hệt bit -0.0.

Trường hợp nhân

Trong chế độ làm tròn mặc định , không có vấn đề như vậy xảy ra với x*1.0. Nếu x:

  • là một số (phụ) bình thường, x*1.0 == xluôn luôn.
  • +/- infinity, sau đó kết quả là +/- infinitycùng một dấu hiệu.
  • NaN, sau đó theo

    Tuyên truyền của IEEE 754 § 6.2.3 NaN

    Một hoạt động truyền một toán hạng NaN đến kết quả của nó và có một NaN duy nhất làm đầu vào sẽ tạo ra một NaN với tải trọng của NaN đầu vào nếu có thể biểu thị ở định dạng đích.

    có nghĩa là số mũ và mantissa (mặc dù không phải là dấu hiệu) NaN*1.0được khuyến nghị không thay đổi so với đầu vào NaN. Dấu hiệu không được chỉ định theo §6.3p1 ở trên, nhưng việc triển khai có thể chỉ định nó giống hệt với nguồn NaN.

  • +/- 0.0, sau đó kết quả là a 0với bit dấu XOR của nó với bit dấu của 1.0, phù hợp với §6.3p2. Vì bit dấu của 1.00, giá trị đầu ra không thay đổi so với đầu vào. Vì vậy, x*1.0 == xngay cả khi xlà một (âm) bằng không.

Trường hợp phép trừ

Trong chế độ làm tròn mặc định , phép trừ x-0.0cũng là không có, vì nó tương đương với x + (-0.0). Nếu x

  • NaN, sau đó §6.3p1 và §6.2.3 áp dụng theo cách tương tự như đối với phép cộng và phép nhân.
  • +/- infinity, sau đó kết quả là +/- infinitycùng một dấu hiệu.
  • là một số (phụ) bình thường, x-0.0 == xluôn luôn.
  • -0.0, sau đó bởi §6.3p2, chúng ta có " [...] dấu của một tổng, hoặc của một sự khác biệt x - y được coi là một tổng x + (y), khác với nhiều nhất là một trong các dấu cộng; ". Điều này buộc chúng ta phải gán -0.0như là kết quả của (-0.0) + (-0.0), bởi vì -0.0khác nhau về dấu hiệu từ không có phần bổ sung nào, trong khi +0.0khác về dấu hiệu của hai phần bổ sung, vi phạm điều khoản này.
  • +0.0, sau đó điều này giảm xuống trường hợp bổ sung (+0.0) + (-0.0)được xem xét ở trên trong Trường hợp bổ sung , mà theo §6.3p3 được quy định để đưa ra +0.0.

Vì đối với tất cả các trường hợp, giá trị đầu vào là hợp pháp như đầu ra, cho phép xem xét x-0.0không có giá trị và x == x-0.0tautology.

Tối ưu hóa thay đổi giá trị

Tiêu chuẩn IEEE 754-2008 có trích dẫn thú vị sau:

IEEE 754 § 10,4 Nghĩa đen và tối ưu hóa thay đổi giá trị

[...]

Các phép biến đổi thay đổi giá trị sau đây, trong số các phép biến đổi khác, bảo tồn nghĩa đen của mã nguồn:

  • Áp dụng thuộc tính nhận dạng 0 + x khi x không bằng 0 và không phải là NaN báo hiệu và kết quả có cùng số mũ với x.
  • Áp dụng thuộc tính nhận dạng 1 × x khi x không phải là NaN báo hiệu và kết quả có cùng số mũ với x.
  • Thay đổi tải trọng hoặc ký bit của một NaN yên tĩnh.
  • [...]

Vì tất cả các NaN và tất cả các số nguyên đều có cùng số mũ và kết quả được làm tròn chính xác x+0.0x*1.0cho hữu hạn xcó cùng độ lớn như nhau x, nên số mũ của chúng là như nhau.

sNaNs

NaN báo hiệu là các giá trị bẫy dấu phẩy động; Chúng là các giá trị NaN đặc biệt có sử dụng làm toán hạng dấu phẩy động dẫn đến ngoại lệ hoạt động không hợp lệ (SIGFPE). Nếu một vòng lặp kích hoạt một ngoại lệ được tối ưu hóa, phần mềm sẽ không còn hoạt động như vậy nữa.

Tuy nhiên, như user2357112 chỉ ra trong các bình luận , Tiêu chuẩn C11 rõ ràng không xác định hành vi báo hiệu NaNs ( sNaN), do đó trình biên dịch được phép giả sử chúng không xảy ra và do đó, các trường hợp ngoại lệ mà chúng nêu ra cũng không xảy ra. Tiêu chuẩn C ++ 11 bỏ qua mô tả một hành vi để báo hiệu NaN, và do đó cũng không xác định được nó.

Chế độ làm tròn

Trong các chế độ làm tròn thay thế, tối ưu hóa cho phép có thể thay đổi. Chẳng hạn, trong chế độ Round-to-Negative-Infinity , việc tối ưu hóa x+0.0 -> xtrở nên cho phép, nhưng x-0.0 -> xbị cấm.

Để ngăn GCC giả định các chế độ và hành vi làm tròn mặc định, cờ thử nghiệm -frounding-mathcó thể được chuyển cho GCC.

Phần kết luận

Clang và GCC , ngay cả tại -O3, vẫn tuân thủ chuẩn IEEE-754. Điều này có nghĩa là nó phải tuân theo các quy tắc trên của tiêu chuẩn IEEE-754. x+0.0không chút giống hệt nhau để xcho tất cả xdưới những quy tắc, nhưng x*1.0 có thể được chọn để được như vậy : Cụ thể, khi chúng ta

  1. Tuân theo khuyến nghị để không thay đổi tải trọng xkhi nó là NaN.
  2. Để lại dấu bit của kết quả NaN không thay đổi * 1.0.
  3. Tuân theo để XOR bit dấu trong một thương / sản phẩm, khi xkhông một NaN.

Để kích hoạt tối ưu hóa không an toàn của IEEE-754 (x+0.0) -> x, cờ -ffast-mathcần được chuyển đến Clang hoặc GCC.


2
Hãy cẩn thận: nếu đó là một NaN báo hiệu thì sao? (Tôi thực sự nghĩ rằng đó có thể là lý do bằng cách nào đó, nhưng tôi thực sự không biết làm thế nào, vì vậy tôi đã hỏi.)
user541686

6
@Mehrdad: Phụ lục F, phần (tùy chọn) của tiêu chuẩn C chỉ định tuân thủ C với IEEE 754, rõ ràng không bao gồm các NaN báo hiệu. (C11 F.2.1., Dòng đầu tiên: "Thông số kỹ thuật này không xác định hành vi báo hiệu NaN.") Việc triển khai tuyên bố tuân thủ Phụ lục F vẫn miễn phí để thực hiện những gì họ muốn với tín hiệu NaN. Chuẩn C ++ có cách xử lý riêng của IEEE 754, nhưng dù nó là gì (tôi không quen), tôi nghi ngờ nó cũng chỉ định hành vi báo hiệu NaN.
user2357112 hỗ trợ Monica

2
@Mehrdad: sNaN gọi hành vi không xác định theo tiêu chuẩn (nhưng có lẽ nó được xác định rõ bởi nền tảng) vì vậy cho phép trình biên dịch nén ở đây được cho phép.
Joshua

1
@ user2357112: Khả năng bẫy lỗi là tác dụng phụ đối với các tính toán không được sử dụng thường can thiệp vào rất nhiều tối ưu hóa; nếu kết quả của phép tính đôi khi bị bỏ qua, trình biên dịch có thể trì hoãn tính toán một cách hữu ích cho đến khi nó có thể biết liệu kết quả sẽ được sử dụng hay không, nhưng nếu phép tính sẽ tạo ra tín hiệu quan trọng, điều đó có thể xấu.
supercat

2
Hãy nhìn xem, một câu hỏi áp dụng hợp pháp cho cả C và C ++ được trả lời chính xác cho cả hai ngôn ngữ bằng cách tham chiếu đến một tiêu chuẩn duy nhất. Điều này sẽ khiến mọi người ít phàn nàn về các câu hỏi được gắn thẻ cả C và C ++, ngay cả khi câu hỏi liên quan đến tính phổ biến ngôn ngữ? Đáng buồn thay, tôi nghĩ là không.
Kyle Strand

35

x += 0.0không phải là NOOP nếu x-0.0. Mặc dù vậy, trình tối ưu hóa có thể loại bỏ toàn bộ vòng lặp vì kết quả không được sử dụng. Nói chung, thật khó để nói tại sao một trình tối ưu hóa đưa ra quyết định.


2
Tôi thực sự đã đăng bài này sau khi tôi vừa đọc lý do tại sao x += 0.0không phải là không có, nhưng tôi nghĩ rằng có lẽ đó không phải là lý do bởi vì toàn bộ vòng lặp nên được tối ưu hóa theo bất kỳ cách nào. Tôi có thể mua nó, nó không hoàn toàn thuyết phục như tôi đã hy vọng ...
user541686

Với xu hướng cho các ngôn ngữ hướng đối tượng tạo ra các hiệu ứng phụ, tôi sẽ tưởng tượng rằng sẽ rất khó để chắc chắn rằng trình tối ưu hóa không thay đổi hành vi thực tế.
Robert Harvey

Có thể là lý do, vì long longtối ưu hóa có hiệu lực (đã làm với gcc, hoạt động tương tự ít nhất là gấp đôi )
e2-e4

2
@ ringø: long longlà một loại tích phân, không phải là loại IEEE754.
MSalters

1
Thế còn x -= 0, nó có giống nhau không?
Viktor Mellgren
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.