Làm cách nào để viết một điểm chuẩn vi mô chính xác trong Java?


870

Làm thế nào để bạn viết (và chạy) một điểm chuẩn vi mô chính xác trong Java?

Tôi đang tìm kiếm một số mẫu mã và ý kiến ​​minh họa những điều khác nhau để suy nghĩ.

Ví dụ: Nên đo điểm chuẩn thời gian / lần lặp hoặc lần lặp / lần, và tại sao?

Liên quan: Điểm chuẩn đồng hồ bấm giờ có được chấp nhận?


Xem [câu hỏi này] [1] từ vài phút trước để biết một số thông tin liên quan. chỉnh sửa: xin lỗi, đây không phải là một câu trả lời. Tôi nên đăng một bình luận. [1]: stackoverflow.com/questions/503877/ Mạnh
Tiago

Đó là sau khi lên kế hoạch giới thiệu poster của câu hỏi đó cho một câu hỏi như thế này mà tôi lưu ý rằng câu hỏi này không tồn tại. Vì vậy, đây là, hy vọng nó sẽ tập hợp một số lời khuyên tốt theo thời gian.
John Nilsson

5
Java 9 có thể cung cấp một số tính năng cho điểm chuẩn vi mô: openjdk.java.net/jeps/230
Raedwald

1
@Raedwald Tôi nghĩ rằng JEP nhằm mục đích thêm một số điểm chuẩn vi mô vào mã JDK, nhưng tôi không nghĩ rằng jmh sẽ được đưa vào JDK ...
assylias

1
@Raedwald Xin chào từ tương lai. Nó đã không làm cho việc cắt giảm .
Michael

Câu trả lời:


787

Mẹo về cách viết điểm chuẩn vi mô từ những người tạo Java HotSpot :

Quy tắc 0: Đọc một bài báo có uy tín về JVM và điểm chuẩn vi mô. Một người tốt là Brian Goetz, 2005 . Đừng mong đợi quá nhiều từ điểm chuẩn vi mô; chúng chỉ đo một phạm vi giới hạn các đặc tính hiệu năng JVM.

Quy tắc 1: Luôn bao gồm giai đoạn khởi động chạy suốt nhân thử nghiệm của bạn, đủ để kích hoạt tất cả các khởi tạo và biên dịch trước khi pha thời gian. (Lặp lại ít hơn là OK trong giai đoạn khởi động. Quy tắc của ngón tay cái là vài chục ngàn lần lặp vòng lặp bên trong.)

Quy tắc 2: Luôn luôn chạy với -XX:+PrintCompilation, -verbose:gcv.v., vì vậy bạn có thể xác minh rằng trình biên dịch và các phần khác của JVM không hoạt động bất ngờ trong giai đoạn thời gian của bạn.

Quy tắc 2.1: In tin nhắn ở đầu và cuối giai đoạn khởi động và thời gian khởi động, do đó bạn có thể xác minh rằng không có đầu ra từ Quy tắc 2 trong giai đoạn thời gian.

Quy tắc 3: Hãy nhận biết sự khác biệt giữa -client-server, và OSR và các phần tổng hợp thông thường. Các -XX:+PrintCompilationcờ báo cáo biên soạn OSR với một ít-dấu hiệu để biểu thị điểm vào phi ban đầu, ví dụ: Trouble$1::run @ 2 (41 bytes). Thích máy chủ hơn cho máy khách và thường xuyên đến OSR, nếu bạn có hiệu suất tốt nhất.

Quy tắc 4: Hãy nhận biết các hiệu ứng khởi tạo. Không in lần đầu tiên trong giai đoạn thời gian của bạn, vì in tải và khởi tạo các lớp. Không tải các lớp mới bên ngoài giai đoạn khởi động (hoặc giai đoạn báo cáo cuối cùng), trừ khi bạn đang kiểm tra tải lớp cụ thể (và trong trường hợp đó chỉ tải các lớp kiểm tra). Quy tắc 2 là tuyến phòng thủ đầu tiên của bạn chống lại các hiệu ứng như vậy.

