Một số chiến lược tốt để cải thiện hiệu suất nối tiếp của mã của tôi là gì?


66

Tôi làm việc trong khoa học tính toán, và kết quả là, tôi dành một lượng thời gian không hề nhỏ để cố gắng tăng thông lượng khoa học của nhiều mã, cũng như hiểu được hiệu quả của các mã này.

Giả sử tôi đã đánh giá hiệu năng so với khả năng đọc / tái sử dụng / khả năng bảo trì của phần mềm tôi đang làm việc và tôi đã quyết định rằng đã đến lúc phải thực hiện. Chúng ta cũng giả sử rằng tôi biết rằng tôi không có thuật toán tốt hơn cho vấn đề của mình (về flop / s và băng thông bộ nhớ). Bạn cũng có thể giả sử cơ sở mã của tôi là ngôn ngữ cấp thấp như C, C ++ hoặc Fortran. Cuối cùng, hãy giả sử rằng không có sự song song trong mã hoặc chúng ta chỉ quan tâm đến hiệu suất trên một lõi đơn.

Những điều quan trọng nhất để thử đầu tiên là gì? Làm thế nào để tôi biết tôi có thể đạt được bao nhiêu hiệu suất?

Câu trả lời:


66

Trước hết, như kỹ năngDan đã chỉ ra, hồ sơ là điều cần thiết. Cá nhân tôi sử dụng Bộ khuếch đại VTune của Intel trên Linux vì nó cho tôi một cái nhìn tổng quan rất chi tiết về thời gian dành cho việc gì.

Nếu bạn sẽ không thay đổi thuật toán (nghĩa là nếu không có thay đổi lớn nào sẽ khiến tất cả tối ưu hóa của bạn bị lỗi thời), thì tôi khuyên bạn nên tìm kiếm một số chi tiết triển khai phổ biến có thể tạo ra sự khác biệt lớn:

  • Địa phương bộ nhớ : dữ liệu được đọc / sử dụng cùng nhau cũng được lưu trữ cùng nhau hay bạn đang chọn các bit và các mảnh ở đây và ở đó?

  • Căn chỉnh bộ nhớ : nhân đôi của bạn có thực sự được căn chỉnh thành 4 byte không? Làm thế nào bạn đóng gói của bạn structs? Để được phạm vi, sử dụng posix_memalignthay vì malloc.

  • Hiệu quả của bộ đệm: Địa phương xử lý hầu hết các vấn đề về hiệu quả của bộ đệm, nhưng nếu bạn có một số cấu trúc dữ liệu nhỏ mà bạn đọc / ghi thường xuyên, sẽ rất hữu ích nếu chúng là một số nguyên hoặc một phần của một dòng bộ đệm (thường là 64 byte). Nó cũng giúp nếu dữ liệu của bạn được căn chỉnh theo kích thước của một dòng bộ đệm. Điều này có thể làm giảm đáng kể số lần đọc cần thiết để tải một phần dữ liệu.

  • Vectorization : Không, đừng đi tâm thần với trình biên dịch mã hóa bằng tay. gcccung cấp các loại vectơ được dịch sang SSE / AltiVec / bất kỳ hướng dẫn nào một cách tự động.

  • Hướng dẫn song song cấp độ : Con trai khốn của vector hóa. Nếu một số tính toán thường lặp đi lặp lại không tốt, bạn có thể thử tích lũy các giá trị đầu vào và tính toán một số giá trị cùng một lúc. Nó giống như vòng lặp không kiểm soát. Điều bạn đang khai thác ở đây là CPU của bạn thường sẽ có nhiều hơn một đơn vị dấu phẩy động trên mỗi lõi.

  • Độ chính xác số học : Bạn có thực sự cần số học chính xác kép trong mọi việc bạn làm không? Ví dụ: nếu bạn đang tính toán một hiệu chỉnh trong Lặp lại Newton, bạn thường không cần tất cả các chữ số bạn đang tính toán. Để thảo luận sâu hơn, xem bài viết này .

Một số các thủ thuật được sử dụng trong chủ đề daxpy_cvec này . Phải nói rằng, nếu bạn đang sử dụng Fortran (không phải là ngôn ngữ cấp thấp trong sách của tôi), bạn sẽ có rất ít quyền kiểm soát đối với hầu hết các "thủ thuật" này.

Nếu bạn đang chạy trên một số phần cứng chuyên dụng, ví dụ như cụm bạn sử dụng cho tất cả các hoạt động sản xuất của mình, bạn cũng có thể muốn đọc chi tiết cụ thể về CPU được sử dụng. Không phải là bạn nên viết công cụ trong trình biên dịch trực tiếp cho kiến ​​trúc đó, nhưng nó có thể truyền cảm hứng cho bạn để tìm một số tối ưu hóa khác mà bạn có thể đã bỏ lỡ. Biết về một tính năng là bước đầu tiên cần thiết để viết mã có thể khai thác nó.

Cập nhật

Đã được một thời gian kể từ khi tôi viết bài này và tôi đã không nhận thấy rằng nó đã trở thành một câu trả lời phổ biến như vậy. Vì lý do này, tôi muốn thêm một điểm quan trọng:

  • Nói chuyện với Nhà khoa học máy tính tại địa phương của bạn : Sẽ không hay nếu có một ngành học chuyên xử lý các thuật toán và / hoặc tính toán hiệu quả hơn / thanh lịch / song song và tất cả chúng ta có thể hỏi họ lời khuyên không? Vâng, tin tốt, ngành học đó tồn tại: Khoa học máy tính. Rất có thể, tổ chức của bạn thậm chí có cả một bộ phận dành riêng cho nó. Nói chuyện với những kẻ này.

