Làm cách nào để cấu hình mã C ++ chạy trên Linux?


1816

Tôi có một ứng dụng C ++, chạy trên Linux, tôi đang trong quá trình tối ưu hóa. Làm cách nào tôi có thể xác định chính xác khu vực nào trong mã của tôi đang chạy chậm?


27
Nếu bạn sẽ cung cấp thêm dữ liệu về ngăn xếp phát triển của mình, bạn có thể nhận được câu trả lời tốt hơn. Có các trình biên dịch từ Intel và Sun nhưng bạn phải sử dụng trình biên dịch của họ. Đó có phải là một lựa chọn không?
Nazgob

2
Nó đã được trả lời trên liên kết sau: stackoverflow.com/questions/2497211/ cấp
Kapil Gupta

4
Hầu hết các câu trả lời là codehồ sơ. Tuy nhiên, đảo ngược ưu tiên, khử răng cưa, tranh chấp tài nguyên, v.v ... đều có thể là các yếu tố tối ưu hóa và hiệu suất. Tôi nghĩ rằng mọi người đọc thông tin vào mã chậm của tôi . Câu hỏi thường gặp đang tham khảo chủ đề này.
tiếng ồn vô nghĩa


3
Tôi đã từng sử dụng pstack một cách ngẫu nhiên, hầu hết thời gian sẽ in ra ngăn xếp điển hình nhất trong đó chương trình là hầu hết thời gian, do đó chỉ ra nút cổ chai.
Jose Manuel Gomez Alvarez

Câu trả lời:


1406

Nếu mục tiêu của bạn là sử dụng một hồ sơ, hãy sử dụng một trong những gợi ý.

Tuy nhiên, nếu bạn đang vội và bạn có thể tự ngắt chương trình của mình theo trình gỡ lỗi trong khi nó bị chậm chủ quan, có một cách đơn giản để tìm các vấn đề về hiệu năng.

Chỉ cần dừng nó vài lần và mỗi lần nhìn vào ngăn xếp cuộc gọi. Nếu có một số mã đang lãng phí một số phần trăm thời gian, 20% hoặc 50% hoặc bất cứ điều gì, đó là xác suất mà bạn sẽ bắt được nó trong hành động trên mỗi mẫu. Vì vậy, đó là khoảng phần trăm các mẫu mà bạn sẽ thấy nó. Không có phỏng đoán giáo dục cần thiết. Nếu bạn có một dự đoán về vấn đề là gì, điều này sẽ chứng minh hoặc bác bỏ nó.

Bạn có thể có nhiều vấn đề hiệu suất của các kích cỡ khác nhau. Nếu bạn dọn sạch bất kỳ một trong số chúng, những cái còn lại sẽ chiếm tỷ lệ lớn hơn và dễ dàng phát hiện hơn trong các lần tiếp theo. Hiệu ứng phóng đại này , khi được kết hợp qua nhiều vấn đề, có thể dẫn đến các yếu tố tăng tốc thực sự lớn.

Hãy cẩn thận : Các lập trình viên có xu hướng hoài nghi về kỹ thuật này trừ khi họ đã sử dụng nó. Họ sẽ nói rằng các trình biên dịch cung cấp cho bạn thông tin này, nhưng điều đó chỉ đúng nếu họ lấy mẫu toàn bộ ngăn xếp cuộc gọi và sau đó cho phép bạn kiểm tra một bộ mẫu ngẫu nhiên. (Tóm tắt là nơi mất cái nhìn sâu sắc.) Biểu đồ cuộc gọi không cung cấp cho bạn thông tin tương tự, bởi vì

  1. Họ không tóm tắt ở cấp hướng dẫn, và
  2. Họ đưa ra những tóm tắt khó hiểu khi có sự đệ quy.

Họ cũng sẽ nói rằng nó chỉ hoạt động trên các chương trình đồ chơi, khi thực sự nó hoạt động trên bất kỳ chương trình nào và dường như nó hoạt động tốt hơn trên các chương trình lớn hơn, bởi vì chúng có xu hướng gặp nhiều vấn đề hơn. Họ sẽ nói rằng đôi khi nó tìm thấy những thứ không phải là vấn đề, nhưng điều đó chỉ đúng nếu bạn nhìn thấy một cái gì đó một lần . Nếu bạn thấy một vấn đề trên nhiều mẫu, thì đó là sự thật.

PS Điều này cũng có thể được thực hiện trên các chương trình đa luồng nếu có một cách để thu thập các mẫu ngăn xếp cuộc gọi của nhóm luồng tại một thời điểm, như có trong Java.

PPS Là một tổng quát, bạn càng có nhiều lớp trừu tượng trong phần mềm của mình, bạn càng có nhiều khả năng thấy đó là nguyên nhân của các vấn đề về hiệu năng (và cơ hội để tăng tốc).

Đã thêm : Có thể không rõ ràng, nhưng kỹ thuật lấy mẫu ngăn xếp hoạt động tốt như nhau khi có đệ quy. Lý do là thời gian sẽ được lưu bằng cách loại bỏ một lệnh được tính gần đúng bằng tỷ lệ mẫu có chứa nó, bất kể số lần nó có thể xảy ra trong một mẫu.

Một sự phản đối khác mà tôi thường nghe là: " Nó sẽ dừng ở một nơi nào đó ngẫu nhiên, và nó sẽ bỏ lỡ vấn đề thực sự ". Điều này xuất phát từ việc có một khái niệm trước về vấn đề thực sự là gì. Một tính chất quan trọng của vấn đề hiệu suất là họ bất chấp sự mong đợi. Lấy mẫu cho bạn biết một cái gì đó là một vấn đề, và phản ứng đầu tiên của bạn là sự hoài nghi. Điều đó là tự nhiên, nhưng bạn có thể chắc chắn nếu nó tìm thấy một vấn đề thì đó là sự thật và ngược lại.

Đã thêm : Hãy để tôi thực hiện một lời giải thích Bayes về cách thức hoạt động. Giả sử có một số lệnh I(gọi hoặc cách khác) trên ngăn xếp cuộc gọi một phần nhỏ fthời gian (và do đó chi phí rất nhiều). Để đơn giản, giả sử chúng ta không biết nó flà gì , nhưng giả sử nó là 0,1, 0,2, 0,3, ... 0,9, 1,0 và xác suất trước của mỗi khả năng này là 0,1, vì vậy tất cả các chi phí này đều có khả năng như nhau a-tiên nghiệm.

Sau đó, giả sử chúng ta chỉ lấy 2 mẫu ngăn xếp và chúng ta thấy hướng dẫn Itrên cả hai mẫu, quan sát được chỉ định o=2/2. Điều này cho chúng ta những ước tính mới về tần suất fcủa I, theo điều này:

Prior                                    
P(f=x) x  P(o=2/2|f=x) P(o=2/2&&f=x)  P(o=2/2&&f >= x)  P(f >= x | o=2/2)

0.1    1     1             0.1          0.1            0.25974026
0.1    0.9   0.81          0.081        0.181          0.47012987
0.1    0.8   0.64          0.064        0.245          0.636363636
0.1    0.7   0.49          0.049        0.294          0.763636364
0.1    0.6   0.36          0.036        0.33           0.857142857
0.1    0.5   0.25          0.025        0.355          0.922077922
0.1    0.4   0.16          0.016        0.371          0.963636364
0.1    0.3   0.09          0.009        0.38           0.987012987
0.1    0.2   0.04          0.004        0.384          0.997402597
0.1    0.1   0.01          0.001        0.385          1

                  P(o=2/2) 0.385                

Cột cuối cùng nói rằng, ví dụ, xác suất f> = 0,5 là 92%, tăng so với giả định trước đó là 60%.

Giả sử các giả định trước là khác nhau. Giả sử chúng ta giả sử P(f=0.1)là 0,991 (gần như chắc chắn) và tất cả các khả năng khác gần như không thể (0,001). Nói cách khác, sự chắc chắn trước của chúng tôi Ilà giá rẻ. Sau đó, chúng tôi nhận được:

Prior                                    
P(f=x) x  P(o=2/2|f=x) P(o=2/2&& f=x)  P(o=2/2&&f >= x)  P(f >= x | o=2/2)

