Làm thế nào để ghi lại và dạy cho những người khác được tối ưu hóa ngoài việc nhận ra mã chuyên sâu tính toán?


11

Đôi khi, có 1% mã đủ chuyên sâu về mặt tính toán, cần loại tối ưu hóa mức độ thấp nhất. Ví dụ là xử lý video, xử lý hình ảnh và tất cả các loại xử lý tín hiệu, nói chung.

Các mục tiêu là ghi lại và dạy các kỹ thuật tối ưu hóa, để mã không trở nên không thể nhầm lẫn và dễ bị loại bỏ bởi các nhà phát triển mới hơn. (*)

(*) Mặc dù có khả năng tối ưu hóa cụ thể là hoàn toàn vô dụng trong một số CPU trong tương lai không lường trước được, do đó mã sẽ bị xóa bằng mọi cách.

Xem xét rằng các dịch vụ phần mềm (thương mại hoặc nguồn mở) vẫn giữ được lợi thế cạnh tranh của chúng bằng cách có mã nhanh nhất và sử dụng kiến ​​trúc CPU mới nhất, người viết phần mềm thường cần phải điều chỉnh mã của mình để làm cho nó chạy nhanh hơn trong khi có cùng một đầu ra nhiệm vụ, danh sách cho phép một số lượng nhỏ các lỗi làm tròn.

Thông thường, một người viết phần mềm có thể giữ nhiều phiên bản của hàm làm tài liệu cho mỗi lần viết lại tối ưu hóa / thuật toán diễn ra. Làm thế nào để làm cho các phiên bản này có sẵn cho người khác nghiên cứu các kỹ thuật tối ưu hóa của họ?

Liên quan:


1
Bạn chỉ có thể giữ các phiên bản khác nhau trong mã, nhận xét, với nhiều bình luận cho người đọc biết điều gì đang xảy ra.
Mike Dunlavey

1
Và đừng chỉ nói cho họ biết mã đang làm gì, nhưng tại sao nó lại nhanh hơn như vậy. Bao gồm các liên kết đến các thuật toán nếu cần, cả tài liệu, wiki, tài liệu hoặc tài nguyên có sẵn trên internet của bạn (chỉ cần lưu ý về liên kết trong trường hợp đó, có thể là khôn ngoan khi sao chép nó vào hệ thống tài liệu của riêng bạn với liên kết đến bản gốc .)
Marjan Venema

1
@MikeDunlavey: Ouch, xin đừng bình luận. Chỉ cần thực hiện một số triển khai của cùng một chức năng và gọi đó là cách nhanh nhất. Bằng cách đó, bạn có thể dễ dàng chuyển sang một phiên bản mã khác và đánh giá tất cả chúng.
sleske

2
@sleske Đôi khi chỉ cần có thêm mã nhị phân có thể làm chậm nó.
quant_dev

@quant_dev: Vâng, điều đó có thể xảy ra. Tôi chỉ nghĩ rằng điều quan trọng là mã được xây dựng và chạy (lý tưởng) thường xuyên, để giữ cho nó được cập nhật. Có lẽ chỉ xây dựng nó trong chế độ gỡ lỗi.
sleske

Câu trả lời:


10

Câu trả lời ngắn

Giữ tối ưu hóa cục bộ, làm cho chúng rõ ràng, ghi lại chúng tốt và giúp dễ dàng so sánh các phiên bản được tối ưu hóa với nhau và với phiên bản không được tối ưu hóa, cả về mã nguồn và hiệu suất thời gian chạy.

Câu trả lời đầy đủ

Nếu optimisations như vậy thực sự quan trọng với sản phẩm của bạn, sau đó bạn cần phải biết tại sao không chỉ optimisations là hữu ích trước đây, mà còn cung cấp đủ thông tin để các nhà phát triển giúp đỡ biết liệu họ sẽ có ích trong tương lai.

Lý tưởng nhất, bạn cần đưa thử nghiệm hiệu suất vào quy trình xây dựng của mình, để bạn tìm ra khi nào các công nghệ mới làm mất hiệu lực tối ưu hóa cũ.