Tôi chắc chắn với một số nhà khoa học không sử dụng máy tính, điều này sẽ mang lại những ký ức về các cuộc thảo luận bực bội với kỷ luật nói trên dẫn đến không có gì, hoặc ký ức về những giai thoại của người khác. Đừng nản lòng. Hợp tác liên ngành là một điều khó khăn, và nó cần một chút công việc, nhưng phần thưởng có thể rất lớn.

Theo kinh nghiệm của tôi, với tư cách là một nhà khoa học máy tính (CS), mánh khóe là đạt được cả sự mong đợi và sự giao tiếp đúng đắn.

Mong đợi - theo cách đó, một CS sẽ chỉ giúp bạn nếu anh ấy / cô ấy nghĩ rằng vấn đề của bạn là thú vị. Điều này khá nhiều loại trừ việc cố gắng tối ưu hóa / vector hóa / song song hóa một đoạn mã bạn đã viết, nhưng không thực sự nhận xét, cho một vấn đề mà họ không hiểu. CS thường quan tâm nhiều hơn đến vấn đề tiềm ẩn, ví dụ các thuật toán được sử dụng để giải quyết nó. Đừng cho họ giải pháp của bạn , hãy cho họ vấn đề của bạn .

Ngoài ra, hãy chuẩn bị cho CS nói rằng " vấn đề này đã được giải quyết " và chỉ cần cung cấp cho bạn một tài liệu tham khảo. Một lời khuyên: Đọc bài báo đó và, nếu nó thực sự áp dụng cho vấn đề của bạn, hãy thực hiện bất kỳ thuật toán nào mà nó gợi ý. Đây không phải là một CS đang tự mãn, đó là một CS chỉ giúp bạn. Đừng xúc phạm, hãy nhớ rằng: Nếu vấn đề không thú vị về mặt tính toán, tức là nó đã được giải quyết và giải pháp được hiển thị là tối ưu, họ sẽ không làm việc với nó, ít mã hóa hơn cho bạn.

Giao tiếp - theo chiều dọc, hãy nhớ rằng hầu hết các CS không phải là chuyên gia trong lĩnh vực của bạn và giải thích vấn đề theo những gì bạn đang làm, trái ngược với cách thứclý do . Chúng ta thường thực sự không quan tâm đến lý do tại sao , và làm thế nào là, những gì chúng ta làm tốt nhất.

Ví dụ, tôi hiện đang làm việc với một nhóm các nhà vũ trụ học tính toán để viết một phiên bản tốt hơn cho mã mô phỏng của họ, dựa trên SPHMultipoles . Phải mất khoảng ba cuộc họp để ngừng nói về vật chất tối và thiên hà (hả?) Và đi sâu vào cốt lõi của tính toán, tức là họ cần tìm tất cả các lân cận trong bán kính cho mỗi hạt, tính toán một số số lượng trên chúng, và sau đó chạy qua tất cả các hàng xóm đã nói một lần nữa và áp dụng số lượng đó trong một số tính toán khác. Sau đó di chuyển các hạt, hoặc ít nhất là một số trong số chúng, và làm lại tất cả. Bạn thấy đấy, trong khi cái trước có thể cực kỳ thú vị (đó là!), Cái sau là thứ tôi cần hiểu để bắt đầu nghĩ về thuật toán.

Nhưng tôi đang chuyển hướng từ điểm chính: Nếu bạn thực sự quan tâm đến việc làm cho tính toán của mình nhanh chóng và bản thân bạn không phải là Nhà khoa học Máy tính, hãy nói chuyện với một người.


4
khi các công cụ định hình đi, tôi sẽ không quên valgrind .
GertVdE

1
Tôi đồng ý với bạn Pedro, khi chương trình được tối ưu hóa giống như một chiếc xe đua F1, đã gần đạt đến mức tối ưu. Các chương trình tôi thấy trong thực tế, khoa học và không, thường giống như Cadillac Coupe DeVilles. Để có được hiệu suất thực sự, hàng tấn chất béo có thể được cắt bỏ. Sau đó, chu trình cạo bắt đầu đạt được bước tiến của nó.
Mike Dunlavey

1
@MikeDunlavey: Hoàn toàn đồng ý. Tôi đã thêm một bản cập nhật cho câu trả lời của mình để giải quyết các vấn đề liên quan đến thuật toán hơn.
Pedro

1
@MikeDunlavey, tôi dân gian CS :)
Pedro

2
Tôi đã chứng minh điều này trong một cuộc nói chuyện tại U Mass. Lowell. Đó là một bản demo trực tiếp, hiển thị tất cả các giai đoạn của tăng tốc 730x. Tôi nghĩ rằng một giáo sư đã nhận được điểm, trong số một nửa tá.
Mike Dunlavey

38

Phần mềm khoa học không có nhiều khác biệt so với các phần mềm khác, về cách biết những gì cần điều chỉnh.