Quy tắc 5: Hãy nhận biết các hiệu ứng deoptimization và biên dịch lại. Không sử dụng bất kỳ đường dẫn mã nào lần đầu tiên trong giai đoạn thời gian, bởi vì trình biên dịch có thể rác và biên dịch lại mã, dựa trên giả định lạc quan trước đó rằng đường dẫn này hoàn toàn không được sử dụng. Quy tắc 2 là tuyến phòng thủ đầu tiên của bạn chống lại các hiệu ứng như vậy.

Quy tắc 6: Sử dụng các công cụ thích hợp để đọc suy nghĩ của người biên dịch và hy vọng sẽ ngạc nhiên với mã mà nó tạo ra. Tự kiểm tra mã trước khi hình thành lý thuyết về những gì làm cho một cái gì đó nhanh hơn hoặc chậm hơn.

Quy tắc 7: Giảm tiếng ồn trong các phép đo của bạn. Chạy điểm chuẩn của bạn trên một máy yên tĩnh và chạy nó nhiều lần, loại bỏ các ngoại lệ. Sử dụng -Xbatchđể tuần tự hóa trình biên dịch với ứng dụng và xem xét cài đặt -XX:CICompilerCount=1để ngăn trình biên dịch chạy song song với chính nó. Cố gắng hết sức để giảm chi phí GC, đặt Xmx(đủ lớn) bằng Xmsvà sử dụng UseEpsilonGCnếu có sẵn.

Quy tắc 8: Sử dụng một thư viện cho điểm chuẩn của bạn vì nó có thể hiệu quả hơn và đã được gỡ lỗi cho mục đích duy nhất này. Chẳng hạn như Điểm chuẩn UCSD xuất sắc của JMH , Caliper hoặc Bill và Paul cho Java .


5
Đây cũng là một bài viết thú vị: ibm.com/developerworks/java/l Library / j
John Nilsson

142
Ngoài ra, không bao giờ sử dụng System.cienTimeMillis () trừ khi bạn ổn với độ chính xác + hoặc - 15 ms, điển hình trên hầu hết các kết hợp OS + JVM. Sử dụng System.nanoTime () thay thế.
Scott Carey

5
Một số bài viết từ javaOne: azulsystems.com/events/javaone_2009/session/iêu
bestsss

93
Cần lưu ý rằng System.nanoTime()không được đảm bảo là chính xác hơn System.currentTimeMillis(). Nó chỉ được đảm bảo là ít nhất là chính xác. Nó thường là chính xác hơn, tuy nhiên.
Trọng lực

41
Lý do chính tại sao người ta phải sử dụng System.nanoTime()thay vì System.currentTimeMillis()là trước đây được đảm bảo là tăng đơn điệu. Trừ các giá trị được trả về hai lần currentTimeMillisgọi thực sự có thể cho kết quả âm, có thể do thời gian hệ thống được điều chỉnh bởi một số trình nền NTP.
Waldheinz

239

Tôi biết câu hỏi này đã được đánh dấu là đã trả lời nhưng tôi muốn đề cập đến hai thư viện giúp chúng tôi viết điểm chuẩn vi mô

Caliper từ Google

Bắt đầu hướng dẫn

  1. http://codingjunkie.net/micro-benchmarking-with-caliper/
  2. http://vertexlabs.co.uk/blog/caliper

JMH từ OpenJDK

Bắt đầu hướng dẫn

  1. Tránh các cạm bẫy điểm chuẩn trên JVM
  2. http://nitschinger.at/Using-JMH-for-Java-Microbenchmarking
  3. http://java-performance.info/jmh/

37
+1 nó có thể đã được thêm vào như Quy tắc 8 của câu trả lời được chấp nhận: Quy tắc 8: bởi vì rất nhiều điều có thể sai, bạn có thể nên sử dụng một thư viện hiện có thay vì cố gắng tự làm!
assylias