0.001  1    1              0.001        0.001          0.072727273
0.001  0.9  0.81           0.00081      0.00181        0.131636364
0.001  0.8  0.64           0.00064      0.00245        0.178181818
0.001  0.7  0.49           0.00049      0.00294        0.213818182
0.001  0.6  0.36           0.00036      0.0033         0.24
0.001  0.5  0.25           0.00025      0.00355        0.258181818
0.001  0.4  0.16           0.00016      0.00371        0.269818182
0.001  0.3  0.09           0.00009      0.0038         0.276363636
0.001  0.2  0.04           0.00004      0.00384        0.279272727
0.991  0.1  0.01           0.00991      0.01375        1

                  P(o=2/2) 0.01375                

Bây giờ nó nói P(f >= 0.5)là 26%, tăng từ giả định trước đó là 0,6%. Vì vậy, Bayes cho phép chúng tôi cập nhật ước tính của chúng tôi về chi phí có thể xảy ra I. Nếu lượng dữ liệu nhỏ, nó không cho chúng ta biết chính xác chi phí là bao nhiêu, chỉ có điều nó đủ lớn để có giá trị sửa chữa.

Tuy nhiên, một cách khác để xem xét nó được gọi là Quy tắc thành công . Nếu bạn lật một đồng xu 2 lần và nó xuất hiện cả hai lần, điều đó cho bạn biết gì về trọng số có thể xảy ra của đồng tiền? Cách trả lời được tôn trọng là nói rằng đó là bản phân phối Beta, với giá trị trung bình (number of hits + 1) / (number of tries + 2) = (2+1)/(2+2) = 75%.

(Điều quan trọng là chúng ta thấy Inhiều hơn một lần. Nếu chúng ta chỉ nhìn thấy một lần, điều đó không cho chúng ta biết nhiều ngoại trừ f> 0.)

Vì vậy, ngay cả một số lượng rất nhỏ các mẫu có thể cho chúng ta biết rất nhiều về chi phí hướng dẫn mà nó nhìn thấy. (Và nó sẽ thấy chúng với tần số, trung bình, tỷ lệ với chi phí của họ. Nếu nlấy mẫu, và flà chi phí, sau đó Isẽ xuất hiện trên nf+/-sqrt(nf(1-f))mẫu. Ví dụ, n=10, f=0.3, đó là 3+/-1.4mẫu.)


Đã thêm : Để tạo cảm giác trực quan về sự khác biệt giữa đo lường và lấy mẫu ngăn xếp ngẫu nhiên:
Hiện tại có các trình định hình lấy mẫu ngăn xếp, ngay cả trên đồng hồ treo tường, nhưng những gì xuất hiện là các phép đo (hoặc đường nóng hoặc điểm nóng, từ đó một "nút cổ chai" có thể dễ dàng che giấu). Những gì họ không cho bạn thấy (và họ dễ dàng có thể) là chính các mẫu thực tế. Và nếu mục tiêu của bạn là tìm ra nút cổ chai, thì số lượng chúng bạn cần xem là, trung bình , 2 chia cho phần thời gian cần thiết. Vì vậy, nếu mất 30% thời gian, trung bình 2 / .3 = 6,7 mẫu sẽ hiển thị nó và khả năng 20 mẫu sẽ hiển thị là 99,2%.

Dưới đây là một minh họa rõ ràng về sự khác biệt giữa kiểm tra các phép đo và kiểm tra các mẫu ngăn xếp. Nút cổ chai có thể là một đốm lớn như thế này, hoặc nhiều nút nhỏ, nó không tạo ra sự khác biệt.

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

Đo lường là ngang; nó cho bạn biết phần nào của thói quen cụ thể thời gian thực hiện. Lấy mẫu là dọc. Nếu có bất kỳ cách nào để tránh những gì toàn bộ chương trình đang làm vào lúc đó và nếu bạn thấy nó trên một mẫu thứ hai , bạn đã tìm thấy nút cổ chai. Đó là điều làm nên sự khác biệt - nhìn thấy toàn bộ lý do cho thời gian được sử dụng, không chỉ là bao nhiêu.


292
Về cơ bản, đây là một hồ sơ lấy mẫu của một người nghèo, điều này thật tuyệt, nhưng bạn có nguy cơ về cỡ mẫu quá nhỏ sẽ có thể mang lại cho bạn kết quả hoàn toàn giả.
Crashworks

100
@Crash: Tôi sẽ không tranh luận về phần "người nghèo" :-) Đúng là độ chính xác của phép đo thống kê đòi hỏi nhiều mẫu, nhưng có hai mục tiêu mâu thuẫn - đo lường và vị trí vấn đề. Tôi tập trung vào thứ hai, mà bạn cần độ chính xác của vị trí, không phải độ chính xác của thước đo. Vì vậy, ví dụ, có thể có, giữa ngăn xếp, một hàm duy nhất gọi A (); chiếm 50% thời gian, nhưng nó có thể ở một hàm B lớn khác, cùng với nhiều cuộc gọi khác đến A () không tốn kém. Tóm tắt chính xác về thời gian chức năng có thể là một đầu mối, nhưng mọi mẫu ngăn xếp khác sẽ xác định chính xác vấn đề.
Mike Dunlavey

41
... Thế giới dường như nghĩ rằng một biểu đồ cuộc gọi, được chú thích với số lượng cuộc gọi và / hoặc thời gian trung bình, là đủ tốt. Không phải vậy. Và điều đáng buồn là, đối với những người lấy mẫu ngăn xếp cuộc gọi, thông tin hữu ích nhất sẽ ở ngay trước mặt họ, nhưng họ vứt nó đi, vì lợi ích của "thống kê".
Mike Dunlavey

30
Tôi không có ý không đồng ý với kỹ thuật của bạn. Rõ ràng tôi phụ thuộc khá nhiều vào các hồ sơ lấy mẫu stack-walk. Bây giờ tôi chỉ chỉ ra rằng có một số công cụ thực hiện theo cách tự động, điều này rất quan trọng khi bạn vượt qua điểm nhận chức năng từ 25% đến 15% và cần hạ nó xuống từ 1,2% xuống 0,6%.
Crashworks

13
-1: Ý tưởng gọn gàng, nhưng nếu bạn được trả tiền để làm việc trong một môi trường có định hướng hiệu suất vừa phải thì đây là một sự lãng phí thời gian của mọi người. Sử dụng một trình hồ sơ thực sự để chúng tôi không phải đi sau bạn và khắc phục các sự cố thực tế.
Sam Harwell

583

Bạn có thể sử dụng Valgrind với các tùy chọn sau

valgrind --tool=callgrind ./(Your binary)

Nó sẽ tạo ra một tập tin gọi là callgrind.out.x. Sau đó bạn có thể sử dụng kcachegrindcông cụ để đọc tệp này. Nó sẽ cung cấp cho bạn một phân tích đồ họa về những thứ có kết quả như dòng nào có giá bao nhiêu.


51
valgrind là rất tốt, nhưng được cảnh báo rằng nó sẽ làm cho chương trình của bạn darn chậm
Neves

30
Hãy xem Gprof2Dot để biết cách thay thế tuyệt vời để trực quan hóa đầu ra. ./gprof2dot.py -f callgrind callgrind.out.x | dot -Tsvg -o output.svg
Sebastian

2
@neves Có Valgrind không hữu ích lắm về tốc độ để định hình các ứng dụng "guxer" và "opencv" theo thời gian thực.
nhiệt tình

1
stackoverflow.com/questions/375913/ trên là một phần của vấn đề tốc độ.
Tõnu Samuel

3
@Sebastian: gprof2dothiện đang ở đây: github.com/jrfonseca/gprof2dot
John Zwinck

348

Tôi giả sử bạn đang sử dụng GCC. Giải pháp chuẩn sẽ là hồ sơ với gprof .

Hãy chắc chắn để thêm -pgvào biên dịch trước khi định hình:

cc -o myprog myprog.c utils.c -g -pg

Tôi chưa thử nhưng tôi đã nghe những điều hay về google-perftools . Nó chắc chắn là giá trị một thử.

Câu hỏi liên quan ở đây .

Một vài từ thông dụng khác nếu gprofkhông thực hiện công việc cho bạn: Valgrind , Intel VTune , Sun DTrace .


3
Tôi đồng ý rằng gprof là tiêu chuẩn hiện tại. Tuy nhiên, chỉ cần một lưu ý, Valgrind được sử dụng để lập hồ sơ rò rỉ bộ nhớ và các khía cạnh liên quan đến bộ nhớ khác trong các chương trình của bạn, không phải để tối ưu hóa tốc độ.
Bill Lizard