Nhớ lại:

Nguyên tắc tối ưu hóa chương trình đầu tiên: Đừng làm điều đó.

Quy tắc tối ưu hóa chương trình thứ hai (chỉ dành cho chuyên gia!): Đừng làm điều đó. "

- Michael A. Jackson

Để biết liệu bây giờ là thời gian yêu cầu điểm chuẩn và thử nghiệm.

Như bạn đã đề cập, vấn đề lớn nhất với mã được tối ưu hóa cao là rất khó để duy trì, vì vậy, càng nhiều càng tốt, bạn cần giữ các phần được tối ưu hóa tách biệt với các phần không được tối ưu hóa. Cho dù bạn thực hiện điều này thông qua liên kết thời gian biên dịch, các cuộc gọi chức năng ảo thời gian chạy hoặc một cái gì đó ở giữa không nên quan trọng. Điều quan trọng là khi bạn chạy thử nghiệm, bạn muốn có thể thử nghiệm tất cả các phiên bản mà bạn hiện đang quan tâm.

Tôi sẽ có xu hướng xây dựng một hệ thống theo cách mà phiên bản cơ bản không được tối ưu hóa của mã sản xuất luôn có thể được sử dụng để hiểu ý định của mã, sau đó xây dựng các mô-đun được tối ưu hóa khác nhau cùng với phiên bản được tối ưu hóa này, có tài liệu rõ ràng ở bất cứ đâu phiên bản tối ưu hóa khác với dòng cơ sở. Khi bạn chạy thử nghiệm (đơn vị và tích hợp), bạn chạy nó trên phiên bản chưa được tối ưu hóa trên tất cả các mô-đun được tối ưu hóa hiện tại.

Thí dụ

Chẳng hạn, giả sử bạn có chức năng Biến đổi Fourier nhanh . Có thể bạn có một triển khai thuật toán cơ bản fft.cvà thử nghiệm trong fft_tests.c.

Sau đó, đến Pentium và bạn quyết định triển khai phiên bản điểm cố định fft_mmx.cbằng cách sử dụng các hướng dẫn MMX . Sau đó, pentium 3 xuất hiện và bạn quyết định thêm một phiên bản sử dụng Tiện ích mở rộng Truyền SIMD trong fft_sse.c.

Bây giờ bạn muốn thêm CUDA , vì vậy bạn thêm fft_cuda.c, nhưng thấy rằng với bộ dữ liệu thử nghiệm mà bạn đã sử dụng trong nhiều năm, phiên bản CUDA chậm hơn phiên bản SSE! Bạn thực hiện một số phân tích và kết thúc việc thêm một tập dữ liệu lớn hơn 100 lần và bạn sẽ tăng tốc độ mà bạn mong đợi, nhưng bây giờ bạn biết rằng thời gian thiết lập để sử dụng phiên bản CUDA rất quan trọng và với các bộ dữ liệu nhỏ bạn nên sử dụng thuật toán mà không có chi phí thiết lập.

Trong mỗi trường hợp này, bạn đang thực hiện cùng một thuật toán, tất cả sẽ hành xử theo cùng một cách, nhưng sẽ chạy với hiệu quả và tốc độ khác nhau trên các kiến ​​trúc khác nhau (nếu chúng sẽ chạy hoàn toàn). Tuy nhiên, từ quan điểm mã, bạn có thể so sánh bất kỳ cặp tệp nguồn nào để tìm hiểu lý do tại sao cùng một giao diện được triển khai theo các cách khác nhau và thông thường, cách dễ nhất sẽ là quay lại phiên bản gốc không được tối ưu hóa.

Tất cả đều giống nhau đối với việc triển khai OOP trong đó một lớp cơ sở thực hiện thuật toán không được tối ưu hóa và các lớp dẫn xuất thực hiện các tối ưu hóa khác nhau.

Điều quan trọng là giữ những thứ giống nhau , sao cho sự khác biệt là rõ ràng .


7