Phương pháp tôi sử dụng là tạm dừng ngẫu nhiên . Đây là một số tốc độ mà nó đã tìm thấy cho tôi:

Nếu một phần lớn thời gian được dành cho các hàm như logexp, tôi có thể thấy các đối số cho các hàm đó là gì, như là một hàm của các điểm mà chúng được gọi từ đó. Thông thường họ đang được gọi nhiều lần với cùng một lập luận. Nếu vậy, ghi nhớ tạo ra một yếu tố tăng tốc lớn.

Nếu tôi đang sử dụng các hàm BLAS hoặc LAPACK, tôi có thể thấy rằng một phần lớn thời gian được sử dụng trong các thói quen để sao chép mảng, nhân ma trận, biến đổi choleski, v.v.

  • Các thói quen để sao chép mảng không có ở đó cho tốc độ, nó là để thuận tiện. Bạn có thể thấy có một cách ít thuận tiện hơn, nhưng nhanh hơn, để làm điều đó.

  • Các thường trình để nhân hoặc đảo ngược ma trận, hoặc thực hiện các phép biến đổi choleski, có xu hướng có các đối số ký tự chỉ định các tùy chọn, chẳng hạn như 'U' hoặc 'L' cho tam giác trên hoặc dưới. Một lần nữa, đó là cho thuận tiện. Những gì tôi tìm thấy là, vì ma trận của tôi không lớn lắm, các thói quen đã dành hơn một nửa thời gian của họ để gọi chương trình con để so sánh các ký tự chỉ để giải mã các tùy chọn. Viết các phiên bản mục đích đặc biệt của các thói quen toán học tốn kém nhất đã tạo ra sự tăng tốc lớn.

Nếu tôi chỉ có thể mở rộng ở phần sau: DGEMM thường xuyên nhân ma trận gọi LSAME để giải mã các đối số ký tự của nó. Nhìn vào phần trăm thời gian bao gồm (giá trị thống kê duy nhất nhìn vào) các trình biên dịch được coi là "tốt" có thể hiển thị DGEMM bằng cách sử dụng một số phần trăm tổng thời gian, như 80% và LSAME sử dụng một số phần trăm tổng thời gian, như 50%. Nhìn vào cái trước, bạn sẽ bị cám dỗ để nói rằng "nó phải được tối ưu hóa rất nhiều, vì vậy tôi không thể làm gì nhiều về điều đó". Nhìn vào cái sau, bạn sẽ bị cám dỗ để nói "Huh? Tất cả những gì về nó? Đó chỉ là một thói quen nhỏ tuổi. Hồ sơ này phải sai!"

Điều đó không sai, nó chỉ không nói cho bạn biết những gì bạn cần biết. Điều tạm dừng ngẫu nhiên cho bạn thấy là DGEMM có trên 80% mẫu ngăn xếp và LSAME là trên 50%. (Bạn không cần nhiều mẫu để phát hiện ra. 10 thường là rất nhiều.) Hơn nữa, trên nhiều mẫu đó, DGEMM đang trong quá trình gọi LSAME từ một vài dòng mã khác nhau.

Vì vậy, bây giờ bạn biết tại sao cả hai thói quen đang mất rất nhiều thời gian. Bạn cũng biết nơi mà mã của bạn được gọi từ đâu để dành tất cả thời gian này. Đó là lý do tại sao tôi sử dụng tạm dừng ngẫu nhiên và có một cái nhìn vàng da về các trình biên dịch, bất kể chúng được làm tốt như thế nào. Họ quan tâm đến việc đo đạc hơn là nói cho bạn biết chuyện gì đang xảy ra.

Thật dễ dàng để giả định các thói quen thư viện toán học đã được tối ưu hóa đến mức thứ n, nhưng trên thực tế, chúng đã được tối ưu hóa để có thể sử dụng cho nhiều mục đích. Bạn cần phải xem những gì thực sự đang diễn ra, không phải là những gì dễ dàng để giả định.

THÊM: Vì vậy, để trả lời hai câu hỏi cuối cùng của bạn:

Những điều quan trọng nhất để thử đầu tiên là gì?

Lấy 10-20 mẫu ngăn xếp và không chỉ tóm tắt chúng, hiểu những gì mỗi người đang nói với bạn. Làm điều này đầu tiên, cuối cùng và ở giữa. (Không có "thử", Skywalker trẻ.)

Làm thế nào để tôi biết tôi có thể đạt được bao nhiêu hiệu suất?

xβ(s+1,(ns)+1)sn1/(1x)n=10s=5x
nhập mô tả hình ảnh ở đây
xx

Như tôi đã chỉ ra cho bạn trước đây, bạn có thể lặp lại toàn bộ quy trình cho đến khi bạn không thể làm được nữa và tỷ lệ tăng tốc gộp có thể khá lớn.

(s+1)/(n+2)=3/22=13.6%.) Đường cong dưới trong biểu đồ sau là phân phối của nó:

nhập mô tả hình ảnh ở đây

Hãy xem xét nếu chúng tôi đã lấy tới 40 mẫu (nhiều hơn tôi từng có một lần) và chỉ thấy một vấn đề ở hai trong số chúng. Chi phí ước tính (chế độ) của vấn đề đó là 5%, như thể hiện trên đường cong cao hơn.

