Đú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/m
và imul 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ẻ x86.
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ã.
'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
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 –
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