2015-05-21 21 views
7

tôi muốn để xem nếu GCC sẽ làm giảm a - (b - c)-(a + c) - b với số nguyên có chữ ký và unsigned vì vậy tôi tạo ra hai bài kiểm trađại số giảm các biểu thức số nguyên ký trong C/C++

//test1.c 
unsigned fooau(unsigned a, unsigned b, unsigned c) { return a - (b - c); } 
signed fooas(signed a, signed b, signed c) { return a - (b - c); } 
signed fooms(signed a) { return a*a*a*a*a*a; } 
unsigned foomu(unsigned a) { return a*a*a*a*a*a; } 

//test2.c 
unsigned fooau(unsigned a, unsigned b, unsigned c) { return (a + c) - b; } 
signed fooas(signed a, signed b, signed c) { return (a + c) - b; } 
signed fooms(signed a) { return (a*a*a)*(a*a*a); } 
unsigned foomu(unsigned a) { return (a*a*a)*(a*a*a); } 

tôi biên soạn đầu tiên với gcc -O3 test1.c test2.c -S và nhìn vào hội,, tổ hợp. Đối với cả hai thử nghiệm fooau giống hệt nhau tuy nhiên fooas thì không.

Theo như tôi hiểu số học unsigned thể được bắt nguồn từ the following formula

(a%n + b%n)%n = (a+b)%n 

mà có thể được sử dụng để chứng minh rằng số học unsigned là liên kết. Nhưng kể từ khi signed overflow is undefined behavior sự bình đẳng này không nhất thiết giữ cho việc bổ sung có chữ ký (tức là bổ sung đã ký không phải là kết hợp), giải thích tại sao GCC không giảm a - (b - c) thành (a + c) - b đối với các số nguyên đã ký. Nhưng chúng tôi có thể yêu cầu GCC sử dụng công thức này bằng cách sử dụng -fwrapv. Sử dụng tùy chọn này fooas cho cả hai bài kiểm tra là giống hệt nhau.

Nhưng còn về phép nhân thì sao? Đối với cả hai thử nghiệm foomsfoomu được đơn giản hóa thành ba phép nhân (a*a*a*a*a*a to (a*a*a)*(a*a*a)). Nhưng nhân có thể được viết như sau Ngoài ra lặp đi lặp lại nên sử dụng công thức trên, chúng tôi nghĩ rằng nó có thể được hiển thị mà

((a%n)*(b%n))%n = (a*b)%n 

mà tôi nghĩ cũng có thể cho thấy rằng unsigned nhân mô-đun là kết hợp là tốt. Nhưng kể từ khi GCC chỉ sử dụng ba phép nhân cho foomu điều này cho thấy rằng GCC giả định ký số nguyên nhân là kết hợp.

Điều này có vẻ như mâu thuẫn với tôi. Đối với số học đã ký không phải là kết hợp nhưng đối với phép nhân thì nó là.

Hai câu hỏi:

  1. Có đúng là bổ sung không được kết hợp với số nguyên ký nhưng nhân là trong C/C++?

  2. Nếu tràn đăng nhập được sử dụng để tối ưu hóa không phải là thực tế rằng GCC không làm giảm biểu thức đại số thất bại trong việc tối ưu hóa? Sẽ tốt hơn nếu tối ưu hóa để sử dụng -fwrapv (Tôi hiểu rằng a - (b - c) đến (a + c) - b không giảm nhiều nhưng tôi lo lắng về các trường hợp phức tạp hơn)? Điều này có nghĩa là tối ưu hóa đôi khi sử dụng -fwrapv có hiệu quả hơn và đôi khi không?

+1

Điều gì sẽ xảy ra với 'fooms' và' foomu' nếu bạn làm cho cơ thể 'a * a * a * a * a' - nghĩa là. một số ** số nhân ** lẻ? Họ vẫn tối ưu hóa giống nhau không? Với số chẵn, ký hiệu không liên quan vì kết quả sẽ luôn dương. – kdopen

+0

@kdopen, nó giống nhau: 'fooms' và' foomu' tạo cùng một mã và sử dụng 3 phép nhân cho 'a * a * a * a * a'. –

Trả lời

6
  1. Không, nhân không được kết hợp trong số nguyên ký kết. Hãy xem xét (0 * x) * x so với 0 * (x * x) - sau này có hành vi có khả năng không xác định trong khi hành vi trước luôn được xác định.

  2. Tiềm năng cho hành vi undefined duy nhất từng giới thiệu cơ hội tối ưu hóa mới, ví dụ điển hình là việc tối ưu hóa x + 1 > x để true cho ký x, tối ưu mà không có sẵn cho số nguyên unsigned.