Cụ thể vì bạn đã lấy ví dụ về xử lý Video và hình ảnh, người ta có thể giữ mã như một phần của cùng một phiên bản nhưng hoạt động hoặc không hoạt động tùy thuộc vào ngữ cảnh.

Trong khi bạn chưa đề cập, tôi giả sử Cở đây.

Cách đơn giản nhất trong Cmã, người ta thực hiện tối ưu hóa (và cũng áp dụng khi cố gắng làm cho mọi thứ có thể di động) là giữ

 
#ifdef OPTIMIZATION_XYZ_ENABLE 
   // your optimzied code here... 
#else  
   // your basic code here...

Khi bạn bật #define OPTIMIZATION_XYZ_ENABLEtrong quá trình biên dịch trong Makefile, mọi thứ sẽ hoạt động tương ứng.

Thông thường, việc cắt một vài dòng mã ở giữa các hàm có thể trở nên lộn xộn khi có quá nhiều chức năng được tối ưu hóa. Do đó, trong trường hợp này, người ta định nghĩa các con trỏ hàm khác nhau để thực hiện một chức năng cụ thể.

mã chính luôn thực thi thông qua một con trỏ hàm như


   codec->computed_idct(blocks); 

Nhưng các con trỏ hàm được định nghĩa tùy thuộc vào loại ví dụ (ví dụ ở đây hàm idct được tối ưu hóa cho kiến ​​trúc CPU khác nhau.



if(OPTIMIZE_X86) {
  codec->computed_idct = compute_idct_x86; 
}
else if(OPTIMZE_ARM) {
  codec->computed_idct = compute_idct_ARM;
}
else {
  codec->computed_idct = compute_idct_C; 
}

bạn sẽ thấy mã libjpeg và mã libmpeg2 và có thể là ffmpeg cho các kỹ thuật đó.


6

Là một nhà nghiên cứu, cuối cùng tôi đã viết khá nhiều mã "nút cổ chai". Tuy nhiên, một khi nó được đưa vào sản xuất, trách nhiệm tích hợp nó vào sản phẩm và cung cấp hỗ trợ tiếp theo thuộc về các nhà phát triển. Như bạn có thể tưởng tượng, việc truyền đạt rõ ràng những gì và cách chương trình được cho là hoạt động là vô cùng quan trọng.

Tôi đã thấy rằng có ba thành phần thiết yếu để hoàn thành bước này thành công

  1. Các thuật toán được sử dụng phải hoàn toàn rõ ràng.
  2. Mục đích của mỗi dòng thực hiện phải rõ ràng.
  3. Những sai lệch so với kết quả dự kiến ​​phải được xác định càng sớm càng tốt.

Bước đầu tiên, tôi luôn viết một whitepaper ngắn ghi lại thuật toán. Mục đích ở đây là thực sự viết nó lên để người khác có thể thực hiện nó từ đầu chỉ bằng cách sử dụng whitepaper. Nếu nó là một thuật toán nổi tiếng, được xuất bản, nó đủ để cung cấp các tham chiếu và lặp lại các phương trình chính. Nếu đó là tác phẩm gốc, bạn sẽ cần phải rõ ràng hơn một chút. Điều này sẽ cho bạn biết những gìphải làm .

Việc triển khai thực tế được đưa ra để phát triển phải được ghi lại theo cách sao cho tất cả các phép tính được thể hiện rõ ràng. Nếu bạn có được các khóa theo một thứ tự cụ thể để tránh bế tắc, hãy thêm một bình luận. Nếu bạn lặp qua các cột thay vì trên các hàng của ma trận vì các vấn đề liên kết bộ đệm, hãy thêm một nhận xét. Nếu bạn làm bất cứ điều gì thậm chí hơi thông minh, hãy bình luận nó. Nếu bạn có thể đảm bảo whitepaper và mã sẽ không bao giờ bị tách rời (thông qua VCS hoặc hệ thống tương tự), bạn có thể tham khảo lại whitepaper. Kết quả có thể dễ dàng nhận xét hơn 50%. Không sao đâu. Điều này sẽ cho bạn biết lý do tại sao mã làm những gì nó làm.

Cuối cùng, bạn cần có khả năng đảm bảo tính chính xác khi đối mặt với những thay đổi. May mắn thay, chúng tôi là một công cụ tiện dụng trong các nền tảng thử nghiệm tự độngtích hợp liên tục . Chúng sẽ cho bạn biết những gìthực sự đang làm .

Đề nghị nhiệt tình nhất của tôi sẽ không bỏ qua bất kỳ bước nào. Bạn sẽ cần chúng sau này;)


