5

Phân tách số nguyên / và modulo % hoạt động thường được sử dụng cùng nhau trong lập trình, đôi khi ngay cả trên cùng toán hạng và các dòng tiếp theo. Ví dụ, hàm C sau đây, mà là một chức năng đơn giản mà tóm tắt kết quả của một / của 2 số với kết quả của % của họ, không chỉ rằng:Tối ưu hóa các cuộc gọi tiếp theo đến số nguyên và modulo (phần còn lại)

int sum2digits(int x, int base) { 
    int n, m; 
    n = x/base; 
    m = x % base; 
    return n + m; 
} 

Như tôi biết, cả hai /% là được thực hiện bởi cùng một lệnh máy (trong x86). Giả sử, nếu bạn thực hiện lệnh máy để chia số nguyên (div hoặc idiv) của hai số, ab, thì sau đó giá trị a/b sẽ được lưu trữ trong EAX đăng ký và số còn lại a % b trong EDX.
Tôi tự hỏi liệu trình biên dịch có tận dụng được chất lượng này và xem xét mã lắp ráp hay không. Nó chỉ ra rằng biên soạn bình thường với gcc không tối ưu hóa này:

push %rbp 
mov %rsp,%rbp 
mov %edi,-0x14(%rbp) 
mov %esi,-0x18(%rbp) 
mov -0x14(%rbp),%eax 
mov %eax,%edx 
sar $0x1f,%edx 
idivl -0x18(%rbp) 
mov %eax,-0x8(%rbp) 
mov -0x14(%rbp),%eax 
mov %eax,%edx 
sar $0x1f,%edx 
idivl -0x18(%rbp) 
mov %edx,-0x4(%rbp) 
mov -0x4(%rbp),%eax 
mov -0x8(%rbp),%edx 
add %edx,%eax 
pop %rbp 
retq 

mã lắp ráp này 2 cuộc gọi tiếp theo để idivl, nhưng mỗi lần đọc kết quả từ đăng ký khác (EAX cho thương, EDX cho phần còn lại). Tuy nhiên, biên soạn với -O thay đổi hình ảnh:

mov %edi,%eax 
mov %edi,%edx 
sar $0x1f,%edx 
idiv %esi 
add %edx,%eax 
retq 

Mã này gọi idiv chỉ một lần, và sử dụng giá trị của nó cho cả tính toán.
Tại sao loại tối ưu hóa này không được mặc định? Việc sử dụng gọi div hai lần liên tiếp là gì? Tối ưu hóa này có thể thay đổi hành vi của một chương trình theo bất kỳ cách nào không? Ngoài ra, và có lẽ quan trọng hơn, là có cách nào, với tư cách là lập trình viên, để trích xuất thủ công 2 giá trị này (thương và phần dư) đảm bảo rằng chỉ có 1 phân chia số nguyên được thực hiện bởi CPU?

+7

Theo mặc định, GCC vô hiệu hóa tất cả các tối ưu hóa *** trừ khi bạn chỉ định '-O'. Không chỉ cái này ... – Mysticial

+0

Tôi đã xóa câu trả lời của mình, vì tôi không thể làm cho nó hoạt động. Mặt khác, nếu bạn nói 'return x/base + x% base;' và bật tối ưu hóa trình biên dịch, bạn sẽ nhận được việc thực hiện hiệu quả. –

+1

Với GCC bạn có thể sử dụng lắp ráp nội tuyến để đảm bảo rằng bạn nhận được cả hai kết quả từ một bộ phận, nhưng tôi không thể bị làm phiền để tìm kiếm các chi tiết, và tôi đã không sử dụng lắp ráp nội tuyến trong một thời gian ... –

Trả lời

3

Tại sao loại tối ưu hóa này không được mặc định?

Nếu trình biên dịch và trình tối ưu hóa hoàn hảo và trình gỡ rối có thể đảo ngược kỹ sư mã, tối ưu hóa sẽ là mặc định chung. Nhưng các trình biên dịch không phải lúc nào cũng tạo mã đúng, các trình tối ưu hóa không phải lúc nào cũng bảo toàn ngữ nghĩa, và các trình gỡ rối không thể luôn tìm ra phần nào của một chương trình được tối ưu hóa mà bất kỳ lệnh nào có liên quan đến. Dường như trình biên dịch của bạn đã được cài đặt với các tùy chọn mặc định được đặt trước cho độ an toàn tuyệt đối và gỡ lỗi đơn giản.

có cách nào để trích xuất thủ công 2 giá trị này (thương mại và phần còn lại) đảm bảo rằng chỉ có 1 phân chia số nguyên được thực hiện bởi CPU không?