68
Bill, Trong bộ phần mềm vaglrind, bạn có thể tìm thấy callgrind và massif. Cả hai đều khá hữu ích để lập hồ sơ ứng dụng
dario minonne

7
@ Bill-the-Lizard: Một số nhận xét về gprof : stackoverflow.com/questions/1777556/alternigin-to-gprof/ trộm
Mike Dunlavey

6
gprof -pg chỉ là một xấp xỉ của hồ sơ callstack. Nó chèn các cuộc gọi mcount để theo dõi các chức năng đang gọi các chức năng khác. Nó sử dụng lấy mẫu dựa trên thời gian tiêu chuẩn cho, uh, thời gian. Sau đó, nó phân bổ số lần được lấy mẫu trong một hàm foo () trở lại các trình gọi của foo (), theo dự đoán cho số lượng các cuộc gọi. Vì vậy, nó không phân biệt giữa các cuộc gọi của chi phí khác nhau.
Krazy Glew

1
Với clang / clang ++, người ta có thể cân nhắc sử dụng trình cấu hình CPU của gperftools . Hãy cẩn thận: Bản thân tôi đã không làm như vậy.
einpoklum

257

Các hạt nhân mới hơn (ví dụ như các hạt nhân Ubuntu mới nhất) đi kèm với các công cụ 'perf' mới ( apt-get install linux-tools) AKA perf_events .

Chúng đi kèm với trình biên dịch lấy mẫu cổ điển ( man-page ) cũng như timechart tuyệt vời !

Điều quan trọng là các công cụ này có thể được định hình hệ thống chứ không chỉ xử lý hồ sơ - chúng có thể hiển thị sự tương tác giữa các luồng, tiến trình và kernel và cho phép bạn hiểu các phụ thuộc lập lịch và I / O giữa các tiến trình.

Văn bản thay thế


12
Công cụ tuyệt vời! Có cách nào để tôi có được chế độ xem "con bướm" điển hình bắt đầu từ kiểu "chính-> func1-> fun2" không? Tôi dường như không thể tìm ra điều đó ... perf reportdường như cung cấp cho tôi các tên hàm với cha mẹ gọi ... (vì vậy, nó là một chế độ xem hình con bướm đảo ngược)
kizzx2

Will, có thể hoàn hảo hiển thị timechart của hoạt động chủ đề; với thông tin số CPU được thêm vào? Tôi muốn xem khi nào và luồng nào đang chạy trên mọi CPU.
osgx

2
@ kizzx2 - bạn có thể sử dụng gprof2dotperf script. Công cụ rất đẹp!
bảnh bao

2
Ngay cả các hạt nhân mới hơn như 4.13 cũng có eBPF để định hình. Xem brendangregg.com/blog/2015-05-15/ebpf-one-small-step.htmlbrendangregg.com/ebpf.html
Andrew Stern

Một lời giới thiệu hay khác perftồn tại tại archive.li/9r927#selection-767.126-767.271 (Tại sao các vị thần SO quyết định xóa trang đó khỏi cơ sở tri thức SO nằm ngoài tôi ....)
ragerdl

75

Tôi sẽ sử dụng Valgrind và Callgrind làm cơ sở cho bộ công cụ định hình của mình. Điều quan trọng cần biết là Valgrind về cơ bản là một Máy ảo:

(wikipedia) Valgrind về bản chất là một máy ảo sử dụng các kỹ thuật biên dịch chỉ trong thời gian (JIT), bao gồm cả biên dịch lại động. Không có gì từ chương trình gốc được chạy trực tiếp trên bộ xử lý máy chủ. Thay vào đó, Valgrind trước tiên chuyển chương trình thành một hình thức tạm thời, đơn giản hơn gọi là Đại diện trung gian (IR), là một hình thức dựa trên bộ xử lý, trung lập SSA. Sau khi chuyển đổi, một công cụ (xem bên dưới) có thể tự do thực hiện bất kỳ biến đổi nào nó muốn trên IR, trước khi Valgrind chuyển IR trở lại thành mã máy và cho phép bộ xử lý máy chủ chạy nó.

Callgrind là một trình tạo hồ sơ dựa trên điều đó. Lợi ích chính là bạn không phải chạy ứng dụng trong nhiều giờ để có kết quả đáng tin cậy. Ngay cả một giây chạy cũng đủ để có được kết quả đáng tin cậy, vững chắc, bởi vì Callgrind là một hồ sơ không thăm dò .

Một công cụ khác được xây dựng dựa trên Valgrind là Massif. Tôi sử dụng nó để hồ sơ sử dụng bộ nhớ heap. Nó hoạt động rất tốt. Những gì nó làm là nó cung cấp cho bạn ảnh chụp nhanh về việc sử dụng bộ nhớ - thông tin chi tiết CÁI GÌ giữ phần trăm bộ nhớ và WHO đã đặt nó ở đó. Thông tin như vậy có sẵn tại các thời điểm khác nhau của ứng dụng chạy.


70

Câu trả lời để chạy valgrind --tool=callgrindkhông hoàn toàn đầy đủ nếu không có một số tùy chọn. Chúng tôi thường không muốn lập hồ sơ 10 phút thời gian khởi động chậm theo Valgrind và muốn lập hồ sơ chương trình của chúng tôi khi nó đang thực hiện một số nhiệm vụ.

Vì vậy, đây là những gì tôi khuyên bạn nên. Chạy chương trình trước:

valgrind --tool=callgrind --dump-instr=yes -v --instr-atstart=no ./binary > tmp

Bây giờ khi nó hoạt động và chúng tôi muốn bắt đầu cấu hình, chúng ta nên chạy trong một cửa sổ khác:

callgrind_control -i on

Điều này biến hồ sơ trên. Để tắt và dừng toàn bộ tác vụ, chúng tôi có thể sử dụng:

callgrind_control -k

Bây giờ chúng tôi có một số tệp có tên callgrind.out. * Trong thư mục hiện tại. Để xem kết quả hồ sơ sử dụng:

kcachegrind callgrind.out.*

Tôi khuyên bạn nên ở cửa sổ tiếp theo để nhấp vào tiêu đề cột "Tự", nếu không, nó cho thấy "main ()" là nhiệm vụ tốn thời gian nhất. "Tự" cho thấy mỗi chức năng tự mất bao nhiêu thời gian, không cùng với người phụ thuộc.


9
Bây giờ, trên một số lý do, các tệp callgrind.out. * Luôn trống. Thực thi callgrind_control -d rất hữu ích để buộc đổ dữ liệu vào đĩa.
Tõnu Samuel

3
Không thể. Các bối cảnh thông thường của tôi là một cái gì đó giống như toàn bộ MySQL hoặc PHP hoặc một số thứ lớn tương tự. Thường thì thậm chí không biết tôi muốn tách cái gì lúc đầu.
Tõnu Samuel

2
Hoặc trong trường hợp của tôi, chương trình của tôi thực sự tải một loạt dữ liệu vào bộ đệm LRU và tôi không muốn cấu hình nó. Vì vậy, tôi buộc tải một tập hợp con của bộ đệm khi khởi động và cấu hình mã chỉ bằng dữ liệu đó (để OS + CPU quản lý bộ nhớ sử dụng trong bộ đệm của tôi). Nó hoạt động, nhưng tải bộ đệm đó chậm và CPU tập trung vào mã mà tôi đang cố gắng cấu hình trong một ngữ cảnh khác, vì vậy callgrind tạo ra kết quả bị ô nhiễm nặng.
Mã số Abominator

2
cũng có CALLGRIND_TOGGLE_COLLECTđể bật / tắt bộ sưu tập theo chương trình; xem stackoverflow.com/a/13700817/288875
Andre Holzner

1
Ồ, tôi không biết điều này tồn tại, cảm ơn!
Vincent Fourmond

59

Đây là câu trả lời cho câu trả lời Gprof của Nazgob .

