Câu hỏi:
Sự đồng thuận của ngành công nghiệp phần mềm là mã sạch và đơn giản là nền tảng cho khả năng tồn tại lâu dài của cơ sở mã và tổ chức sở hữu nó. Các đặc tính này dẫn đến chi phí bảo trì thấp hơn và tăng khả năng cơ sở mã được tiếp tục.
Tuy nhiên, mã SIMD khác với mã ứng dụng chung và tôi muốn biết liệu có sự đồng thuận tương tự liên quan đến mã sạch và đơn giản áp dụng cụ thể cho mã SIMD hay không.
Bối cảnh cho câu hỏi của tôi.
Tôi viết nhiều mã SIMD (một lệnh, nhiều dữ liệu) cho các tác vụ phân tích và xử lý ảnh khác nhau. Gần đây tôi cũng đã chuyển một số lượng nhỏ các chức năng này từ một kiến trúc (SSE2) sang một kiến trúc khác (ARM NEON).
Mã được viết cho phần mềm được thu nhỏ, do đó, nó không thể phụ thuộc vào các ngôn ngữ độc quyền mà không có quyền phân phối lại không bị hạn chế như MATLAB.
Một ví dụ về cấu trúc mã điển hình:
- Sử dụng loại ma trận của OpenCV (
Mat
) cho tất cả quản lý bộ nhớ, bộ đệm và trọn đời. - Sau khi kiểm tra kích thước (kích thước) của các đối số đầu vào, các con trỏ tới địa chỉ bắt đầu của từng hàng pixel được lấy.
- Số lượng pixel và địa chỉ bắt đầu của từng hàng pixel từ mỗi ma trận đầu vào được truyền vào một số hàm C ++ cấp thấp.
- Các hàm C ++ cấp thấp này sử dụng nội tại SIMD (cho Kiến trúc Intel và ARM NEON ), tải từ và lưu vào địa chỉ con trỏ thô.
- Đặc điểm của các hàm C ++ cấp thấp này:
- Độc quyền một chiều (liên tiếp trong bộ nhớ)
- Không đối phó với phân bổ bộ nhớ.
(Mọi phân bổ, bao gồm cả thời gian, được xử lý bởi mã bên ngoài bằng cách sử dụng các tiện ích OpenCV.) - Phạm vi độ dài tên của các ký hiệu (nội tại, tên biến, v.v.) là khoảng 10 - 20 ký tự, điều này là quá nhiều.
(Đọc như kỹ thuật bập bẹ.) - Việc tái sử dụng các biến SIMD không được khuyến khích vì trình biên dịch khá lỗi trong việc phân tích cú pháp mã chính xác không được viết theo kiểu mã hóa "một lần gán".
(Tôi đã nộp một số báo cáo lỗi trình biên dịch.)
Những khía cạnh nào của lập trình SIMD sẽ khiến cuộc thảo luận khác với trường hợp chung? Hoặc, tại sao SIMD khác nhau?
Về chi phí phát triển ban đầu
- Điều nổi tiếng là chi phí phát triển ban đầu của mã SIMD C ++ với hiệu suất tốt là khoảng 10 - 100 lần (với biên độ rộng) so với mã C ++ được viết ngẫu nhiên .
- Như đã lưu ý trong các câu trả lời cho việc Chọn giữa hiệu năng và mã có thể đọc / sạch hơn? , hầu hết mã (bao gồm mã được viết ngẫu nhiên và mã SIMD) ban đầu không sạch cũng không nhanh .
- Các cải tiến tiến hóa về hiệu suất mã (ở cả mã vô hướng và mã SIMD) không được khuyến khích (vì nó được xem như là một loại phần mềm làm lại ), và chi phí và lợi ích không được theo dõi.
Về mặt xu hướng
(ví dụ: nguyên tắc Pareto, hay còn gọi là quy tắc 80-20 )
- Ngay cả khi xử lý hình ảnh chỉ bao gồm 20% hệ thống phần mềm (cả về kích thước mã và chức năng), việc xử lý hình ảnh tương đối chậm (khi được xem là phần trăm thời gian CPU đã sử dụng), chiếm hơn 80% thời gian.
- Điều này là do hiệu ứng kích thước dữ liệu: Kích thước hình ảnh điển hình được đo bằng megabyte, trong khi kích thước điển hình của dữ liệu không phải hình ảnh được đo bằng kilobyte.
- Trong mã xử lý hình ảnh, một lập trình viên SIMD được đào tạo để tự động nhận ra mã 20% bao gồm các điểm nóng bằng cách xác định cấu trúc vòng lặp trong mã C ++. Do đó, từ quan điểm của một lập trình viên SIMD, 100% "mã quan trọng" là nút cổ chai hiệu năng.
- Thông thường trong một hệ thống xử lý hình ảnh, nhiều điểm nóng tồn tại và chiếm tỷ lệ thời gian tương đương. Ví dụ: có thể có 5 điểm nóng, mỗi điểm chiếm (20%, 18%, 16%, 14%, 12%) trong tổng thời gian. Để đạt được hiệu suất cao, tất cả các điểm nóng cần phải được viết lại trong SIMD.
- Điều này được tóm tắt là quy tắc bật bóng bay: một quả bóng bay không thể được bật hai lần.
- Giả sử có một số bóng bay, nói 5 trong số chúng. Cách duy nhất để giải mã chúng là bật chúng từng cái một.
- Khi quả bóng đầu tiên được bật lên, 4 quả bóng còn lại hiện chiếm tỷ lệ cao hơn trong tổng thời gian thực hiện.
- Để kiếm thêm lợi nhuận, người ta phải bật một quả bóng khác.
(Điều này trái với quy tắc tối ưu hóa 80-20: kết quả kinh tế tốt có thể đạt được sau khi 20% trái cây treo thấp nhất đã được chọn.)
Về khả năng đọc và bảo trì
Mã SIMD rất khó đọc.
- Điều này đúng ngay cả khi người ta tuân theo mọi thực hành tốt nhất về kỹ thuật phần mềm, ví dụ như đặt tên, đóng gói, tính chính xác (và làm rõ tác dụng phụ), phân rã chức năng, v.v.
- Điều này đúng ngay cả với các lập trình viên SIMD có kinh nghiệm.
Mã SIMD tối ưu rất mâu thuẫn, (xem chú thích) so với mã nguyên mẫu C ++ tương đương của nó.
- Có nhiều cách để chống lại mã SIMD, nhưng chỉ 1 trong 10 lần thử như vậy sẽ đạt được kết quả nhanh chóng chấp nhận được.
- (Đó là, trong các giai điệu của hiệu suất tăng gấp 4 lần để chứng minh cho chi phí phát triển cao. Thậm chí mức tăng cao hơn đã được quan sát thấy trong thực tế.)
(Ghi chú)
Đây là luận điểm chính của dự án MIT Halide - trích dẫn nguyên văn tiêu đề của bài báo:
"thuật toán tách từ lịch trình để tối ưu hóa dễ dàng các đường ống xử lý hình ảnh"
Về khả năng ứng dụng phía trước
- Mã SIMD được gắn chặt với một kiến trúc duy nhất. Mỗi kiến trúc mới (hoặc mỗi lần mở rộng các thanh ghi SIMD) yêu cầu viết lại.
- Không giống như phần lớn phát triển phần mềm, mỗi đoạn mã SIMD thường được viết cho một mục đích duy nhất không bao giờ thay đổi.
(Ngoại trừ việc chuyển sang các kiến trúc khác.) - Một số kiến trúc duy trì khả năng tương thích ngược hoàn hảo (Intel); một số giảm xuống một lượng nhỏ (ARM AArch64, thay thế
vtbl
bằngvtblq
) nhưng đủ để khiến một số mã không thể biên dịch.
Về kỹ năng và đào tạo
- Không rõ những điều kiện tiên quyết về kiến thức là cần thiết để đào tạo một lập trình viên mới viết và duy trì mã SIMD.
- Sinh viên tốt nghiệp đại học đã học lập trình SIMD ở trường dường như coi thường và loại bỏ nó như là một sự nghiệp không thực tế.
- Đọc tách và đọc hồ sơ hiệu suất cấp thấp được trích dẫn là hai kỹ năng cơ bản để viết mã SIMD hiệu suất cao. Tuy nhiên, không rõ làm thế nào để đào tạo một cách có hệ thống các lập trình viên về hai kỹ năng này.
- Kiến trúc CPU hiện đại (phân kỳ đáng kể so với những gì được dạy trong sách giáo khoa) khiến cho việc đào tạo trở nên khó khăn hơn.
Về tính chính xác và chi phí liên quan đến khuyết tật
- Một chức năng xử lý SIMD duy nhất thực sự đủ gắn kết để người ta có thể thiết lập tính chính xác bằng cách:
- Áp dụng phương pháp chính thức (với bút và giấy) , và
- Xác minh phạm vi số nguyên đầu ra (với mã nguyên mẫu và được thực hiện ngoài thời gian chạy) .
- Tuy nhiên, quá trình xác minh rất tốn kém (dành 100% thời gian cho việc xem xét mã và 100% thời gian cho việc kiểm tra mô hình nguyên mẫu), làm tăng gấp ba chi phí phát triển vốn đã tốn kém của mã SIMD.
- Nếu một lỗi nào đó quản lý để vượt qua quá trình xác minh này, gần như không thể "sửa chữa" (sửa chữa) ngoại trừ thay thế (viết lại) chức năng bị nghi ngờ bị lỗi.
- Mã SIMD bị lỗi nghiêm trọng trong trình biên dịch C ++ (tối ưu hóa trình tạo mã).
- Mã SIMD được tạo bằng các mẫu biểu thức C ++ cũng bị ảnh hưởng rất nhiều từ các khiếm khuyết của trình biên dịch.
Về mặt đổi mới đột phá
Nhiều giải pháp đã được đề xuất từ các học viện, nhưng ít người đang thấy việc sử dụng thương mại rộng rãi.
- MIT Halide
- Phòng tối Stanford
- NT2 (Hộp công cụ mẫu số) và Boost.SIMD có liên quan
Các thư viện sử dụng thương mại rộng rãi dường như không hỗ trợ SIMD nhiều.
- Các thư viện nguồn mở có vẻ ấm áp với SIMD.
- Gần đây tôi có quan sát trực tiếp về điều này sau khi định hình một số lượng lớn các hàm API OpenCV, kể từ phiên bản 2.4.9.
- Nhiều thư viện xử lý hình ảnh khác mà tôi đã mô tả cũng không sử dụng SIMD nhiều hoặc họ bỏ lỡ các điểm nóng thực sự.
- Thư viện thương mại dường như tránh SIMD hoàn toàn.
- Trong một số trường hợp, tôi thậm chí đã thấy các thư viện xử lý hình ảnh hoàn nguyên mã được tối ưu hóa SIMD trong phiên bản trước thành mã không phải SIMD trong phiên bản mới hơn, dẫn đến hồi quy hiệu suất nghiêm trọng.
(Phản hồi của nhà cung cấp là cần thiết để tránh các lỗi biên dịch.)
- Trong một số trường hợp, tôi thậm chí đã thấy các thư viện xử lý hình ảnh hoàn nguyên mã được tối ưu hóa SIMD trong phiên bản trước thành mã không phải SIMD trong phiên bản mới hơn, dẫn đến hồi quy hiệu suất nghiêm trọng.
- Các thư viện nguồn mở có vẻ ấm áp với SIMD.
Câu hỏi của lập trình viên này: Mã độ trễ thấp đôi khi phải "xấu xí"? có liên quan, và trước đây tôi đã viết một câu trả lời cho câu hỏi đó để giải thích quan điểm của tôi vài năm trước.
Tuy nhiên, câu trả lời đó là khá nhiều "sự khuyến khích" đối với quan điểm "tối ưu hóa sớm", tức là theo quan điểm rằng:
- Tất cả các tối ưu hóa là sớm theo định nghĩa (hoặc, ngắn hạn theo bản chất ) và
- Tối ưu hóa duy nhất có lợi ích lâu dài là hướng tới sự đơn giản.
Nhưng quan điểm như vậy được tranh luận trong bài viết ACM này .
Tất cả điều đó khiến tôi phải hỏi:
mã SIMD khác với mã ứng dụng chung và tôi muốn biết liệu có sự đồng thuận trong ngành tương tự về giá trị của mã sạch và đơn giản cho mã SIMD hay không.