"Dương tính giả" là gì? Đó là nếu bạn khắc phục một vấn đề bạn nhận ra mức tăng nhỏ hơn dự kiến, bạn sẽ hối tiếc vì đã sửa nó. Các đường cong cho thấy (nếu vấn đề là "nhỏ"), trong khi mức tăng thể nhỏ hơn phần mẫu cho thấy nó, trung bình nó sẽ lớn hơn.

Có một rủi ro nghiêm trọng hơn nhiều - một "âm tính giả". Đó là khi có một vấn đề, nhưng nó không được tìm thấy. (Đóng góp cho điều này là "sự xác nhận thiên vị", trong đó sự vắng mặt của bằng chứng có xu hướng được coi là bằng chứng của sự vắng mặt.)

Những gì bạn nhận được với một hồ sơ (một cái tốt) là bạn có được phép đo chính xác hơn nhiều (do đó ít có khả năng dương tính giả), với chi phí thông tin ít chính xác hơn về vấn đề thực sự là gì (do đó ít có cơ hội tìm thấy và nhận được bất kỳ lợi ích). Điều đó giới hạn tốc độ tổng thể có thể đạt được.

Tôi sẽ khuyến khích người dùng trình biên dịch báo cáo các yếu tố tăng tốc mà họ thực sự có được trong thực tế.


Có một điểm khác sẽ được thực hiện lại. Câu hỏi của Pedro về dương tính giả.

Ông đã đề cập có thể có một khó khăn khi gặp các vấn đề nhỏ trong mã được tối ưu hóa cao. (Đối với tôi, một vấn đề nhỏ là một vấn đề chiếm 5% hoặc ít hơn tổng thời gian.)

Vì hoàn toàn có thể xây dựng một chương trình hoàn toàn tối ưu ngoại trừ 5%, điểm này chỉ có thể được giải quyết theo kinh nghiệm, như trong câu trả lời này . Để khái quát từ kinh nghiệm thực nghiệm, nó diễn ra như sau:

Một chương trình, như được viết, thường chứa một số cơ hội để tối ưu hóa. (Chúng ta có thể gọi chúng là "các vấn đề" nhưng chúng thường là mã hoàn toàn tốt, đơn giản là có khả năng cải thiện đáng kể.) Sơ đồ này minh họa một chương trình nhân tạo mất một khoảng thời gian (100 giây, nói) và nó chứa các vấn đề A, B, C, ... rằng, khi được tìm thấy và sửa chữa, hãy tiết kiệm 30%, 21%, v.v. của 100 bản gốc.

nhập mô tả hình ảnh ở đây

Lưu ý rằng vấn đề F tốn 5% thời gian ban đầu, vì vậy nó "nhỏ" và khó tìm thấy nếu không có 40 mẫu trở lên.

Tuy nhiên, 10 mẫu đầu tiên dễ dàng tìm thấy sự cố A. ** Khi điều đó được khắc phục, chương trình chỉ mất 70 giây, với tốc độ tăng tốc 100/70 = 1,43x. Điều đó không chỉ làm cho chương trình nhanh hơn, mà còn phóng to, theo tỷ lệ đó, tỷ lệ phần trăm được thực hiện bởi các vấn đề còn lại. Ví dụ: vấn đề B ban đầu mất 21 giây, chiếm 21% tổng số, nhưng sau khi loại bỏ A, B mất 21 trong số 70 hoặc 30%, do đó dễ dàng tìm thấy hơn khi toàn bộ quá trình được lặp lại.

Sau khi quá trình được lặp lại năm lần, bây giờ thời gian thực hiện là 16,8 giây, trong đó vấn đề F là 30%, không phải 5%, vì vậy 10 mẫu tìm thấy nó dễ dàng.

Vì vậy, đó là điểm. Theo kinh nghiệm, các chương trình chứa một loạt các vấn đề có sự phân bố kích thước và bất kỳ vấn đề nào được tìm thấy và khắc phục sẽ giúp bạn dễ dàng tìm thấy các vấn đề còn lại. Để thực hiện điều này, không có vấn đề nào có thể bỏ qua bởi vì, nếu có, họ ngồi đó mất thời gian, hạn chế tổng tốc độ và không phóng đại các vấn đề còn lại. Đó là lý do tại sao nó rất quan trọng để tìm ra những vấn đề đang ẩn giấu .

Nếu các sự cố từ A đến F được tìm thấy và khắc phục, tốc độ tăng tốc là 100 / 11,8 = 8,5x. Nếu một trong số chúng bị bỏ lỡ, ví dụ D, thì tốc độ chỉ là 100 / (11.8 + 10.3) = 4.5x. Đó là cái giá phải trả cho những tiêu cực giả.

Vì vậy, khi trình hồ sơ nói rằng "dường như không có vấn đề gì đáng kể ở đây" (tức là người viết mã giỏi, đây thực sự là mã tối ưu), có thể đúng, và có thể không. (Một phủ định sai .) Bạn không biết chắc chắn liệu có nhiều vấn đề cần khắc phục hay không, để tăng tốc độ cao hơn, trừ khi bạn thử một phương pháp định hình khác và phát hiện ra rằng có. Theo kinh nghiệm của tôi, phương pháp định hình không cần một số lượng lớn các mẫu, được tóm tắt, nhưng một số lượng nhỏ các mẫu, trong đó mỗi mẫu được hiểu thấu đáo đủ để nhận ra bất kỳ cơ hội nào để tối ưu hóa.