8
@Pangea jmh có lẽ vượt trội hơn Caliper hiện nay, Xem thêm: Groups.google.com.vn/forum/#!msg/m cơ
sympathy / m4opvy4xq3U / trên

86

Những điều quan trọng đối với điểm chuẩn Java là:

  • Ấm lên JIT đầu tiên bằng cách chạy mã nhiều lần trước khi thời gian
  • Đảm bảo bạn chạy nó đủ lâu để có thể đo lường kết quả sau vài giây hoặc (tốt hơn) hàng chục giây
  • Mặc dù bạn không thể gọi System.gc()giữa các lần lặp, nhưng bạn nên chạy nó giữa các lần kiểm tra, để mỗi bài kiểm tra hy vọng sẽ có một không gian bộ nhớ "sạch" để làm việc. (Vâng, gc()là một gợi ý hơn là một sự đảm bảo, nhưng rất có khả năng nó thực sự sẽ thu gom rác theo kinh nghiệm của tôi.)
  • Tôi thích hiển thị các lần lặp và thời gian, và một số điểm thời gian / lần lặp có thể được thu nhỏ sao cho thuật toán "tốt nhất" đạt được điểm 1.0 và các điểm khác được tính theo cách tương đối. Điều này có nghĩa là bạn có thể chạy tất cả các thuật toán trong một thời gian dài, thay đổi cả số lần lặp và thời gian, nhưng vẫn nhận được kết quả tương đương.

Tôi chỉ đang trong quá trình viết blog về thiết kế khung điểm chuẩn trong .NET. Tôi đã có một vài các bài viết trước đây mà có thể cung cấp cho bạn một số ý tưởng - không mọi thứ sẽ thích hợp, tất nhiên, nhưng một số của nó có thể được.


3
Nitlog nhỏ: IMO "sao cho mỗi thử nghiệm được" nên "để mỗi thử nghiệm có thể nhận được" vì trước đây mang lại ấn tượng rằng việc gọi gc luôn giải phóng bộ nhớ không sử dụng.
Sanjay T. Sharma

@ SanjayT.Sharma: Vâng, ý định là nó thực sự làm. Mặc dù nó không được bảo đảm nghiêm ngặt, nhưng đây thực sự là một gợi ý khá mạnh mẽ. Sẽ chỉnh sửa để rõ ràng hơn.
Jon Skeet

1
Tôi không đồng ý với việc gọi System.gc (). Đó là một gợi ý, đó là tất cả. Thậm chí không "nó sẽ hy vọng làm một cái gì đó". Bạn không bao giờ nên gọi nó. Đây là lập trình, không phải nghệ thuật.
gyorgyabraham

13
@gyabraham: Vâng, đó là một gợi ý - nhưng đó là một gợi ý mà tôi thường thấy. Vì vậy, nếu bạn không thích sử dụng System.gc(), làm thế nào để bạn đề xuất giảm thiểu thu gom rác trong một thử nghiệm do các đối tượng được tạo trong các thử nghiệm trước đó? Tôi thực dụng, không giáo điều.
Jon Skeet

9
@gyabraham: Tôi không biết ý của bạn là "dự phòng tuyệt vời". Bạn có thể giải thích, và một lần nữa - bạn có một đề xuất để cho kết quả tốt hơn không? Tôi đã nói rõ ràng rằng đó không phải là một sự đảm bảo ...
Jon Skeet

48

jmh là một bổ sung gần đây cho OpenJDK và đã được viết bởi một số kỹ sư hiệu suất từ ​​Oracle. Chắc chắn đáng xem.

Jmh là một khai thác Java để xây dựng, chạy và phân tích các điểm chuẩn nano / micro / macro được viết bằng Java và các ngôn ngữ khác nhắm mục tiêu JVM.