Cảm ơn câu trả lời toàn diện của bạn. Tôi đồng ý với tất cả các điểm của bạn. Về mặt kiểm tra tự động, tôi thấy rằng việc bao quát đầy đủ phạm vi số của số học điểm cố định và mã SIMD là khó khăn, điều mà tôi đã bị đốt cháy hai lần. Điều kiện tiên quyết chỉ được nêu trong các ý kiến ​​(không có mã để củng cố) không phải lúc nào cũng được đáp ứng.
rwong

Lý do tôi chưa chấp nhận câu trả lời của bạn là vì tôi cần thêm hướng dẫn về "một whitepaper ngắn" nghĩa là gì và nỗ lực nào để tạo ra nó. Đối với một số ngành, đây là một phần của ngành kinh doanh chính, nhưng trong các ngành khác, chi phí phải được xem xét và các phím tắt có sẵn về mặt pháp lý nên được thực hiện.
rwong

Trước hết, tôi cảm thấy nỗi đau của bạn về kiểm tra tự động, số học dấu phẩy động và mã song song. Tôi e rằng không có giải pháp nào hợp lệ cho mọi trường hợp. Thông thường tôi làm việc với dung sai khá tự do, nhưng trong ngành của bạn có thể không thể.
drxzcl

2
Trong thực tế, whitepaper thường trông giống như bản nháp đầu tiên của một bài báo khoa học, không có phần "lông tơ" (không có phần giới thiệu có ý nghĩa, không có kết luận / thảo luận trừu tượng, tối thiểu và chỉ có các tài liệu tham khảo cần thiết để hiểu nó). Tôi thấy việc viết bài báo là một báo cáo và là một phần không thể thiếu của sự phát triển thuật toán và / hoặc lựa chọn thuật toán. Bạn đã chọn thực hiện thuật toán này (giả sử FFT quang phổ). Chính xác nó là cái gì? Tại sao bạn lại chọn cái này hơn cái kia? Đặc điểm song song của nó là gì? Nỗ lực phải tỷ lệ thuận với công việc lựa chọn / phát triển.
drxzcl

5

Tôi tin rằng điều này sẽ được giải quyết tốt nhất thông qua nhận xét toàn diện về mã, đến mức mỗi khối mã quan trọng có nhận xét giải thích trước.

Các ý kiến ​​nên bao gồm trích dẫn các thông số kỹ thuật hoặc tài liệu tham khảo phần cứng.

Sử dụng thuật ngữ và tên thuật toán trong toàn ngành khi thích hợp - ví dụ: 'architecture X tạo bẫy CPU cho các lần đọc không được phân bổ, do đó, Thiết bị của Duff này sẽ điền vào ranh giới căn chỉnh tiếp theo'.

Tôi sẽ sử dụng đặt tên biến trong khuôn mặt của bạn để đảm bảo không có sự hiểu lầm về những gì đang xảy ra. Không phải tiếng Hungary, nhưng những thứ như 'sải chân' để mô tả khoảng cách tính bằng byte giữa hai pixel dọc.

Tôi cũng sẽ bổ sung điều này bằng một tài liệu ngắn, dễ đọc, có sơ đồ cấp cao và thiết kế khối.


1
Sử dụng một thuật ngữ nhất quán cho một điều duy nhất (ví dụ: sử dụng "bước tiến" trên các thuật ngữ có nghĩa tương tự, ví dụ: "bước", "căn chỉnh") trong cùng một dự án sẽ giúp ích. Điều này hơi khó khăn khi tích hợp một số cơ sở mã của dự án vào một dự án.
rwong
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.