Tôi đã sử dụng Gprof trong vài ngày qua và đã tìm thấy ba hạn chế đáng kể, một trong số đó tôi chưa thấy tài liệu ở bất kỳ nơi nào khác (chưa):

  1. Nó không hoạt động đúng trên mã đa luồng, trừ khi bạn sử dụng một cách giải quyết

  2. Biểu đồ cuộc gọi bị nhầm lẫn bởi các con trỏ hàm. Ví dụ: Tôi có một hàm được gọi là multithread()cho phép tôi đa luồng một hàm được chỉ định trên một mảng được chỉ định (cả hai đều được truyền dưới dạng đối số). Tuy nhiên, Gprof xem tất cả các cuộc gọi multithread()tương đương với mục đích dành thời gian tính toán cho trẻ em. Vì một số chức năng tôi vượt qua multithread()mất nhiều thời gian hơn các chức năng khác, biểu đồ cuộc gọi của tôi chủ yếu là vô dụng. (Đối với những người thắc mắc liệu luồng có phải là vấn đề ở đây không: không, multithread()có thể tùy chọn và đã làm trong trường hợp này, chỉ chạy mọi thứ tuần tự trên luồng gọi).

  3. Nó nói ở đây rằng "... các số liệu cuộc gọi được tính bằng cách đếm, không lấy mẫu. Chúng hoàn toàn chính xác ...". Tuy nhiên, tôi thấy biểu đồ cuộc gọi của mình mang lại cho tôi 5345859132 + 784984078 dưới dạng số liệu thống kê cuộc gọi cho hàm được gọi nhiều nhất của tôi, trong đó số đầu tiên được coi là cuộc gọi trực tiếp và cuộc gọi đệ quy thứ hai (tất cả đều từ chính nó). Vì điều này ngụ ý rằng tôi có một lỗi, tôi đã đặt các bộ đếm dài (64 bit) vào mã và thực hiện lại hoạt động tương tự. Tổng số của tôi: 5345859132 trực tiếp và 78094395406 cuộc gọi tự đệ quy. Có rất nhiều chữ số ở đó, vì vậy tôi sẽ chỉ ra các cuộc gọi đệ quy mà tôi đo được là 78 ​​tỷ, so với 784m từ Gprof: một hệ số 100 khác nhau. Cả hai lần chạy đều là một chuỗi đơn và mã không được tối ưu hóa, một mã được biên dịch -gvà mã kia -pg.

Đây là GNU Gprof ( GNU Binutils cho Debian) 2.18.0.20080103 chạy dưới Debian Lenny 64 bit, nếu điều đó giúp được bất cứ ai.


Có, nó thực hiện lấy mẫu, nhưng không phải cho số liệu cuộc gọi. Thật thú vị, theo liên kết của bạn cuối cùng đã dẫn tôi đến một phiên bản cập nhật của trang hướng dẫn mà tôi đã liên kết trong bài đăng của mình, URL mới: sourceware.org/binutils/docs/gprof/ Điều này lặp lại trích dẫn trong phần (iii) câu trả lời của tôi, nhưng cũng cho biết "Trong các ứng dụng đa luồng hoặc các ứng dụng đơn luồng liên kết với các thư viện đa luồng, số đếm chỉ mang tính quyết định nếu chức năng đếm là an toàn luồng. (Lưu ý: hãy cẩn thận rằng hàm đếm mcount trong glibc không phải là luồng an toàn). "
Rob_b Before_edits

Tôi không rõ nếu điều này giải thích kết quả của tôi trong (iii). Mã của tôi đã được liên kết -lpthread -lm và khai báo cả biến tĩnh "pthread_t * thr" và "pthread_mutex_t nextLock = PTHREAD_MUTEX_INITIALIZER" ngay cả khi nó đang chạy một luồng. Tôi thường cho rằng "liên kết với các thư viện đa luồng" có nghĩa là thực sự sử dụng các thư viện đó, và ở mức độ lớn hơn thế này, nhưng tôi có thể sai!
Rob_b Before_edits

23

Sử dụng Valgrind, callgrind và kcachegrind:

valgrind --tool=callgrind ./(Your binary)

tạo callgrind.out.x. Đọc nó bằng cách sử dụng kcachegrind.

Sử dụng gprof (thêm -pg):

cc -o myprog myprog.c utils.c -g -pg 

(không tốt cho đa luồng, con trỏ hàm)

Sử dụng google-perftools:

Sử dụng lấy mẫu thời gian, các nút thắt I / O và CPU được tiết lộ.

Intel VTune là tốt nhất (miễn phí cho mục đích giáo dục).

Khác: AMD Codeanalyst (được thay thế bằng AMD CodeXL), OProfile, công cụ 'perf' (apt-get install linux-tools)


10

Khảo sát kỹ thuật định hình C ++

Trong câu trả lời này, tôi sẽ sử dụng một số công cụ khác nhau để phân tích một vài chương trình kiểm tra rất đơn giản, để so sánh cụ thể cách thức các công cụ đó hoạt động.

Chương trình kiểm tra sau rất đơn giản và thực hiện như sau:

  • maincác cuộc gọi fastmaybe_slow3 lần, một trong nhữngmaybe_slow cuộc gọi bị chậm

    Cuộc gọi chậm maybe_slowdài hơn gấp 10 lần và chi phối thời gian chạy nếu chúng ta xem xét các cuộc gọi đến chức năng con common. Lý tưởng nhất, công cụ định hình sẽ có thể chỉ cho chúng ta cuộc gọi chậm cụ thể.

  • cả hai fastmaybe_slowcuộc gọi common, chiếm phần lớn trong việc thực hiện chương trình

  • Giao diện chương trình là:

    ./main.out [n [seed]]

    và chương trình thực hiện O(n^2)các vòng lặp trong tổng số. seedchỉ để có được đầu ra khác nhau mà không ảnh hưởng đến thời gian chạy.

C chính

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>

uint64_t __attribute__ ((noinline)) common(uint64_t n, uint64_t seed) {
    for (uint64_t i = 0; i < n; ++i) {
        seed = (seed * seed) - (3 * seed) + 1;
    }
    return seed;
}

uint64_t __attribute__ ((noinline)) fast(uint64_t n, uint64_t seed) {
    uint64_t max = (n / 10) + 1;
    for (uint64_t i = 0; i < max; ++i) {
        seed = common(n, (seed * seed) - (3 * seed) + 1);
    }
    return seed;
}

uint64_t __attribute__ ((noinline)) maybe_slow(uint64_t n, uint64_t seed, int is_slow) {
    uint64_t max = n;
    if (is_slow) {
        max *= 10;
    }
    for (uint64_t i = 0; i < max; ++i) {
        seed = common(n, (seed * seed) - (3 * seed) + 1);
    }
    return seed;
}

int main(int argc, char **argv) {
    uint64_t n, seed;
    if (argc > 1) {
        n = strtoll(argv[1], NULL, 0);
    } else {
        n = 1;
    }
    if (argc > 2) {
        seed = strtoll(argv[2], NULL, 0);
    } else {
        seed = 0;
    }
    seed += maybe_slow(n, seed, 0);
    seed += fast(n, seed);
    seed += maybe_slow(n, seed, 1);
    seed += fast(n, seed);
    seed += maybe_slow(n, seed, 0);
    seed += fast(n, seed);
    printf("%" PRIX64 "\n", seed);
    return EXIT_SUCCESS;
}

gprof

gprof yêu cầu biên dịch lại phần mềm với thiết bị và nó cũng sử dụng phương pháp lấy mẫu cùng với thiết bị đó. Do đó, nó tạo ra sự cân bằng giữa độ chính xác (lấy mẫu không phải lúc nào cũng hoàn toàn chính xác và có thể bỏ qua các chức năng) và làm chậm thực thi (thiết bị và lấy mẫu là các kỹ thuật tương đối nhanh, không làm chậm quá trình thực thi).

gprof được tích hợp GCC / binutils, vì vậy tất cả những gì chúng ta phải làm là biên dịch với -pgtùy chọn bật gprof. Sau đó, chúng tôi chạy chương trình bình thường với tham số CLI kích thước tạo ra thời gian chạy hợp lý trong vài giây ( 10000):

gcc -pg -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
time ./main.out 10000

Vì lý do giáo dục, chúng tôi cũng sẽ chạy mà không kích hoạt tối ưu hóa. Lưu ý rằng điều này là vô ích trong thực tế, vì thông thường bạn chỉ quan tâm đến việc tối ưu hóa hiệu suất của chương trình được tối ưu hóa:

gcc -pg -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out 10000

Đầu tiên, timecho chúng ta biết rằng thời gian thực hiện có và không -pggiống nhau, điều này thật tuyệt: không bị chậm lại! Tuy nhiên, tôi đã thấy các tài khoản giảm tốc độ 2x - 3x trên phần mềm phức tạp, ví dụ như được hiển thị trong vé này .