Những mẩu thông tin rất thú vị được chôn trong các bài kiểm tra mẫu .

Xem thêm:


1
Xem thêm bài đăng trên blog này: psy-lob-saw.blogspot.com/2013/04/ Khăn để biết chi tiết về việc bắt đầu với JMH.
Nitsan Wakart

FYI, JEP 230: Microbenchmark Suite là một đề xuất OpenJDK dựa trên dự án Khai thác Microbenchmark Java (JMH) này. Không thực hiện cắt giảm cho Java 9 nhưng có thể được thêm vào sau.
Basil Bourque

23

Điểm chuẩn nên đo thời gian / lần lặp hoặc lần lặp / lần, và tại sao?

Nó phụ thuộc vào những gì bạn đang cố gắng để kiểm tra.

Nếu bạn quan tâm đến độ trễ , hãy sử dụng thời gian / lần lặp và nếu bạn quan tâm đến thông lượng , hãy sử dụng lần lặp / thời gian.


16

Nếu bạn đang cố gắng so sánh hai thuật toán, hãy thực hiện ít nhất hai điểm chuẩn cho mỗi thuật toán, xen kẽ thứ tự. I E:

for(i=1..n)
  alg1();
for(i=1..n)
  alg2();
for(i=1..n)
  alg2();
for(i=1..n)
  alg1();

Tôi đã tìm thấy một số khác biệt đáng chú ý (đôi khi 5-10%) trong thời gian chạy của cùng một thuật toán trong các lần chuyển khác nhau ..

Ngoài ra, hãy chắc chắn rằng n rất lớn, sao cho thời gian chạy của mỗi vòng lặp ít nhất là 10 giây hoặc lâu hơn. Càng lặp nhiều, số liệu có ý nghĩa trong thời gian chuẩn của bạn càng cao và dữ liệu đó càng đáng tin cậy.


5
Tự nhiên thay đổi thứ tự ảnh hưởng đến thời gian chạy. Tối ưu hóa JVM và hiệu ứng bộ nhớ đệm sẽ hoạt động ở đây. Tốt hơn là 'làm nóng' tối ưu hóa JVM, mak nhiều lần chạy và điểm chuẩn mỗi thử nghiệm trong một JVM khác nhau.
Mnementh

15

Hãy chắc chắn rằng bạn bằng cách nào đó sử dụng kết quả được tính toán trong mã chuẩn. Nếu không, mã của bạn có thể được tối ưu hóa đi.


13

Có nhiều cạm bẫy có thể xảy ra khi viết các điểm chuẩn vi mô trong Java.

Thứ nhất: Bạn phải tính toán với tất cả các loại sự kiện mất ít nhiều thời gian ngẫu nhiên: Bộ sưu tập rác, hiệu ứng bộ đệm (của hệ điều hành cho tệp và CPU cho bộ nhớ), IO, v.v.

Thứ hai: Bạn không thể tin tưởng vào độ chính xác của thời gian đo trong khoảng thời gian rất ngắn.

Thứ ba: JVM tối ưu hóa mã của bạn trong khi thực thi. Vì vậy, các lần chạy khác nhau trong cùng một cá thể JVM sẽ trở nên nhanh hơn và nhanh hơn.

Đề xuất của tôi: Làm cho điểm chuẩn của bạn chạy vài giây, đáng tin cậy hơn thời gian chạy trong một phần nghìn giây. Làm nóng JVM (có nghĩa là chạy điểm chuẩn ít nhất một lần mà không cần đo, rằng JVM có thể chạy tối ưu hóa). Và chạy điểm chuẩn của bạn nhiều lần (có thể 5 lần) và lấy giá trị trung bình. Chạy mọi điểm chuẩn vi mô trong một cá thể JVM mới (gọi cho mọi Java điểm chuẩn mới) nếu không các hiệu ứng tối ưu hóa của JVM có thể ảnh hưởng đến các thử nghiệm chạy sau này. Đừng thực thi những thứ không được thực hiện trong giai đoạn khởi động (vì điều này có thể kích hoạt tải lớp và biên dịch lại lớp).