Tôi không nghĩ bạn có thể giả định gcc không thay đổi a - (b - c) thành (a + c) - b đại diện cho cơ hội tối ưu hóa bị bỏ lỡ; hai phép tính này biên dịch thành hai hướng dẫn tương tự trên x86-64 (lealsubl), chỉ theo một thứ tự khác.

Thực tế, việc thực hiện được giả định rằng số học là kết hợp và sử dụng để tối ưu hóa vì mọi thứ có thể xảy ra trên UB bao gồm số học modulo hoặc số học vô hạn. Tuy nhiên, bạn là người lập trình không được quyền giả sử tính kết hợp trừ khi bạn có thể đảm bảo rằng không có kết quả trung gian nào bị tràn.

Ví dụ khác, hãy thử (a + a) - a - gcc sẽ tối ưu hóa điều này thành a cho chữ ký a cũng như cho chưa ký.

+0

Tại sao đầu tiên có khả năng UB? Vì có giá trị lớn? – gsamaras

+1

@ G.Samaras yes, 'x * x' có thể tràn. – ecatmur

+0

Với điểm GCC thứ hai của bạn không giảm 'a - (b - c)' thành '(a + c) - b' vì tràn cho ký là UB nhưng khi tôi sử dụng' -fwrapv' có nghĩa là tràn tràn được xác định hành vi nó giảm. Điều đó không có nghĩa là GCC đã tránh được một cơ hội tối ưu hóa do UB? –

2

Giảm đại số các biểu thức nguyên có chữ ký có thể được thực hiện miễn là nó có kết quả tương tự cho bất kỳ được xác định bộ đầu vào nào. Vì vậy, nếu các biểu hiện

a * a * a * a * a * a 

được xác định - có nghĩa là, a là đủ nhỏ mà không tràn ký xảy ra trong tính toán - sau đó bất kỳ tập kết của phép nhân sẽ tạo ra giá trị như nhau, bởi vì không có sản phẩm dưới sáu a s có thể tràn.

Điều tương tự cũng đúng với a + a + a + a + a + a.

Mọi thứ thay đổi nếu các biến nhân với (hoặc được thêm) không giống nhau hoặc nếu bổ sung được xen kẽ với phép trừ. Trong những trường hợp đó, việc tập hợp lại và sắp xếp lại tính toán có thể dẫn đến một luồng tràn đã ký không xảy ra trong tính toán kinh điển.

Ví dụ, đi biểu

a - (b - c) 

mặt đại số, đó là tương đương với

(a + c) - b 

Nhưng trình biên dịch không thể thực hiện sắp xếp lại đó bởi vì nó có thể là giá trị trung gian a+c sẽ tràn với đầu vào mà sẽ không gây ra một tràn trong bản gốc. Giả sử chúng tôi có a=INT_MAX-1; b=1; c=2; thì a+c dẫn đến tràn, nhưng a - (b - c) được tính là a - (-1), là INT_MAX, không bị tràn.

Nếu trình biên dịch có thể giả định rằng luồng tràn đã ký không bị bẫy mà thay vào đó được tính là modulo INT_MAX+1, thì có thể sắp xếp lại. Các tùy chọn -fwrapv cho phép gcc thực hiện giả định đó.

+0

Nhưng 'a * a * a * a * a * a' chắc chắn có thể tràn và' fooms' phải giả định bất kỳ 'a' và GCC nào làm giảm điều này ngay cả khi không có' -fwrapv'. –

+0

@ Zboson: GCC được cho phép giả định rằng 'a * a * a * a * a * a' sẽ không tràn, vì nếu nó tràn mà sẽ dẫn đến hành vi không xác định. Vì vậy, GCC chỉ cần đảm bảo tính nhất quán trong trường hợp không có tràn sẽ xảy ra với tính toán kinh điển '((((((* a) * a) * a) * a) * a) * a) '. Nếu không có tràn xảy ra trong trường hợp đó, không có tràn xảy ra trong tính toán '(((a * a) * a) * ((a * a) * a))', do đó, thay thế là hợp lệ. – rici

+0

Cảm ơn! Tôi nghĩ rằng tôi nhận được nó ngay bây giờ. Vì GCC có thể giả định tràn không xảy ra, nó có thể sử dụng số học phạm vi vô hạn nếu nó muốn có liên kết. Tôi vẫn đang học C. –

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