Hướng dẫn tối ưu hóa của Agner Fog là tuyệt vời. Anh ta có các hướng dẫn, bảng thời gian hướng dẫn và tài liệu về kiến trúc vi mô của tất cả các thiết kế CPU x86 gần đây (quay trở lại như Intel Pentium). Xem thêm một số tài nguyên khác được liên kết từ /programming//tags/x86/info
Để giải trí, tôi sẽ trả lời một số câu hỏi (số từ các CPU Intel gần đây). Lựa chọn ops không phải là yếu tố chính trong việc tối ưu hóa mã (trừ khi bạn có thể tránh phân chia.)
Là một nhân nhân chậm hơn trên CPU so với một bổ sung?
Có (trừ khi nó có sức mạnh bằng 2). (Độ trễ gấp 3-4 lần, chỉ với một thông lượng trên mỗi đồng hồ trên Intel.) Tuy nhiên, đừng đi quá xa để tránh điều đó, vì nó nhanh như 2 hoặc 3 lần thêm.
Chính xác các đặc điểm tốc độ của opcodes toán học và kiểm soát dòng cơ bản là gì?
Xem bảng hướng dẫn của Agner Fog và hướng dẫn kiến trúc vi mô nếu bạn muốn biết chính xác : P. Cẩn thận với những cú nhảy có điều kiện. Nhảy không điều kiện (như các cuộc gọi chức năng) có một số chi phí nhỏ, nhưng không nhiều.
Nếu hai opcodes có cùng số chu kỳ để thực thi, thì cả hai có thể được sử dụng thay thế cho nhau mà không có bất kỳ tăng / giảm hiệu suất nào không?
Không, họ có thể cạnh tranh cho cùng một cổng thực thi như một cái gì đó khác, hoặc họ có thể không. Nó phụ thuộc vào chuỗi phụ thuộc khác mà CPU có thể làm việc song song. (Trong thực tế, thường không có bất kỳ quyết định hữu ích nào được đưa ra. Đôi khi, bạn có thể sử dụng dịch chuyển vectơ hoặc xáo trộn vectơ chạy trên các cổng khác nhau trên CPU Intel. Nhưng thay đổi theo từng byte của toàn bộ thanh ghi ( PSLLDQ
v.v.) chạy trong đơn vị xáo trộn.)
Bất kỳ chi tiết kỹ thuật nào khác bạn có thể chia sẻ về hiệu suất CPU x86 đều được đánh giá cao
Các tài liệu vi mô của Agner Fog mô tả các đường ống của CPU Intel và AMD đủ chi tiết để tìm ra chính xác số vòng lặp cần thực hiện trong mỗi lần lặp và liệu nút cổ chai có thông lượng, chuỗi phụ thuộc hay tranh chấp cho một cổng thực thi hay không. Xem một số câu trả lời của tôi trên StackOverflow, như cái này hoặc cái này .
Ngoài ra, http://www.realworldtech.com/haswell-cpu/ (và tương tự cho các thiết kế trước đó) rất thú vị nếu bạn thích thiết kế CPU.
Đây là danh sách của bạn, được sắp xếp cho CPU Haswell, dựa trên các khách mời tốt nhất của tôi. Đây thực sự không phải là một cách suy nghĩ hữu ích về mọi thứ cho bất cứ điều gì ngoài việc điều chỉnh một vòng lặp asm. Hiệu ứng dự đoán bộ nhớ cache / chi nhánh thường chiếm ưu thế, vì vậy hãy viết mã của bạn để có các mẫu tốt. Các con số rất sóng và cố gắng tính toán độ trễ cao, ngay cả khi thông lượng không phải là vấn đề hoặc để tạo ra nhiều uops làm tắc nghẽn đường ống cho những điều khác xảy ra song song. Đặc biệt các số bộ đệm / chi nhánh rất được tạo ra. Các vấn đề về độ trễ đối với các phụ thuộc mang theo vòng lặp, các vấn đề thông lượng khi mỗi lần lặp là độc lập.
TL: DR những con số này được tạo thành dựa trên những gì tôi đang hình dung cho một trường hợp sử dụng "điển hình", cho đến khi đánh đổi giữa độ trễ, tắc nghẽn cổng thực thi và thông lượng mặt trước (hoặc quầy hàng cho những thứ như sai sót chi nhánh ). Vui lòng không sử dụng những con số này cho bất kỳ loại phân tích hoàn hảo nghiêm túc nào .
- 0,5 đến 1 Bitwise / Phép cộng / Phép trừ /
dịch chuyển và xoay (số đếm thời gian biên dịch) /
phiên bản vectơ của tất cả các phiên bản này (1 đến 4 trên mỗi chu kỳ, độ trễ 1 chu kỳ)
- 1 vectơ tối thiểu, tối đa, so sánh bằng, so sánh lớn hơn (để tạo mặt nạ)
- Xáo trộn 1,5 vector. Haswell và mới hơn chỉ có một cổng xáo trộn và đối với tôi, nó thường cần nhiều sự xáo trộn nếu bạn cần, vì vậy tôi cân nhắc nó cao hơn một chút để khuyến khích suy nghĩ về việc sử dụng ít xáo trộn hơn. Chúng không miễn phí, đặc biệt. nếu bạn cần một mặt nạ kiểm soát pshufb từ bộ nhớ.
- Tải 1,5 lần / lưu trữ (nhấn bộ đệm L1. Thông lượng tốt hơn độ trễ)
- Phép nhân số nguyên 1,75 (độ trễ 3c / một lần truy cập 1c trên Intel, 4c lat trên AMD và chỉ một lần cho mỗi lần truy cập 2c). Các hằng số nhỏ thậm chí còn rẻ hơn khi sử dụng LEA và / hoặc ADD / SUB / shift . Nhưng tất nhiên các hằng số thời gian biên dịch luôn tốt và thường có thể tối ưu hóa thành những thứ khác. (Và nhân trong một vòng lặp thường có thể được giảm sức mạnh bởi trình biên dịch thành
tmp += 7
trong một vòng lặp thay vì tmp = i*7
)
- 1,75 một số xáo trộn vector 256b (độ trễ thêm vào trong phần bên trong có thể di chuyển dữ liệu giữa các làn 128b của vectơ AVX). (Hoặc 3 đến 7 trên Ryzen, nơi các làn xáo trộn làn đường cần nhiều vòng lặp hơn)
- 2 fp add / sub (và các phiên bản vectơ giống nhau) (1 hoặc 2 cho mỗi thông lượng chu kỳ, độ trễ 3 đến 5 chu kỳ). Có thể chậm nếu bạn tắc nghẽn về độ trễ, ví dụ: tổng một mảng chỉ có 1
sum
biến. (Tôi có thể cân trọng lượng này và fp mul thấp đến 1 hoặc cao tới 5 tùy theo trường hợp sử dụng).
- 2 vector fp mul hoặc FMA. (x * y + z rẻ như một mul hoặc add nếu bạn biên dịch với hỗ trợ FMA được kích hoạt).
- 2 chèn / trích xuất các thanh ghi mục đích chung vào các phần tử vectơ (
_mm_insert_epi8
, v.v.)
- 2,25 vector int mul (các phần tử 16 bit hoặc pmaddubsw thực hiện 8 * 8 -> 16 bit). Rẻ hơn trên Skylake, với thông lượng tốt hơn so với mul vô hướng
- 2,25 thay đổi / xoay theo số lượng biến (độ trễ 2c, thông lượng trên mỗi 2c trên Intel, nhanh hơn trên AMD hoặc với BMI2)
- 2.5 So sánh mà không phân nhánh (
y = x ? a : b
, hoặc y = x >= 0
) ( test / setcc
hoặc cmov
)
- 3 int-> chuyển đổi float
- 3 dự đoán hoàn hảo Luồng điều khiển (dự đoán nhánh, gọi, trả lại).
- 4 vector int mul (phần tử 32 bit) (2 uops, độ trễ 10c trên Haswell)
- 4 số nguyên hoặc
%
theo hằng số thời gian biên dịch (không lũy thừa 2).
- 7 vectơ ngang (vd
PHADD
thêm các giá trị trong một vectơ)
- 11 (vector) Phòng FP (độ trễ 10-13c, một thông lượng trên 7c hoặc tệ hơn). (Có thể rẻ nếu được sử dụng hiếm khi, nhưng thông lượng kém hơn 6 đến 40 lần so với FP mul)
- 13? Kiểm soát lưu lượng (chi nhánh dự đoán kém, có thể dự đoán 75%)
- Phân chia 13 int ( vâng , thực sự , nó chậm hơn phân chia FP và không thể vector hóa). (lưu ý rằng trình biên dịch chia cho một hằng số bằng cách sử dụng mul / shift / add với hằng số ma thuật và div / mod theo lũy thừa là 2 rất rẻ.)
- 16 (vectơ) FP sqrt
- 25? tải (nhấn bộ đệm L3). (cửa hàng nhớ cache rẻ hơn tải.)
- 50? FP trig / exp / log. Nếu bạn cần nhiều exp / log và không cần độ chính xác đầy đủ, bạn có thể trao đổi độ chính xác cho tốc độ với đa thức ngắn hơn và / hoặc bảng. Bạn cũng có thể vector hóa SIMD.
- 50-80? chi nhánh luôn được dự đoán trước, chi phí 15-20 chu kỳ
- 200-400? tải / lưu trữ (nhớ cache)
- 3000 ??? đọc trang từ tệp (nhấn bộ đệm đĩa hệ điều hành) (tạo số ở đây)
- 20000 ??? trang đọc đĩa (bỏ lỡ bộ đệm đĩa hệ điều hành, SSD nhanh) (số hoàn toàn tạo thành)
Tôi hoàn toàn làm điều này dựa trên phỏng đoán . Nếu có gì đó không ổn, đó là do tôi đã nghĩ đến một trường hợp sử dụng khác hoặc lỗi chỉnh sửa.
Chi phí tương đối của mọi thứ trên CPU AMD sẽ tương tự nhau, ngoại trừ chúng có bộ dịch chuyển số nguyên nhanh hơn khi số lượng ca thay đổi. Các CPU gia đình AMD Bulldozer tất nhiên chậm hơn trên hầu hết các mã, vì nhiều lý do. (Ryzen khá giỏi trong nhiều thứ).
Hãy nhớ rằng thực sự không thể đun sôi mọi thứ với chi phí một chiều . Khác với lỗi nhớ cache và sai lệch nhánh, nút cổ chai trong một khối mã có thể là độ trễ, tổng thông lượng uop (frontend) hoặc thông lượng của một cổng cụ thể (cổng thực thi).
Một hoạt động "chậm" như phân chia FP có thể rất rẻ nếu mã xung quanh khiến CPU bận rộn với công việc khác . (vectơ div div hoặc sqrt là 1 uop mỗi cái, chúng chỉ có độ trễ và thông lượng kém. Chúng chỉ chặn đơn vị phân chia, không phải toàn bộ cổng thực thi mà nó bật. Số nguyên div là một số u.) Vì vậy, nếu bạn chỉ có một phân chia FP cứ sau 20 mul và thêm, và có một công việc khác để CPU thực hiện (ví dụ: lặp vòng lặp độc lập), thì "chi phí" của div FP có thể tương đương với một mul FP. Đây có lẽ là ví dụ tốt nhất về một cái gì đó có thông lượng thấp khi tất cả những gì bạn đang làm, nhưng kết hợp rất tốt với mã khác (khi độ trễ không phải là một yếu tố), vì tổng số uops thấp.
Lưu ý rằng phân chia số nguyên gần như không thân thiện với mã xung quanh: Trên Haswell, nó là 9 uops, với thông lượng một trên 8-11c và độ trễ 22-29c. (Phân chia 64 bit chậm hơn nhiều , ngay cả trên Skylake.) Vì vậy, độ trễ và số lượng thông lượng có phần giống với div div, nhưng div div chỉ là một uop.
Để biết ví dụ về việc phân tích một chuỗi ngắn các thông tin về thông lượng, độ trễ và tổng số vòng, hãy xem một số câu trả lời SO của tôi:
IDK nếu người khác viết câu trả lời SO bao gồm loại phân tích này. Tôi có một thời gian dễ dàng hơn để tìm kiếm chính mình, bởi vì tôi biết tôi thường đi sâu vào chi tiết này và tôi có thể nhớ những gì tôi đã viết.