2/0.3=6.671 - pbinom(1, numberOfSamples, sizeOfProblem)1 - pbinom(1, 20, 0.3) = 0.9923627

xβ(s+1,(ns)+1)nsy1/(1x)xyy1Phân phối BetaPrime . Tôi đã mô phỏng nó với 2 triệu mẫu, đến hành vi này:

         distribution of speedup
               ratio y

 s, n    5%-ile  95%-ile  mean
 2, 2    1.58    59.30   32.36
 2, 3    1.33    10.25    4.00
 2, 4    1.23     5.28    2.50
 2, 5    1.18     3.69    2.00
 2,10    1.09     1.89    1.37
 2,20    1.04     1.37    1.17
 2,40    1.02     1.17    1.08

 3, 3    1.90    78.34   42.94
 3, 4    1.52    13.10    5.00
 3, 5    1.37     6.53    3.00
 3,10    1.16     2.29    1.57
 3,20    1.07     1.49    1.24
 3,40    1.04     1.22    1.11

 4, 4    2.22    98.02   52.36
 4, 5    1.72    15.95    6.00
 4,10    1.25     2.86    1.83
 4,20    1.11     1.62    1.31
 4,40    1.05     1.26    1.14

 5, 5    2.54   117.27   64.29
 5,10    1.37     3.69    2.20
 5,20    1.15     1.78    1.40
 5,40    1.07     1.31    1.17

(n+1)/(ns)s=ny

Đây là một âm mưu phân phối các yếu tố tăng tốc và phương tiện của chúng, cho 2 lần truy cập trong số 5, 4, 3 và 2 mẫu. Ví dụ: nếu 3 mẫu được lấy và 2 trong số đó là các vấn đề và vấn đề đó có thể được loại bỏ, hệ số tăng tốc trung bình sẽ là 4 lần. Nếu chỉ nhìn thấy 2 lần truy cập trong 2 mẫu, thì tốc độ trung bình không được xác định - về mặt khái niệm vì các chương trình có vòng lặp vô hạn tồn tại với xác suất khác không!

nhập mô tả hình ảnh ở đây


1
Uhm ... Bạn không nhận được chính xác thông tin này khi nhìn vào biểu đồ cuộc gọi hồ sơ hoặc tóm tắt loại "từ dưới lên" như được cung cấp bởi VTune?
Pedro

2
@Pedro: Nếu chỉ. Trong một mẫu ngăn xếp (& các biến liên quan) được mã hóa toàn bộ lý do gia tăng thời gian đang được sử dụng. Bạn không thể thoát khỏi nó trừ khi bạn biết tại sao nó được chi tiêu. Một số vấn đề có thể được tìm thấy với thông tin hạn chế, nhưng không phải mỗi vấn đề . Nếu bạn chỉ nhận được một số trong số họ, nhưng không phải mỗi người, thì những vấn đề bạn không gặp phải sẽ khiến bạn không thể tăng tốc. Kiểm tra ở đâyở đây .
Mike Dunlavey

Có thể cho rằng, bạn đang so sánh phương pháp của mình với hồ sơ xấu ... Bạn cũng có thể xem qua hồ sơ cho từng thói quen, không phụ thuộc vào đóng góp của nó vào tổng thời gian thực hiện và tìm kiếm các cải tiến, với cùng hiệu quả. Điều tôi lo lắng trong cách tiếp cận của bạn là số lượng dương tính giả ngày càng tăng mà bạn sẽ theo dõi khi các "điểm nóng" trong mã của bạn ngày càng nhỏ hơn.
Pedro

@Pedro: Chỉ cần tiếp tục lấy mẫu cho đến khi bạn thấy một cái gì đó bạn có thể sửa trên nhiều mẫu. Bản phân phối beta cho biết bạn có thể tiết kiệm được bao nhiêu, nếu bạn quan tâm, nhưng nếu bạn sợ bị tăng tốc ít hơn mức hiển thị, hãy lưu ý rằng bạn sẽ bỏ qua cơ hội rằng nó cũng có thể nhiều hơn (và nó bị lệch phải ). Mối nguy hiểm lớn hơn, với tóm tắt hồ sơ, là âm tính giả . Có thể có một vấn đề, nhưng bạn chỉ hy vọng trực giác của mình sẽ đánh hơi được nó khi trình hồ sơ rất không cụ thể về nơi nó có thể.
Mike Dunlavey

@Pedro: Điểm yếu duy nhất tôi biết là khi nhìn vào ảnh chụp nhanh, bạn không thể hiểu tại sao thời gian đó lại được sử dụng, chẳng hạn như nếu nó chỉ xử lý các sự kiện không đồng bộ trong đó người yêu cầu đang ẩn hoặc giao thức không đồng bộ. Để biết thêm mã "bình thường", hãy chỉ cho tôi một trình lược tả "tốt" và tôi sẽ chỉ cho bạn một vấn đề mà nó gặp phải hoặc đơn giản là không thể tìm thấy (khiến bạn rơi vào tình huống xấu của bạn). Nói chung cách để xây dựng một vấn đề như vậy là để đảm bảo rằng mục đích được phục vụ không thể được giải mã cục bộ. Và những vấn đề như vậy rất nhiều trong phần mềm.
Mike Dunlavey