Bởi vì chúng tôi đã biên dịch -pg, chạy chương trình sẽ tạo ra một tệpgmon.out tệp chứa dữ liệu lược tả.

Chúng ta có thể quan sát tệp đó bằng đồ họa với câu gprof2dothỏi tại: Có thể lấy biểu diễn đồ họa của kết quả gprof không?

sudo apt install graphviz
python3 -m pip install --user gprof2dot
gprof main.out > main.gprof
gprof2dot < main.gprof | dot -Tsvg -o output.svg

Tại đây, gprofcông cụ đọc gmon.outthông tin theo dõi và tạo một báo cáo có thể đọc được trong main.gprofđógprof2dot sau đó đọc để tạo biểu đồ.

Nguồn cho gprof2dot là tại: https://github.com/jrfonseca/gprof2dot

Chúng tôi quan sát sau đây để -O0chạy:

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

và để -O3chạy:

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

Đầu -O0ra là khá nhiều tự giải thích. Ví dụ, nó cho thấy rằng 3 maybe_slowcuộc gọi và các cuộc gọi con của chúng chiếm 97,56% tổng thời gian chạy, mặc dù việc thực hiện maybe_slowmà không có con chiếm 0,00% tổng thời gian thực hiện, tức là gần như toàn bộ thời gian dành cho chức năng đó đã dành cho con gọi.

TODO: tại sao mainbị thiếu từ -O3đầu ra, mặc dù tôi có thể thấy nó trên một btGDB? Thiếu chức năng từ đầu ra GProf Tôi nghĩ rằng đó là do gprof cũng lấy mẫu dựa trên công cụ được biên dịch của nó, và -O3 mainnó quá nhanh và không có mẫu.

Tôi chọn đầu ra SVG thay vì PNG vì SVG có thể tìm kiếm được bằng Ctrl + F và kích thước tệp có thể nhỏ hơn khoảng 10 lần. Ngoài ra, chiều rộng và chiều cao của hình ảnh được tạo ra có thể rất khiêm tốn với hàng chục nghìn pixel cho phần mềm phức tạp và eoglỗi Gnome 3.28.1 trong trường hợp đó đối với PNG, trong khi SVG được trình duyệt của tôi tự động mở. gimp 2.8 hoạt động tốt mặc dù, xem thêm:

nhưng ngay cả khi đó, bạn sẽ kéo hình ảnh xung quanh rất nhiều để tìm thấy những gì bạn muốn, xem ví dụ hình ảnh này từ một ví dụ phần mềm "thực" được lấy từ vé này :

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

Bạn có thể tìm thấy ngăn xếp cuộc gọi quan trọng nhất một cách dễ dàng với tất cả các dòng spaghetti nhỏ chưa được sắp xếp này đi qua nhau không? Có thể có những dotlựa chọn tốt hơn tôi chắc chắn, nhưng tôi không muốn đến đó ngay bây giờ. Những gì chúng tôi thực sự cần là một người xem dành riêng cho nó, nhưng tôi chưa tìm thấy:

Tuy nhiên, bạn có thể sử dụng bản đồ màu để giảm thiểu những vấn đề đó một chút. Ví dụ, trên hình ảnh lớn trước đó, cuối cùng tôi đã tìm được con đường quan trọng ở bên trái khi tôi đưa ra suy luận tuyệt vời rằng màu xanh lá cây xuất hiện sau màu đỏ, cuối cùng là màu xanh đậm hơn và tối hơn.

Ngoài ra, chúng ta cũng có thể quan sát đầu ra văn bản của gprofcông cụ binutils tích hợp mà trước đây chúng ta đã lưu tại:

cat main.gprof

Theo mặc định, điều này tạo ra một đầu ra cực kỳ dài để giải thích ý nghĩa của dữ liệu đầu ra. Vì tôi không thể giải thích tốt hơn thế, tôi sẽ để bạn tự đọc.

Khi bạn đã hiểu định dạng đầu ra dữ liệu, bạn có thể giảm mức độ chi tiết để chỉ hiển thị dữ liệu mà không có hướng dẫn với -btùy chọn:

gprof -b main.out

Trong ví dụ của chúng tôi, các kết quả đầu ra là cho -O0:

Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls   s/call   s/call  name    
100.35      3.67     3.67   123003     0.00     0.00  common
  0.00      3.67     0.00        3     0.00     0.03  fast
  0.00      3.67     0.00        3     0.00     1.19  maybe_slow

            Call graph


granularity: each sample hit covers 2 byte(s) for 0.27% of 3.67 seconds

index % time    self  children    called     name
                0.09    0.00    3003/123003      fast [4]
                3.58    0.00  120000/123003      maybe_slow [3]
[1]    100.0    3.67    0.00  123003         common [1]
-----------------------------------------------
                                                 <spontaneous>
[2]    100.0    0.00    3.67                 main [2]
                0.00    3.58       3/3           maybe_slow [3]
                0.00    0.09       3/3           fast [4]
-----------------------------------------------
                0.00    3.58       3/3           main [2]
[3]     97.6    0.00    3.58       3         maybe_slow [3]
                3.58    0.00  120000/123003      common [1]
-----------------------------------------------
                0.00    0.09       3/3           main [2]
[4]      2.4    0.00    0.09       3         fast [4]
                0.09    0.00    3003/123003      common [1]
-----------------------------------------------

Index by function name

   [1] common                  [4] fast                    [3] maybe_slow

và cho -O3:

Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  us/call  us/call  name    
100.52      1.84     1.84   123003    14.96    14.96  common

            Call graph


granularity: each sample hit covers 2 byte(s) for 0.54% of 1.84 seconds

index % time    self  children    called     name
                0.04    0.00    3003/123003      fast [3]
                1.79    0.00  120000/123003      maybe_slow [2]
[1]    100.0    1.84    0.00  123003         common [1]
-----------------------------------------------
                                                 <spontaneous>
[2]     97.6    0.00    1.79                 maybe_slow [2]
                1.79    0.00  120000/123003      common [1]
-----------------------------------------------
                                                 <spontaneous>
[3]      2.4    0.00    0.04                 fast [3]
                0.04    0.00    3003/123003      common [1]
-----------------------------------------------

Index by function name

   [1] common

Như một bản tóm tắt rất nhanh cho mỗi phần, ví dụ:

                0.00    3.58       3/3           main [2]
[3]     97.6    0.00    3.58       3         maybe_slow [3]
                3.58    0.00  120000/123003      common [1]

trung tâm xung quanh hàm được thụt lề ( maybe_flow). [3]là ID của chức năng đó. Phía trên chức năng, là những người gọi của nó, và bên dưới nó là các callees.

Đối với -O3, hãy xem ở đây như trong đầu ra đồ họa maybe_slowfastkhông có cha mẹ đã biết, đó là những gì tài liệu nói lên <spontaneous>.

Tôi không chắc có cách nào để thực hiện hồ sơ theo từng dòng với gprof: `gprof` dành thời gian cho các dòng mã cụ thể

cuộc gọi valgrind

valgrind chạy chương trình thông qua máy ảo valgrind. Điều này làm cho hồ sơ rất chính xác, nhưng nó cũng tạo ra một sự chậm lại rất lớn của chương trình. Tôi cũng đã đề cập đến kcachegrind trước đây tại: Công cụ để có được một biểu đồ gọi hàm mã hình ảnh của mã

callgrind là công cụ của valgrind để mã hồ sơ và kcachegrind là một chương trình KDE có thể trực quan hóa đầu ra của bộ nhớ cache.

Đầu tiên chúng ta phải xóa -pgcờ để quay lại quá trình biên dịch bình thường, nếu không thì việc chạy thực sự thất bại vớiProfiling timer expired , và vâng, điều này phổ biến đến mức tôi đã làm và có một câu hỏi Stack Overflow cho nó.

Vì vậy, chúng tôi biên dịch và chạy như:

sudo apt install kcachegrind valgrind
gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
time valgrind --tool=callgrind valgrind --dump-instr=yes \
  --collect-jumps=yes ./main.out 10000

Tôi kích hoạt --dump-instr=yes --collect-jumps=yes bởi vì điều này cũng loại bỏ thông tin cho phép chúng tôi xem phân tích hiệu suất trên mỗi dây chuyền lắp ráp, với chi phí bổ sung tương đối nhỏ.

