2016-06-20 20 views
5

Khi nhìn vào lắp ráp sản xuất bởi Visual Studio (2015U2) trong /O2 (release) chế độ tôi thấy rằng đây mảnh 'tay được tối ưu hóa' của mã C được dịch trở lại vào một phép nhân:x86_64: IMUL nhanh hơn 2x SHL + 2x ADD?

int64_t calc(int64_t a) { 
    return (a << 6) + (a << 16) - a; 
} 

hội:

imul  rdx,qword ptr [a],1003Fh 

vì vậy, tôi đã tự hỏi nếu đó là thực sự nhanh hơn làm nó theo cách nó được viết, một cái gì đó như:

mov   rbx,qword ptr [a] 
    mov   rax,rbx 
    shl   rax,6 
    mov   rcx,rbx 
    shl   rcx,10h 
    add   rax,rcx 
    sub   rax,rbx 

Tôi luôn luôn ấn tượng rằng phép nhân luôn luôn chậm hơn một vài ca/​​bổ sung? Điều đó không còn là trường hợp với bộ vi xử lý Intel x86_64 hiện đại?

+4

'IMUL' thường là thứ tự của độ trễ 3-4, thông lượng 1 trên mỗi đồng hồ. Vì vậy, yeah, có lẽ nhanh hơn. Cũng lưu ý ít nhất 3 lệnh cuối cùng của bạn phụ thuộc vào nhau để chính nó sẽ tạo ra một chuỗi phụ thuộc tương đương. – Jester

+0

Nó sẽ là tốt trong những ngày cũ: 8086 IMUL mất khoảng 100 đồng hồ, tùy thuộc vào chế độ địa chỉ, kích thước đăng ký, vv –

+1

Nếu bạn giả định tải từ bộ nhớ là miễn phí (vì bạn sẽ phải đợi nó * anyway *) và thanh ghi 'đăng ký' có thể được loại bỏ, 'shl' có thể thực thi song song, thì lý tưởng, mã dịch chuyển sẽ nhanh bằng nhân. Tuy nhiên, nó gunks lên rất nhiều đơn vị chức năng và nó nhiều hơn nữa i-cache. – EOF

Trả lời

10

Đúng vậy, các CPU x86 hiện đại (đặc biệt là Intel) có số nhân hiệu suất rất cao.
imul r, r/mimul r, r/m, imm đều có độ trễ 3 chu kỳ, mỗi lần truyền 1c trên Intel SnB-family và AMD Ryzen, ngay cả đối với kích thước toán hạng 64 bit.

Trong gia đình AMD Bulldozer, thời gian chờ là 4c hoặc 6c và một cho mỗi 2c hoặc một cho mỗi thông lượng 4c. (Thời gian chậm hơn cho kích thước và kích cỡ 64 bit).

Dữ liệu từ Agner Fog's instruction tables. Xem thêm các nội dung khác trong wiki thẻ .


Ngân sách transistor trong CPU hiện đại là khá lớn, và cho phép số lượng xử lý song song phần cứng cần thiết để làm một chút 64 nhân với độ trễ thấp như vậy. (It takes a lot of adders để tạo large fast multiplier).

Bị giới hạn bởi ngân sách điện, không phải ngân sách bán dẫn, có nghĩa là có phần cứng chuyên dụng cho nhiều chức năng khác nhau, miễn là chúng không thể chuyển đổi cùng một lúc (https://en.wikipedia.org/wiki/Dark_silicon). ví dụ. bạn không thể bão hòa đơn vị pext/pdep, hệ số nguyên và các đơn vị FMA vector cùng một lúc trên CPU Intel, bởi vì nhiều thiết bị trong số đó nằm trên cùng một cổng thực thi.

Thực tế thú vị: imul r64 cũng là 3c, vì vậy bạn có thể nhận được kết quả nhân 64 * 64 => 128b đầy đủ trong 3 chu kỳ. Tuy nhiên, imul r32 là độ trễ 4c và có thêm một uop. Tôi đoán là uop/chu kỳ phụ sẽ tách kết quả 64bit từ hệ số 64 bit thông thường thành hai nửa 32 bit.


Trình biên dịch thường tối ưu hóa cho độ trễ, và thường không biết làm thế nào để tối ưu hóa chuỗi phụ thuộc độc lập viết tắt của thông lượng so với chuỗi sự phụ thuộc loop-tiến dài mà nút cổ chai về độ trễ.

gcc và clang3.8 trở lên sử dụng tối đa hai hướng dẫn LEA thay vì imul r, r/m, imm. Tôi nghĩ gcc sẽ sử dụng imul nếu thay thế là 3 hoặc nhiều hướng dẫn hơn (không bao gồm mov).

Đó là lựa chọn điều chỉnh hợp lý, vì chuỗi lệnh 3 sẽ có cùng chiều dài với imul trên Intel. Sử dụng hai hướng dẫn 1 chu kỳ sẽ chi tiêu một số tiền phụ trội để rút ngắn độ trễ theo 1 chu kỳ.

clang3.7 và trước đó có xu hướng ưu tiên imul ngoại trừ các số nhân chỉ yêu cầu một LEA hoặc ca. Vì vậy, clang gần đây đã thay đổi để tối ưu hóa cho độ trễ thay vì thông lượng cho nhân bằng các hằng số nhỏ. (Hoặc có thể vì các lý do khác, như không cạnh tranh với những thứ khác chỉ trên cùng một cổng với hệ số nhân.)

ví dụ: this code on the Godbolt compiler explorer:

int foo (int a) { return a * 63; } 
    # gcc 6.1 -O3 -march=haswell (and clang actually does the same here) 
    mov  eax, edi # tmp91, a 
    sal  eax, 6 # tmp91, 
    sub  eax, edi # tmp92, a 
    ret 

clang3.8 và sau đó tạo cùng mã.

+0

Liệu nó có nghĩa là một 'imul', trong khi nhanh hơn, có khả năng tiêu thụ nhiều năng lượng hơn 4 op ALU (vì một hệ số kích hoạt nhiều bóng bán dẫn hơn)? – rustyx

+2

@rustyx: IDK. Theo dõi 4 ALU uops thông qua các đường ống dẫn và chuyển tiếp kết quả của họ với nhau có nhiều năng lượng hơn so với theo dõi một, và đó cũng có thể chi phí nhiều hơn hoạt động nhân! Thực hiện out-of-order là tốn kém. Tôi ước mình có một số ý tưởng về các con số, vì vậy tôi có thể đoán được câu trả lời, nhưng tôi đã không thực hiện bất kỳ sự tối ưu hóa nào (ngoài các công cụ hiển nhiên như sử dụng opec vector 128 bit khi tôi không cần 128 phần trên của kết quả). –

+1

@rustyx: BTW, hệ số độ trễ cao sử dụng ít bóng bán dẫn hơn có thể vẫn có năng lượng tương tự trên mỗi chi phí nhân vì nó vẫn phải cộng tất cả các khoản tiền một phần. Nó chỉ mất nhiều chu kỳ. (Đây là một sự đơn giản hóa quá mức, và có thể thực sự sai. Tôi không phải là một gã logic cổng, và không thực sự đọc lại những liên kết đó một cách chi tiết). Một hệ số độ trễ thấp làm cho nó hấp dẫn sử dụng nó thường xuyên hơn, mặc dù (ví dụ: thay vì hai lệnh 'lea' để nhân với 10). –

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