23

Bạn không chỉ phải có kiến ​​thức sâu sắc về trình biên dịch của mình , bạn còn có kiến ​​thức sâu sắc về kiến trúchệ điều hành mục tiêu của bạn .

Điều gì có thể ảnh hưởng đến hiệu suất?

Nếu bạn muốn siết chặt mỗi ounce hiệu suất cuối cùng, thì mỗi khi bạn thay đổi kiến ​​trúc mục tiêu, bạn sẽ phải điều chỉnh và tối ưu hóa lại mã của mình. Một cái gì đó được tối ưu hóa với một CPU có thể trở thành tối ưu phụ trong lần sửa đổi tiếp theo của cùng CPU đó.

Một ví dụ tuyệt vời về điều này sẽ là bộ nhớ cache của CPU. Di chuyển chương trình của bạn từ một CPU với bộ nhớ cache nhỏ nhanh để một với một chút chậm hơn, nhẹ bộ nhớ cache lớn hơn và profiling của bạn có thể thay đổi đáng kể.

Ngay cả khi kiến ​​trúc đích không thay đổi, mức độ thấp thay đổi đối với hệ điều hành cũng có thể ảnh hưởng đến hiệu suất. Các bản vá giảm thiểu Spectre và Meltdown có tác động rất lớn trong một số khối lượng công việc, vì vậy chúng có thể buộc phải đánh giá lại các tối ưu hóa của bạn.

Làm thế nào tôi có thể giữ mã của mình được tối ưu hóa?

Khi phát triển mã được tối ưu hóa cao, bạn cần giữ cho nó được mô đun hóa và giúp dễ dàng trao đổi các phiên bản khác nhau của cùng một thuật toán, thậm chí có thể chọn phiên bản cụ thể được sử dụng trong thời gian chạy, tùy thuộc vào tài nguyên có sẵn và kích thước / độ phức tạp của dữ liệu được xử lý.

Tính mô đun cũng có nghĩa là có thể sử dụng cùng một bộ thử nghiệm trên tất cả các phiên bản được tối ưu hóa và không được tối ưu hóa của bạn, cho phép bạn xác minh rằng tất cả chúng đều hoạt động giống nhau và lập hồ sơ từng cái một cách nhanh chóng trong một so sánh tương tự . Tôi đi vào chi tiết hơn một chút trong câu trả lời của mình về Cách làm tài liệu 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? .

đọc thêm

Ngoài ra, tôi đặc biệt khuyên bạn nên xem bài báo xuất sắc của Ulrich Drepper Những gì mọi lập trình viên nên biết về bộ nhớ , với tiêu đề là sự tôn kính đối với David Goldberg, điều mà mọi nhà khoa học máy tính nên biết về số học dấu phẩy động .

Hãy nhớ rằng mọi tối ưu hóa đều có khả năng trở thành một tối ưu hóa chống tối ưu hóa trong tương lai , vì vậy nên được coi là một mùi mã có thể, được giữ ở mức tối thiểu. Câu trả lời của tôi cho việc tối ưu hóa vi mô có quan trọng khi mã hóa không? cung cấp một ví dụ cụ thể về điều này từ kinh nghiệm cá nhân.


8

Tôi nghĩ rằng bạn cụm từ câu hỏi quá hẹp. Theo quan điểm của tôi, một thái độ hữu ích là sống theo giả định rằng chỉ những thay đổi về cấu trúc dữ liệu và thuật toán mới có thể mang lại hiệu suất tăng đáng kể cho các mã có nhiều hơn 100 dòng và tôi tin rằng tôi vẫn chưa tìm thấy một ví dụ nào Tuyên bố này.


3
Đồng ý về nguyên tắc, nhưng người ta không nên đánh giá thấp sự tương tác giữa hiệu suất của thuật toán / cấu trúc dữ liệu và các chi tiết của phần cứng cơ bản. Ví dụ, cây nhị phân cân bằng rất tốt cho việc tìm kiếm / lưu trữ dữ liệu, nhưng tùy thuộc vào độ trễ của bộ nhớ chung, bảng băm thể tốt hơn.
Pedro

1
Đã đồng ý. Các thuật toán và cấu trúc dữ liệu có thể cung cấp các cải tiến O (10) đến O (100). Tuy nhiên, đối với một số vấn đề giới hạn tính toán (như trong tính toán động lực học phân tử, vật lý thiên văn, xử lý hình ảnh và video thời gian thực, tài chính), một vòng lặp quan trọng được điều chỉnh cao có thể có nghĩa là thời gian chạy ứng dụng tổng thể nhanh hơn gấp 3 đến 10 lần.
fcruz

Tôi đã thấy các vòng lặp lồng nhau được sắp xếp rất tệ trong các mã "sản xuất" có kích thước đáng kể. Khác hơn là tôi nghĩ bạn đúng.
dmckee

8

Điều đầu tiên bạn nên làm là hồ sơ mã của bạn. Bạn muốn tìm hiểu bộ phận của chương trình của bạn được làm chậm bạn xuống trước khi bạn bắt đầu để tối ưu hóa, nếu không bạn có thể sẽ tối ưu hóa một phần của mã của bạn mà không được ăn chiếm hầu hết thời gian thực hiện anyway.

Linux