8

Cũng cần lưu ý rằng cũng có thể quan trọng để phân tích kết quả của điểm chuẩn vi mô khi so sánh các triển khai khác nhau. Do đó, một bài kiểm tra quan trọng nên được thực hiện.

Điều này là do việc triển khai Acó thể nhanh hơn trong hầu hết các lần chạy chuẩn so với thực hiện B. Nhưng Acũng có thể có mức chênh lệch cao hơn, do đó, lợi ích hiệu suất đo được Asẽ không có ý nghĩa gì khi so sánh với B.

Vì vậy, điều quan trọng là viết và chạy một điểm chuẩn vi mô một cách chính xác, nhưng cũng phải phân tích nó một cách chính xác.


8

Để thêm vào những lời khuyên tuyệt vời khác, tôi cũng lưu ý những điều sau:

Đối với một số CPU (ví dụ: phạm vi Intel Core i5 với TurboBoost), nhiệt độ (và số lõi hiện đang được sử dụng, cũng như phần trăm sử dụng mạnh hơn) ảnh hưởng đến tốc độ xung nhịp. Vì CPU được chạy tự động, điều này có thể ảnh hưởng đến kết quả của bạn. Ví dụ: nếu bạn có một ứng dụng đơn luồng, tốc độ xung nhịp tối đa (với TurboBoost) cao hơn so với một ứng dụng sử dụng tất cả các lõi. Do đó, điều này có thể can thiệp vào việc so sánh hiệu suất đơn và đa luồng trên một số hệ thống. Hãy nhớ rằng nhiệt độ và biến động cũng ảnh hưởng đến thời gian duy trì tần số Turbo.

Có lẽ một khía cạnh quan trọng hơn về cơ bản mà bạn có quyền kiểm soát trực tiếp: hãy chắc chắn rằng bạn đang đo đúng thứ! Ví dụ: nếu bạn đang sử dụng System.nanoTime()để định chuẩn một bit mã cụ thể, hãy đặt các cuộc gọi đến bài tập ở những nơi hợp lý để tránh đo những thứ bạn không quan tâm. Ví dụ: không làm:

long startTime = System.nanoTime();
//code here...
System.out.println("Code took "+(System.nanoTime()-startTime)+"nano seconds");

Vấn đề là bạn không nhận được ngay thời gian kết thúc khi mã đã kết thúc. Thay vào đó, hãy thử như sau:

final long endTime, startTime = System.nanoTime();
//code here...
endTime = System.nanoTime();
System.out.println("Code took "+(endTime-startTime)+"nano seconds");

Vâng, điều quan trọng là không làm những công việc không liên quan trong khu vực thời gian, nhưng ví dụ đầu tiên của bạn vẫn ổn. Chỉ có một cuộc gọi đến println, không phải là một dòng tiêu đề riêng biệt hoặc một cái gì đó và System.nanoTime()phải được đánh giá là bước đầu tiên trong việc xây dựng chuỗi arg cho cuộc gọi đó. Trình biên dịch không thể làm gì với cái đầu tiên mà chúng không thể làm với cái thứ hai và thậm chí không ai khuyến khích chúng làm thêm trước khi ghi lại thời gian dừng.
Peter Cordes

7

http://opt.sourceforge.net/ Java Micro Benchmark - các nhiệm vụ kiểm soát cần thiết để xác định các đặc tính hiệu suất so sánh của hệ thống máy tính trên các nền tảng khác nhau. Có thể được sử dụng để hướng dẫn các quyết định tối ưu hóa và so sánh các triển khai Java khác nhau.


2
Có vẻ như chỉ điểm chuẩn phần cứng JVM +, không phải là một đoạn mã Java tùy ý.
Stefan L
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.