Tắt dơi, timenói với chúng tôi rằng chương trình mất 29,5 giây để thực thi, vì vậy chúng tôi đã làm chậm khoảng 15 lần trong ví dụ này. Rõ ràng, sự chậm lại này sẽ là một hạn chế nghiêm trọng cho khối lượng công việc lớn hơn. Trên "ví dụ phần mềm thế giới thực" được đề cập ở đây , tôi đã quan sát thấy sự chậm lại 80 lần.

Việc chạy tạo ra một tệp dữ liệu hồ sơ có tên callgrind.out.<pid>ví dụ callgrind.out.8554trong trường hợp của tôi. Chúng tôi xem tập tin đó với:

kcachegrind callgrind.out.8554

trong đó hiển thị GUI chứa dữ liệu tương tự như đầu ra gprof văn bản:

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

Ngoài ra, nếu chúng ta đi vào tab "Biểu đồ cuộc gọi" phía dưới bên phải, chúng ta sẽ thấy một biểu đồ cuộc gọi mà chúng ta có thể xuất bằng cách nhấp chuột phải vào nó để có được hình ảnh sau với số lượng viền trắng không hợp lý :-)

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

Tôi nghĩ rằng fastkhông hiển thị trên biểu đồ đó vì kcachegrind phải đơn giản hóa việc trực quan hóa vì cuộc gọi đó chiếm quá ít thời gian, đây có thể sẽ là hành vi bạn muốn trên một chương trình thực. Menu nhấp chuột phải có một số cài đặt để kiểm soát thời điểm loại bỏ các nút như vậy, nhưng tôi không thể làm cho nó hiển thị một cuộc gọi ngắn như vậy sau một nỗ lực nhanh chóng. Nếu tôi nhấp vào fastcửa sổ bên trái, nó sẽ hiển thị biểu đồ cuộc gọi fast, để ngăn xếp đó thực sự được ghi lại. Chưa ai tìm được cách hiển thị biểu đồ cuộc gọi đồ thị hoàn chỉnh: Thực hiện cuộc gọi hiển thị tất cả các cuộc gọi chức năng trong biểu đồ cuộc gọi kcachegrind

TODO trên phần mềm C ++ phức tạp, tôi thấy một số mục thuộc loại <cycle N>, ví dụ: <cycle 11>nơi tôi mong đợi tên hàm, điều đó có nghĩa là gì? Tôi nhận thấy có một nút "Phát hiện chu kỳ" để bật và tắt, nhưng nó có nghĩa là gì?

perf từ linux-tools

perfdường như sử dụng các cơ chế lấy mẫu nhân Linux độc quyền. Điều này làm cho nó rất đơn giản để thiết lập, nhưng cũng không hoàn toàn chính xác.

sudo apt install linux-tools
time perf record -g ./main.out 10000

Điều này đã thêm 0,2 giây để thực thi, vì vậy chúng tôi rất khôn ngoan về thời gian, nhưng tôi vẫn không thấy nhiều sự quan tâm, sau khi mở rộng commonnút bằng mũi tên phải của bàn phím:

Samples: 7K of event 'cycles:uppp', Event count (approx.): 6228527608     
  Children      Self  Command   Shared Object     Symbol                  
-   99.98%    99.88%  main.out  main.out          [.] common              
     common                                                               
     0.11%     0.11%  main.out  [kernel]          [k] 0xffffffff8a6009e7  
     0.01%     0.01%  main.out  [kernel]          [k] 0xffffffff8a600158  
     0.01%     0.00%  main.out  [unknown]         [k] 0x0000000000000040  
     0.01%     0.00%  main.out  ld-2.27.so        [.] _dl_sysdep_start    
     0.01%     0.00%  main.out  ld-2.27.so        [.] dl_main             
     0.01%     0.00%  main.out  ld-2.27.so        [.] mprotect            
     0.01%     0.00%  main.out  ld-2.27.so        [.] _dl_map_object      
     0.01%     0.00%  main.out  ld-2.27.so        [.] _xstat              
     0.00%     0.00%  main.out  ld-2.27.so        [.] __GI___tunables_init
     0.00%     0.00%  main.out  [unknown]         [.] 0x2f3d4f4944555453  
     0.00%     0.00%  main.out  [unknown]         [.] 0x00007fff3cfc57ac  
     0.00%     0.00%  main.out  ld-2.27.so        [.] _start              

Vì vậy, sau đó tôi cố gắng điểm chuẩn -O0chương trình để xem nếu điều đó hiển thị bất cứ điều gì, và chỉ bây giờ, cuối cùng, tôi mới thấy một biểu đồ cuộc gọi:

Samples: 15K of event 'cycles:uppp', Event count (approx.): 12438962281   
  Children      Self  Command   Shared Object     Symbol                  
+   99.99%     0.00%  main.out  [unknown]         [.] 0x04be258d4c544155  
+   99.99%     0.00%  main.out  libc-2.27.so      [.] __libc_start_main   
-   99.99%     0.00%  main.out  main.out          [.] main                
   - main                                                                 
      - 97.54% maybe_slow                                                 
           common                                                         
      - 2.45% fast                                                        
           common                                                         
+   99.96%    99.85%  main.out  main.out          [.] common              
+   97.54%     0.03%  main.out  main.out          [.] maybe_slow          
+    2.45%     0.00%  main.out  main.out          [.] fast                
     0.11%     0.11%  main.out  [kernel]          [k] 0xffffffff8a6009e7  
     0.00%     0.00%  main.out  [unknown]         [k] 0x0000000000000040  
     0.00%     0.00%  main.out  ld-2.27.so        [.] _dl_sysdep_start    
     0.00%     0.00%  main.out  ld-2.27.so        [.] dl_main             
     0.00%     0.00%  main.out  ld-2.27.so        [.] _dl_lookup_symbol_x 
     0.00%     0.00%  main.out  [kernel]          [k] 0xffffffff8a600158  
     0.00%     0.00%  main.out  ld-2.27.so        [.] mmap64              
     0.00%     0.00%  main.out  ld-2.27.so        [.] _dl_map_object      
     0.00%     0.00%  main.out  ld-2.27.so        [.] __GI___tunables_init
     0.00%     0.00%  main.out  [unknown]         [.] 0x552e53555f6e653d  
     0.00%     0.00%  main.out  [unknown]         [.] 0x00007ffe1cf20fdb  
     0.00%     0.00%  main.out  ld-2.27.so        [.] _start              

TODO: chuyện gì đã xảy ra trong vụ -O3hành quyết? Có phải chỉ đơn giản là như vậy maybe_slowfastquá nhanh và không nhận được bất kỳ mẫu nào? Nó có hoạt động tốt với -O3các chương trình lớn hơn mất nhiều thời gian hơn để thực hiện không? Tôi đã bỏ lỡ một số tùy chọn CLI? Tôi phát hiện ra về -Fviệc kiểm soát tần số mẫu trong Hertz, nhưng tôi đã bật nó lên mức tối đa được phép theo mặc định -F 39500(có thể tăng lên sudo) và tôi vẫn không thấy các cuộc gọi rõ ràng.

Một điều thú vị perflà công cụ FlameGraph của Brendan Gregg, hiển thị thời gian của ngăn xếp cuộc gọi một cách rất gọn gàng cho phép bạn nhanh chóng xem các cuộc gọi lớn. Công cụ có sẵn tại địa chỉ: https://github.com/brendangregg/FlameGraph và cũng được đề cập trên mình Perf hướng dẫn tại địa chỉ: http://www.brendangregg.com/perf.html#FlameGraphs Khi tôi chạy perfmà không cần sudotôi ERROR: No stack counts foundnhư vậy cho bây giờ tôi sẽ làm điều đó với sudo:

git clone https://github.com/brendangregg/FlameGraph
sudo perf record -F 99 -g -o perf_with_stack.data ./main.out 10000
sudo perf script -i perf_with_stack.data | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > flamegraph.svg

nhưng trong một chương trình đơn giản như vậy, đầu ra không dễ hiểu lắm, vì chúng ta không thể dễ dàng nhìn thấy maybe_slowcũng như fasttrên biểu đồ đó:

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

Trong một ví dụ phức tạp hơn, nó trở nên rõ ràng về ý nghĩa của biểu đồ:

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

TODO có một bản ghi của [unknown] hàm trong ví dụ đó, tại sao vậy?