gprof là khá tốt, nhưng nó chỉ cho bạn biết mỗi chức năng mất bao nhiêu thời gian chứ không phải mỗi dòng.

Hệ điều hành Apple X

Bạn có thể muốn thử Shark . Nó có sẵn trong trang web Nhà phát triển của Apple trong Tải xuống> Công cụ dành cho nhà phát triển> CHUD 4.6.2, phiên bản cũ hơn tại đây . CHUD cũng chứa các công cụ định hình khác như giao diện BigTop, công cụ tìm kiếm PMC Index, trình lược tả mức chức năng Saturn và rất nhiều lệnh khác. Shark sẽ đi kèm với một phiên bản dòng lệnh.


Hồ sơ +1? Vâng, theo một cách nào đó ... Nó tốt hơn nhiều so với việc đoán, nhưng đây là danh sách các vấn đề được áp dụng đặc biệt cho gprof và cho nhiều trình mô tả khác.
Mike Dunlavey

Shark có phải là một lệnh cũ trong OS X không? Thêm ở đây . Với Mountain Lion, tôi có nên sử dụng Dụng cụ?
hhh

@hhh: Đó là một trình lược tả GUI cho máy Mac, mặc dù có vẻ như nó không còn được duy trì nữa. Tôi đã không lập trình trên một máy táo kể từ khi tôi viết câu trả lời này, vì vậy tôi không thể giúp bạn nhiều.
Dan

1
Nó có sẵn trong trang web Nhà phát triển của Apple trong Tải xuống> Công cụ dành cho nhà phát triển> CHUD 4.6.2. Phiên bản cũ hơn ở đây và nó chứa tất cả các loại hồ sơ - không may là cài đặt này không thành công: "Liên hệ với nhà sản xuất", không biết gì về lỗi. Shark đã được đưa ra khỏi Xcode sau Lion và sau đó được đưa trở lại trang Apple Dev sau khi trở thành công cụ miễn phí trong MacUpdate.
hhh

@hhh: Bạn có vẻ đủ điều kiện để trả lời điều này hơn tôi. Hãy chỉnh sửa câu trả lời của tôi để cập nhật nó, hoặc viết của riêng bạn.
Dan

7

Đối với hiệu suất bạn có thể nhận được là bao nhiêu, hãy lấy kết quả từ việc định hình mã của bạn và giả sử bạn xác định một phần chiếm phần "p" của thời gian. Nếu bạn chỉ cải thiện hiệu suất của phần đó chỉ bằng một yếu tố "s", thì tốc độ tổng thể của bạn sẽ là 1 / ((1-p) + p / s). Do đó, bạn có thể tăng tối đa tốc độ của mình theo hệ số 1 / (1-p). Hy vọng bạn có khu vực p cao! Điều này tương đương với Luật của Amdahl về tối ưu hóa nối tiếp.


5

Tối ưu hóa mã của bạn phải được thực hiện cẩn thận. Chúng ta cũng giả sử rằng bạn đã gỡ lỗi mã rồi. Bạn có thể tiết kiệm rất nhiều thời gian nếu bạn tuân theo các ưu tiên nhất định, cụ thể là:

  1. Sử dụng các thư viện được tối ưu hóa cao (hoặc tối ưu hóa chuyên nghiệp) nếu có thể. Một số ví dụ có thể bao gồm các thư viện FFTW, OpenBlas, Intel MKL, NAG, v.v. Trừ khi bạn có tài năng cao (như nhà phát triển GotoBLAS), bạn có thể không thể đánh bại các chuyên gia.

  2. Sử dụng một trình lược tả (khá nhiều trong danh sách sau đây đã được đặt tên trong luồng này - Intel Tune, valgrind, gprof, gcov, v.v.) để tìm ra phần nào trong mã của bạn mất nhiều thời gian nhất. Không có điểm lãng phí thời gian tối ưu hóa các phần mã mà hiếm khi được gọi.

  3. Từ các kết quả hồ sơ, hãy xem phần mã của bạn mất nhiều thời gian nhất. Xác định bản chất của thuật toán của bạn là gì - nó bị ràng buộc CPU hay bị ràng buộc bộ nhớ? Mỗi yêu cầu một bộ kỹ thuật tối ưu hóa khác nhau. Nếu bạn nhận được rất nhiều lỗi bộ nhớ cache, bộ nhớ có thể là nút cổ chai - CPU đang lãng phí chu kỳ xung nhịp chờ bộ nhớ khả dụng. Hãy suy nghĩ xem vòng lặp có phù hợp với bộ đệm L1 / L2 / L3 của hệ thống của bạn không. Nếu bạn có câu lệnh "if" trong vòng lặp của mình, hãy kiểm tra xem trình lược tả có nói gì về việc hiểu sai chi nhánh không? Hình phạt sai chi nhánh của hệ thống của bạn là gì? Nhân tiện, bạn có thể nhận được dữ liệu sai lệch chi nhánh từ Hướng dẫn tham khảo tối ưu hóa Intel [1]. Lưu ý rằng hình phạt đánh giá sai chi nhánh là dành riêng cho bộ xử lý, như bạn sẽ thấy trong hướng dẫn sử dụng Intel.

  4. Cuối cùng, giải quyết các vấn đề được xác định bởi trình hồ sơ. Một số kỹ thuật đã được thảo luận ở đây. Một số tài nguyên tốt, đáng tin cậy, toàn diện về tối ưu hóa cũng có sẵn. Để chỉ tên hai, có Hướng dẫn tham khảo tối ưu hóa Intel [1] và năm hướng dẫn tối ưu hóa của Agner Fog [2]. Lưu ý rằng có một số điều mà bạn có thể không cần phải làm, nếu trình biên dịch đã làm điều đó - ví dụ, không kiểm soát vòng lặp, căn chỉnh bộ nhớ, v.v. Đọc kỹ tài liệu trình biên dịch của bạn.