Những ngày này cách tốt nhất là chính xác những gì bạn đã làm: yêu cầu trình biên dịch mã tối ưu hóa. Các thói quen div là một holdover từ ngày khi kết quả từ các nhà khai thác phân chia được thực hiện được xác định cho số âm và tối ưu hóa biên dịch đã rất đau đớn chậm mà xác định những thứ như thế này được thực hiện tốt hơn bằng tay.

2

Bạn luôn có thể thực hiện phân chia của riêng bạn:

#include <stdlib.h> 
#include <stdio.h> 

void mydiv(int dividend, int divisor, int* quotient, int* remainder) 
{ 
    *quotient = dividend/divisor; 
    *remainder = dividend - *quotient * divisor; 
} 

int testData[][2] = 
{ 
    { +5, +3 }, 
    { +5, -3 }, 
    { -5, +3 }, 
    { -5, -3 }, 
}; 

int main(void) 
{ 
    unsigned i; 
    for (i = 0; i < sizeof(testData)/sizeof(testData[0]); i++) 
    { 
    div_t res1, res2; 
    res1 = div(testData[i][0], testData[i][1]); 
    mydiv(testData[i][0], testData[i][1], &res2.quot, &res2.rem); 
    printf("%+d/%+d = %+d:%+d %c= %+d:%+d\n", 
      testData[i][0], testData[i][1], 
      res1.quot, res1.rem, 
      "!="[res1.quot == res2.quot && res1.rem == res2.rem], 
      res2.quot, res2.rem); 
    } 
    return 0; 
} 

Output (ideone):

+5/+3 = +1:+2 == +1:+2 
+5/-3 = -1:+2 == -1:+2 
-5/+3 = -1:-2 == -1:-2 
-5/-3 = +1:-2 == +1:-2 

này có một bộ phận. Tuy nhiên, có vẻ như gcc không đủ thông minh để loại bỏ phép nhân và do đó bạn có một trong số đó.

1

Tại sao không chỉ sử dụng div?

http://www.cplusplus.com/reference/cstdlib/div/

tôi muốn nói đó là cơ hội tốt nhất cho một giải pháp di động tối ưu?

ĐẾN NGƯỜI BẮT BUỘC TRẢ LỜI CỦA TÔI: Vui lòng không xóa câu trả lời này! Bỏ phiếu xuống nếu bạn phải, hoặc bình luận về lý do tại sao nó sai đến mức bạn nghĩ rằng nó cần phải bị xóa.

Các OP muốn biết về Tối ưu hóa các cuộc gọi tiếp theo để nguyên bộ phận và modulo (còn lại) với C.

Vì vậy, nếu bạn muốn được tối ưu trong mã của bạn, tại sao không sử dụng một cuộc gọi thư viện tiêu chuẩn mà trì hoãn trách nhiệm của việc tối ưu hóa cho những người triển khai thư viện chuẩn có thể có thông tin tốt hơn về các hoạt động bên trong của trình biên dịch và các hoạt động có sẵn của máy nguyên bản (tức là sử dụng lệnh assembly div trên x86). Đặc biệt là khi chức năng thực hiện những gì OP đang cố gắng làm.

Nếu tôi thấy một bộ phận theo sau là một mod trong mã thực tế, câu hỏi trước mắt của tôi sẽ là "tại sao bạn không sử dụng thư viện chuẩn?", Không phải "hmm, tôi tự hỏi làm thế nào trình biên dịch có thể tối ưu hóa cao của tôi -level code? ".

Nó cũng trả lời một phần của câu hỏi: Ngoài ra, và thậm chí quan trọng hơn, có cách nào đó, là một lập trình viên, để trích xuất thủ công 2 giá trị này (thương và phần dư) đảm bảo rằng chỉ có 1 phân chia số nguyên được thực hiện bởi CPU?

+0

Josh, Tôi đã upvoted câu trả lời của bạn - mặc dù bạn có lẽ không còn khó chịu 18 tháng sau ... Tôi đã đến đây tìm kiếm nói về 'div (.,.)'. Tôi rất cool với những lập luận rằng các trình biên dịch được tối ưu hóa hiện đại làm cho nó khá/có khả năng không cần thiết nhưng nó ở đó và nó không có hại. Bạn đã trả lời câu hỏi gốc. Tôi thích câu trả lời cho biết 'đây là câu trả lời của bạn nhưng làm thế nào về những suy nghĩ khác có thể hữu ích'. Tôi cảm thấy khó chịu với 'Bạn không muốn làm điều đó' hay 'Tại sao bạn lại làm thế?' câu trả lời trừ khi những gì bạn đang làm là tích cực xấu. – Persixty

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