Một giao diện GUI hoàn hảo khác có thể đáng giá bao gồm:

  • Plugin Eclipse Trace Compass: https://www.eclipse.org/tracecompass/

    Nhưng điều này có nhược điểm là trước tiên bạn phải chuyển đổi dữ liệu sang Định dạng theo dõi chung, có thể được thực hiện perf data --to-ctf, nhưng nó cần phải được bật khi xây dựng / có perfđủ mới, một trong hai trường hợp không phải là hoàn hảo. Ubuntu 18.04

  • https://github.com/KDAB/hotspot

    Nhược điểm của điều này là dường như không có gói Ubuntu và việc xây dựng nó đòi hỏi Qt 5.10 trong khi Ubuntu 18.04 ở mức Qt 5.9.

gperftools

Trước đây được gọi là "Công cụ hiệu suất của Google", nguồn: https://github.com/gperftools/gperftools Mẫu dựa trên.

Đầu tiên cài đặt gperftools với:

sudo apt install google-perftools

Sau đó, chúng ta có thể kích hoạt trình cấu hình CPU gperftools theo hai cách: tại thời gian chạy hoặc tại thời gian xây dựng.

Trong thời gian chạy, chúng ta phải vượt qua thiết lập LD_PRELOADđể trỏ tới libprofiler.so, mà bạn có thể tìm thấy locate libprofiler.so, ví dụ như trên hệ thống của tôi:

gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libprofiler.so \
  CPUPROFILE=prof.out ./main.out 10000

Ngoài ra, chúng ta có thể xây dựng thư viện tại thời điểm liên kết, phân phối truyền LD_PRELOADkhi chạy:

gcc -Wl,--no-as-needed,-lprofiler,--as-needed -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
CPUPROFILE=prof.out ./main.out 10000

Xem thêm: gperftools - tập tin hồ sơ không bị đổ

Cách tốt nhất để xem dữ liệu này tôi đã tìm thấy cho đến nay là làm cho đầu ra pprof có cùng định dạng mà kcachegrind lấy làm đầu vào (vâng, công cụ Valgrind-project-viewer-viewer) và sử dụng kcachegrind để xem:

google-pprof --callgrind main.out prof.out  > callgrind.out
kcachegrind callgrind.out

Sau khi chạy với một trong các phương thức đó, chúng tôi nhận được prof.outtệp dữ liệu hồ sơ làm đầu ra. Chúng ta có thể xem tệp đó dưới dạng SVG với:

google-pprof --web main.out prof.out

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

cung cấp dưới dạng biểu đồ cuộc gọi quen thuộc như các công cụ khác, nhưng với đơn vị số lượng mẫu không rõ ràng hơn là giây.

Ngoài ra, chúng tôi cũng có thể nhận được một số dữ liệu văn bản với:

google-pprof --text main.out prof.out

cung cấp cho:

Using local file main.out.
Using local file prof.out.
Total: 187 samples
     187 100.0% 100.0%      187 100.0% common
       0   0.0% 100.0%      187 100.0% __libc_start_main
       0   0.0% 100.0%      187 100.0% _start
       0   0.0% 100.0%        4   2.1% fast
       0   0.0% 100.0%      187 100.0% main
       0   0.0% 100.0%      183  97.9% maybe_slow

Xem thêm: Cách sử dụng google perf tools

Đã thử nghiệm trong Ubuntu 18.04, gprof2dot 2019.11.30, valgrind 3.13.0, perf 4.15.18, Linux kernel 4.15.0, FLameGraph 1a0dc6985aad06e76857cf2a354bd5ba0c9ce96b, gperftools 2.5-2.


2
Theo mặc định bản ghi perf sử dụng thanh ghi con trỏ khung. Trình biên dịch hiện đại không ghi lại địa chỉ khung và thay vào đó sử dụng thanh ghi làm mục đích chung. Cách khác là biên dịch bằng -fno-omit-frame-pointercờ hoặc sử dụng một cách khác: ghi lại với --call-graph "dwarf"hoặc --call-graph "lbr"tùy theo kịch bản của bạn.
Jorge Bellon

5

Đối với các chương trình đơn luồng, bạn có thể sử dụng igprof , Trình tạo hồ sơ đáng kinh ngạc: https://igprof.org/ .

Đây là một hồ sơ lấy mẫu, dọc theo dòng câu trả lời ... dài ... của Mike Dunlavey, sẽ tặng quà kết quả trong một cây ngăn xếp cuộc gọi có thể duyệt, được chú thích với thời gian hoặc bộ nhớ dành cho mỗi chức năng, được tích lũy hoặc mỗi chức năng.


Có vẻ thú vị, nhưng không biên dịch được với GCC 9.2. (Debian / Sid) Tôi đã tạo một vấn đề trên github.
Basile Starynkevitch

5