Người giới thiệu:

[1] Hướng dẫn tham khảo tối ưu hóa kiến ​​trúc Intel 64 và IA-32: http://www.intel.sg/content/dam/doc/manual/64-ia-32-architectures-optimization-manual.pdf

[2] Sương mù Agner, "Tài nguyên tối ưu hóa phần mềm": http://www.agner.org/optizes/

  • "Tối ưu hóa phần mềm trong C ++: Hướng dẫn tối ưu hóa cho các nền tảng Windows, Linux và Mac"
  • "Tối ưu hóa chương trình con trong ngôn ngữ lắp ráp: Hướng dẫn tối ưu hóa cho nền tảng x86"
  • "Kiến trúc vi mô của CPU Intel, AMD và VIA: Hướng dẫn tối ưu hóa cho các lập trình viên lắp ráp và nhà sản xuất trình biên dịch"
  • "Bảng hướng dẫn: Danh sách thời gian trễ hướng dẫn, thông lượng và sự cố hoạt động vi mô cho CPU Intel, AMD và VIA"
  • "Các quy ước gọi cho các trình biên dịch và hệ điều hành C ++ khác nhau"

3

Tôi không phải là một nhà khoa học tính toán như nhiều người khác ở đây (vì vậy tôi có thể sai :)) nhưng ngày nay có rất ít thời gian để dành quá nhiều thời gian cho hiệu suất nối tiếp miễn là chúng tôi sử dụng libs tiêu chuẩn. Có thể đáng giá hơn khi dành bất kỳ thời gian / nỗ lực bổ sung nào để làm cho mã có khả năng mở rộng hơn.

Trong mọi trường hợp, đây là hai ví dụ (nếu bạn chưa đọc chúng) về cách cải thiện hiệu suất (đối với các vấn đề FE không có cấu trúc).

Nối tiếp : Xem nửa sau của văn bản trừu tượng và liên quan.

Song song : Đặc biệt là giai đoạn khởi tạo, trong giây 4.2.


3

Đây có lẽ là một câu trả lời meta hơn là một câu trả lời ...

Bạn phải phát triển một sự quen thuộc gần gũi với trình biên dịch của bạn. Bạn có thể có được điều này một cách hiệu quả nhất bằng cách đọc hướng dẫn và thử nghiệm với các tùy chọn.

Phần lớn lời khuyên tốt cho việc phân phối @Pedro có thể được thực hiện bằng cách điều chỉnh việc biên dịch thay vì chương trình.


Tôi không đồng ý với điểm cuối cùng. Biết những gì trình biên dịch của bạn có thể làm là một việc, nhưng viết mã của bạn để trình biên dịch của bạn thực sự có thể làm một cái gì đó với nó là một vấn đề hoàn toàn khác. Không có cờ trình biên dịch sẽ sắp xếp dữ liệu của bạn cho bạn, sử dụng độ chính xác thấp hơn khi được yêu cầu hoặc viết lại các vòng lặp trong cùng của bạn sao cho chúng có ít hoặc không có nhánh. Biết trình biên dịch của bạn là một điều tốt, nhưng nó sẽ chỉ giúp bạn viết mã tốt hơn, nó sẽ không làm cho mã của bạn tốt hơn mỗi se.
Pedro

1

Một cách dễ dàng để định hình một chương trình (trong Linux) là sử dụng perftrong statchế độ. Cách đơn giản nhất là chạy nó như thế nào

perf stat ./my_program args ...

và nó sẽ cung cấp cho bạn một loạt các số liệu thống kê hiệu suất hữu ích:

Performance counter stats for './simd_test1':

     3884.559489 task-clock                #    1.000 CPUs utilized
              18 context-switches          #    0.005 K/sec
               0 cpu-migrations            #    0.000 K/sec
             383 page-faults               #    0.099 K/sec
  10,911,904,779 cycles                    #    2.809 GHz
 <not supported> stalled-cycles-frontend
 <not supported> stalled-cycles-backend
  14,346,983,161 instructions              #    1.31  insns per cycle
   2,143,017,630 branches                  #  551.676 M/sec
          28,892 branch-misses             #    0.00% of all branches

     3.885986246 seconds time elapsed

Đôi khi nó cũng sẽ liệt kê các tải D-cache và bỏ lỡ. Nếu bạn thấy rất nhiều bộ nhớ cache bị mất, thì chương trình của bạn bị thiếu bộ nhớ và không xử lý tốt bộ đệm. Ngày nay, CPU ngày càng nhanh hơn băng thông bộ nhớ và thường thì vấn đề luôn là truy cập bộ nhớ.

Bạn cũng có thể thử perf record ./my_program; perf reportđó là một cách dễ dàng để hồ sơ. Đọc các trang người đàn ông để tìm hiểu thêm.

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.