Cũng đáng đề cập là

  1. HPCToolkit ( http://hpctoolkit.org/ ) - Nguồn mở, hoạt động cho các chương trình song song và có GUI để xem kết quả theo nhiều cách
  2. Intel VTune ( https://software.intel.com/en-us/vtune ) - Nếu bạn có trình biên dịch intel thì điều này rất tốt
  3. TAU ( http://www.cs.uoregon.edu/research/tau/home.php )

Tôi đã sử dụng HPCToolkit và VTune và họ rất hiệu quả trong việc tìm kiếm cực dài trong lều và không cần mã của bạn được biên dịch lại (ngoại trừ việc bạn phải sử dụng kiểu xây dựng -g -O hoặc RelWithDebInfo trong CMake để có được đầu ra có ý nghĩa) . Tôi đã nghe nói TAU tương tự về khả năng.


4

Đây là hai phương pháp tôi sử dụng để tăng tốc mã của mình:

Đối với các ứng dụng ràng buộc CPU:

  1. Sử dụng một trình lược tả trong chế độ DEBUG để xác định các phần nghi vấn trong mã của bạn
  2. Sau đó chuyển sang chế độ ĐÁNG TIN CẬY và nhận xét các phần nghi vấn trong mã của bạn (còn sơ khai không có gì) cho đến khi bạn thấy các thay đổi về hiệu suất.

Đối với các ứng dụng ràng buộc I / O:

  1. Sử dụng một trình lược tả trong chế độ ĐÁNG TIN CẬY để xác định các phần nghi vấn trong mã của bạn.

Lưu ý

Nếu bạn không có hồ sơ, hãy sử dụng hồ sơ của người nghèo. Nhấn tạm dừng trong khi gỡ lỗi ứng dụng của bạn. Hầu hết các bộ nhà phát triển sẽ đột nhập vào lắp ráp với số dòng nhận xét. Bạn có khả năng thống kê hạ cánh ở một khu vực đang ăn hầu hết các chu kỳ CPU của bạn.

Đối với CPU, lý do cho profiling ở DEBUG chế độ là bởi vì nếu cố gắng bạn profiling ở CHÍ chế độ, trình biên dịch sẽ giảm môn toán, vòng vectorize, và chức năng nội tuyến mà có xu hướng glob mã của bạn vào một mớ hỗn độn un-trí trên bản đồ khi nó được lắp ráp. Một mớ hỗn độn không thể ánh xạ có nghĩa là trình hồ sơ của bạn sẽ không thể xác định rõ ràng những gì đang mất quá lâu vì lắp ráp có thể không tương ứng với mã nguồn được tối ưu hóa . Nếu bạn cần hiệu suất (ví dụ: nhạy cảm với thời gian) của chế độ ĐÁNG TIN CẬY , hãy tắt các tính năng của trình gỡ lỗi khi cần để duy trì hiệu suất có thể sử dụng.

Đối với ràng buộc I / O, trình lược tả vẫn có thể xác định các hoạt động I / O ở chế độ ĐÁNG TIN vì các hoạt động I / O được liên kết bên ngoài với thư viện dùng chung (hầu hết thời gian) hoặc trong trường hợp xấu nhất, sẽ dẫn đến một hệ thống gọi vectơ ngắt (cũng có thể dễ dàng xác định bởi trình lược tả).


2
+1 Phương pháp của người nghèo cũng hoạt động tốt đối với I / O bị ràng buộc như đối với CPU bị ràng buộc và tôi khuyên bạn nên thực hiện tất cả điều chỉnh hiệu suất trong chế độ DEBUG. Khi bạn điều chỉnh xong, hãy bật ĐÁNG TIN CẬY. Nó sẽ cải thiện nếu chương trình được gắn với CPU trong mã của bạn. Đây là một video thô nhưng ngắn của quá trình.
Mike Dunlavey

3
Tôi sẽ không sử dụng các bản dựng DEBUG để lập hồ sơ hiệu suất. Thông thường tôi đã thấy rằng các phần quan trọng về hiệu năng trong chế độ DEBUG hoàn toàn được tối ưu hóa trong chế độ phát hành. Một vấn đề khác là việc sử dụng các xác nhận trong mã gỡ lỗi làm tăng thêm hiệu suất.
gast128

3
Bạn đã đọc bài viết của tôi ở tất cả? "Nếu bạn cần hiệu suất (ví dụ: nhạy cảm với thời gian) của chế độ RELEASE, hãy tắt các tính năng của trình gỡ lỗi khi cần để duy trì hiệu suất có thể sử dụng được", "Sau đó chuyển sang chế độ ĐÁNG TIN CẬY và nhận xét các phần nghi vấn của mã của bạn (Không có gì cả) cho đến khi bạn thấy thay đổi hiệu suất. "? Tôi đã nói kiểm tra các khu vực có vấn đề có thể xảy ra trong chế độ gỡ lỗi và xác minh những sự cố đó trong chế độ phát hành để tránh những cạm bẫy mà bạn đề cập.
seo


2

Bạn có thể sử dụng khung ghi nhật ký như loguruvì nó bao gồm dấu thời gian và tổng thời gian hoạt động có thể được sử dụng độc đáo để định hình:


1

Trong công việc, chúng tôi có một công cụ thực sự tốt giúp chúng tôi theo dõi những gì chúng tôi muốn về mặt lập kế hoạch. Điều này đã được hữu ích nhiều lần.

Đó là trong C ++ và phải được tùy chỉnh theo nhu cầu của bạn. Thật không may, tôi không thể chia sẻ mã, chỉ là các khái niệm. Bạn sử dụng volatilebộ đệm "lớn" chứa dấu thời gian và ID sự kiện mà bạn có thể kết xuất tử thi hoặc sau khi dừng hệ thống ghi nhật ký (và ví dụ như đổ tệp này vào một tệp).

Bạn truy xuất cái gọi là bộ đệm lớn với tất cả dữ liệu và một giao diện nhỏ phân tích cú pháp và hiển thị các sự kiện có tên (tăng / giảm + giá trị) giống như một máy hiện sóng có màu sắc (được định cấu hình trong .hpp tệp).

Bạn tùy chỉnh số lượng sự kiện được tạo để chỉ tập trung vào những gì bạn mong muốn. Nó giúp chúng tôi rất nhiều trong việc lên lịch các vấn đề trong khi tiêu thụ lượng CPU chúng tôi muốn dựa trên số lượng sự kiện được ghi lại mỗi giây.

Bạn cần 3 tệp:

toolname.hpp // interface
toolname.cpp // code
tool_events_id.hpp // Events ID

Khái niệm này là để xác định các sự kiện tool_events_id.hppnhư thế:

// EVENT_NAME                         ID      BEGIN_END BG_COLOR NAME
#define SOCK_PDU_RECV_D               0x0301  //@D00301 BGEEAAAA # TX_PDU_Recv
#define SOCK_PDU_RECV_F               0x0302  //@F00301 BGEEAAAA # TX_PDU_Recv

Bạn cũng xác định một vài chức năng trong toolname.hpp:

#define LOG_LEVEL_ERROR 0
#define LOG_LEVEL_WARN 1
// ...

void init(void);
void probe(id,payload);
// etc

Bất cứ nơi nào trong mã bạn có thể sử dụng:

toolname<LOG_LEVEL>::log(EVENT_NAME,VALUE);

Các probe chức năng sử dụng một vài dây chuyền lắp ráp để lấy dấu thời gian đồng hồ càng sớm càng tốt và sau đó đặt một mục trong bộ đệm. Chúng tôi cũng có một sự gia tăng nguyên tử để tìm một chỉ mục nơi lưu trữ sự kiện nhật ký một cách an toàn. Tất nhiên bộ đệm là hình tròn.

Hy vọng ý tưởng không bị xáo trộn vì thiếu mã mẫu.


1

Trên thực tế, có một chút ngạc nhiên khi không có nhiều đề cập về google / điểm chuẩn , trong khi nó hơi phức tạp khi ghim vào vùng mã cụ thể, đặc biệt nếu cơ sở mã hơi lớn, tuy nhiên tôi thấy điều này thực sự hữu ích khi được sử dụng kết hợp vớicallgrind

IMHO xác định phần gây ra nút cổ chai là chìa khóa ở đây. Tuy nhiên, tôi sẽ thử và trả lời các câu hỏi sau trước và chọn công cụ dựa trên đó

  1. thuật toán của tôi có đúng không?
  2. Có khóa nào được chứng minh là cổ chai không?
  3. Có một phần cụ thể của mã chứng minh là thủ phạm?
  4. Làm thế nào về IO, xử lý và tối ưu hóa?

valgrindvới sự kết hợp callrindkcachegrindnên cung cấp một ước tính hợp lý về các điểm trên và một khi đã xác định rằng có vấn đề với một số phần của mã, tôi khuyên bạn nên làm một điểm chuẩn vi mô google benchmarklà một nơi tốt để bắt đầu.


1

Sử dụng -pgcờ khi biên dịch và liên kết mã và chạy tệp thực thi. Trong khi chương trình này được thực thi, dữ liệu lược tả được thu thập trong tệp a.out.
Có hai loại hồ sơ khác nhau.

1- Cấu hình phẳng:
bằng cách chạy lệnh, gprog --flat-profile a.outbạn đã nhận được dữ liệu sau
- bao nhiêu phần trăm thời gian tổng thể đã dành cho chức năng,
- đã dành bao nhiêu giây cho một chức năng, bao gồm và loại trừ các cuộc gọi đến các chức năng phụ,
- số lượng cuộc gọi,
- thời gian trung bình cho mỗi cuộc gọi.

2- biểu đồ hồ sơ
cho chúng tôi lệnh gprof --graph a.outđể có được dữ liệu sau cho mỗi chức năng bao gồm
- Trong mỗi phần, một chức năng được đánh dấu bằng một số chỉ mục.
- Hàm trên, có một danh sách các hàm gọi hàm.
- Bên dưới chức năng, có một danh sách các chức năng được gọi bởi chức năng.

Để có thêm thông tin, bạn có thể xem trong https://sourceware.org/binutils/docs-2.32/gprof/


0

Như không ai đề cập đến Arm MAP, tôi muốn thêm nó với tư cách cá nhân, tôi đã sử dụng thành công Map để lập hồ sơ cho một chương trình khoa học C ++.

Arm MAP là trình lược tả cho các mã C, C ++, Fortran và F90 song song, đa luồng hoặc đơn. Nó cung cấp phân tích chuyên sâu và xác định chính xác tắc nghẽn cho dòng nguồn. Không giống như hầu hết các trình biên dịch, nó được thiết kế để có thể cấu hình pthread, OpenMP hoặc MPI cho mã song song và luồng.

MAP là phần mềm thương mại.


0

sử dụng một phần mềm gỡ lỗi làm thế nào để xác định nơi mã đang chạy chậm?

Chỉ cần nghĩ rằng bạn có một chướng ngại vật trong khi bạn đang chuyển động thì nó sẽ làm giảm tốc độ của bạn

giống như việc lặp lại phân bổ không mong muốn, tràn bộ đệm, tìm kiếm, rò rỉ bộ nhớ, v.v ... các hoạt động tiêu tốn nhiều năng lượng thực thi hơn, nó sẽ ảnh hưởng xấu đến hiệu suất của mã, Hãy chắc chắn thêm -pg để biên dịch trước khi lược tả:

g++ your_prg.cpp -pghoặc cc my_program.cpp -g -pgtheo trình biên dịch của bạn

chưa thử nhưng tôi đã nghe những điều hay về google-perftools. Nó chắc chắn là giá trị một thử.

valgrind --tool=callgrind ./(Your binary)

Nó sẽ tạo một tệp có tên gmon.out hoặc callgrind.out.x. Sau đó, bạn có thể sử dụng kcachegrind hoặc công cụ gỡ lỗi để đọc tệp này. Nó sẽ cung cấp cho bạn một phân tích đồ họa về những thứ có kết quả như dòng nào có giá bao nhiêu.

tôi nghĩ vậy

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.