2012-03-16 36 views
11

Tôi đang thử nghiệm một API, được viết bằng Java, dự kiến ​​sẽ giảm thiểu độ trễ trong xử lý các tin nhắn nhận được qua mạng. Để đạt được những mục tiêu này, tôi đang chơi đùa với những người thu gom rác khác nhau có sẵn.Bộ thu gom rác nối tiếp của Java hoạt động tốt hơn nhiều so với các bộ thu gom rác khác?

Tôi đang cố gắng bốn kỹ thuật khác nhau, trong đó sử dụng các cờ sau để kiểm soát thu gom rác thải:

1) Nối tiếp: -XX: + UseSerialGC

2) song song: -XX: + UseParallelOldGC

3) đồng thời: -XX: + UseConcMarkSweepGC

4) đồng thời/gia tăng: -XX: + UseConcMarkSweepGC -XX: + CMSIncrementalMode -XX: + CMSIncrementalPacing

Tôi chạy từng kỹ thuật trong vòng năm giờ. Tôi định kỳ sử dụng danh sách GarbageCollectorMXBean được cung cấp bởi ManagementFactory.getGarbageCollectorMXBeans() để lấy tổng thời gian dành cho việc thu gom rác thải.

Kết quả của tôi? Lưu ý rằng "độ trễ" ở đây là "Lượng thời gian mà ứng dụng của tôi + API đã dành để xử lý từng thư bị ngắt khỏi mạng".

Nối tiếp: 789 sự kiện GC tổng cộng 1309 ms; độ trễ trung bình 47.45, độ trễ trung bình 8.704, thời gian chờ tối đa 1197 chúng tôi

Song song: 1715 Tổng số sự kiện GC tổng cộng 122518 ms; độ trễ trung bình 450,8, độ trễ trung bình 8.448, độ trễ tối đa 8292,

Đồng thời: 4629 tổng số sự kiện GC 116229 ms; độ trễ trung bình 707.2, độ trễ trung bình 9.216, độ trễ tối đa 9151,

Gia tăng: 5066 Tổng số sự kiện GC 200213 ms; độ trễ trung bình 515,9 chúng tôi, độ trễ trung bình 9.472 chúng tôi, độ trễ tối đa 14209 chúng tôi

Tôi thấy những kết quả này không thể xảy ra mà chúng biên giới vô lý. Có ai biết tại sao tôi có thể có những loại kết quả này không?

Ồ, và để lưu nội dung, tôi đang sử dụng Máy chủ Java 64-Bit Java HotSpot (TM).

+0

Bạn có giả định rằng việc thực hiện hai điều song song có nhất thiết phải nhanh hơn so với thực hiện một điều sau cái khác không? – aioobe

+0

Tôi hy vọng độ trễ tối đa sẽ tăng lên mặc dù – jcoder

+0

Vì vậy, có bao nhiêu thư đã thực sự được xử lý trong 5 giờ đó trong các tình huống khác nhau của bạn? Bạn có đang chạy một luồng đơn hay đa luồng không? – pap

Trả lời

18

Tôi đang làm việc trên một ứng dụng Java mà dự kiến ​​sẽ phát huy tối đa thông lượng và giảm thiểu độ trễ

Hai vấn đề với điều đó:

  • Đó là những thường mục tiêu mâu thuẫn nhau, vì vậy bạn cần phải quyết định làm thế nào quan trọng mỗi là chống lại khác (bạn sẽ hy sinh 10% độ trễ để có được 20% thông lượng đạt được hoặc ngược lại? Bạn đang nhắm đến một số cụ thể tar thời gian chờ có được, vượt ra ngoài nó không quan trọng cho dù đó là bất kỳ nhanh hơn? Những điều như thế.)
  • của bạn đã không đưa ra bất kỳ kết quả xung quanh hoặc các

Tất cả các bạn đã thể hiện là bao nhiêu thời gian cho việc thu gom rác thải.Nếu bạn thực sự là đạt được nhiều thông lượng hơn, có thể bạn sẽ là mong đợi để xem thêm thời gian dành cho bộ thu gom rác. Hoặc nói cách khác, tôi có thể làm cho một sự thay đổi trong các mã để giảm thiểu các giá trị bạn đang báo cáo thực sự dễ dàng:

// Avoid generating any garbage 
Thread.sleep(10000000); 

Bạn cần phải làm việc ra những gì thực sự quan trọng với bạn. Đo lường mọi thứ quan trọng, sau đó tìm ra nơi mà sự cân bằng nằm. Vì vậy, đầu tiên điều cần làm là chạy lại các thử nghiệm của bạn và đo độ trễ và thông lượng. Bạn có thể cũng quan tâm đến việc sử dụng CPU tổng cộng (không giống như CPU ​​trong GC) nhưng trong khi bạn không đo lường mục tiêu chính của mình, kết quả của bạn không mang lại cho bạn thông tin đặc biệt hữu ích.

+1

+1 Câu trả lời hay. Tôi ước tôi có thể cung cấp thêm một +1 cho giải pháp của bạn để tránh tạo rác :-) – aioobe

+0

Ba điều. Đầu tiên, tôi hiểu rằng các mục tiêu thường mâu thuẫn. Tôi cho rằng "độ trễ" sẽ là mục tiêu chính của tôi. Thứ hai, tôi không chỉ lặp lại thông qua một tập tin hoặc một cái gì đó. Các ứng dụng đang xử lý lưu lượng mạng (cùng một tập hợp lưu lượng cho mỗi lần chạy ứng dụng), do đó lượng dữ liệu được xử lý giống nhau trên mọi lần chạy. Thứ ba, tôi sẽ đăng kết quả độ trễ của mình trong bài đăng chính trong giây lát. – user1274193

+0

haha. _Không tạo ra bất kỳ garbage_ .. chỉ tuyệt vời! +1 – kromit

0

Bạn không thể nói một GC là tốt hơn cái kia. nó phụ thuộc vào yêu cầu của bạn và ứng dụng của bạn.

nhưng nếu bạn muốn tối đa hóa thông lượng và giảm thiểu độ trễ: GC là kẻ thù của bạn! bạn không nên gọi GC ở tất cả và cũng cố gắng ngăn chặn JVM gọi GC.

đi kèm với các nhóm đối tượng nối tiếp và sử dụng.

4

Tôi không thấy điều này đáng ngạc nhiên chút nào.

Vấn đề với bộ sưu tập rác nối tiếp là trong khi nó đang chạy, không có gì khác có thể chạy ở tất cả (aka "dừng lại trên thế giới"). Điều đó có một điểm tốt mặc dù: nó giữ số lượng công việc dành cho bộ sưu tập rác thải chỉ khoảng tối thiểu của nó.

Hầu như bất kỳ loại bộ sưu tập rác song song hoặc đồng thời nào phải thực hiện một số lượng công việc phụ để đảm bảo rằng tất cả các sửa đổi cho đống xuất hiện nguyên tử cho phần còn lại của mã. Thay vì chỉ dừng lại mọi thứ trong một thời gian, nó phải dừng lại chỉ những điều đó phụ thuộc vào một thay đổi cụ thể và sau đó chỉ đủ dài để thực hiện thay đổi cụ thể đó. Sau đó nó cho phép mã đó bắt đầu chạy lại, đến điểm tiếp theo mà nó sẽ thực hiện thay đổi, dừng các phần mã khác phụ thuộc vào nó, v.v.

Điểm khác (mặc dù trong trường hợp này, có thể là một điều khá nhỏ) là khi bạn xử lý nhiều dữ liệu hơn, bạn thường muốn tạo thêm rác và do đó dành nhiều thời gian hơn để thu gom rác. Vì bộ thu nối tiếp dừng tất cả các quá trình xử lý khác trong khi nó thực hiện công việc của mình, điều đó không chỉ làm cho việc thu gom rác nhanh mà còn ngăn không cho thêm bất kỳ rác nào được tạo ra trong thời gian đó.

Bây giờ, tại sao tôi nói đó có thể là một cộng tác viên nhỏ trong trường hợp này? Đó là khá đơn giản: các bộ thu nối tiếp chỉ sử dụng hết một chút trong một giây trong năm giờ. Mặc dù không có gì khác được thực hiện trong khoảng thời gian đó ~ 1,3 giây, nhưng đó chỉ là một tỷ lệ nhỏ trong năm giờ mà nó có thể không tạo ra bất kỳ sự khác biệt thực sự nào (nếu có) cho thông lượng tổng thể của bạn.

Tóm tắt: vấn đề với bộ sưu tập rác nối tiếp không phải là nó sử dụng tổng thời gian quá mức - nó có thể rất bất tiện nếu nó dừng thế giới ngay khi bạn cần phản ứng nhanh. Đồng thời, tôi nên thêm rằng miễn là chu kỳ thu thập của bạn ngắn, điều này vẫn có thể được khá tối thiểu. Về lý thuyết, các dạng khác của GC chủ yếu là hạn chế trường hợp xấu nhất của bạn, nhưng trên thực tế (ví dụ, bằng cách giới hạn kích thước heap), bạn thường có thể giới hạn thời gian chờ tối đa bằng bộ thu nối tiếp.

2

Có một cuộc trò chuyện tuyệt vời của một kỹ sư Twitter tại 2012 QCon Conference về chủ đề này - bạn có thể xem nó here.

Nó đã thảo luận về các "thế hệ" khác nhau trong bộ nhớ Hotspot JVM và bộ sưu tập rác (Eden, Survivor, Old). Đặc biệt lưu ý rằng "Đồng thời" trong ConcurrentMarkAndSweep chỉ áp dụng cho thế hệ Cũ, tức là các đối tượng treo trong một thời gian.

Các đối tượng sống ngắn là GCd từ thế hệ "Eden" - đây là giá rẻ, nhưng là sự kiện GC "stop-the-world" bất kể bạn đã chọn thuật toán GC nào!

Lời khuyên là điều chỉnh thế hệ trẻ trước, ví dụ: phân bổ rất nhiều Eden mới để có nhiều cơ hội hơn cho các đối tượng chết trẻ và được khai hoang với giá rẻ. Sử dụng + PrintGCDetails, + PrintHeapAtGC, + PrintTenuringDistribution ... Nếu bạn nhận được hơn 100% người sống sót sau đó không có chỗ, vì vậy các đối tượng nhanh chóng được thăng hạng thành Old - đây là Xấu.

Khi điều chỉnh cho generatiohn cũ, nếu độ trễ là ưu tiên hàng đầu, bạn nên thử ParallelOld với tự động điều chỉnh đầu tiên (+ AdaptiveSizePolicy vv), sau đó thử CMS, sau đó có thể là G1GC mới.

+0

Các trang trình bày cũng có sẵn tại http://www.slideshare.net/aszegedi/everything-i-ever-learned-about-jvm-performance-tuning-twitter, nếu liên kết ở trên không hiệu quả với bạn. – ryenus

+0

Cảm ơn - Tôi cũng đã cập nhật liên kết trong câu trả lời của tôi để trỏ đến vị trí mới của video. – DNA

0

Với bộ sưu tập nối tiếp, chỉ một điều xảy ra cùng một lúc. Ví dụ, ngay cả khi nhiều CPU là có sẵn, chỉ có một CPU được sử dụng để thực hiện bộ sưu tập. Khi sử dụng bộ sưu tập song song, nhiệm vụ của bộ sưu tập rác được chia thành các phần và các bộ phận con được thực thi đồng thời, trên các CPU khác nhau. Các hoạt động đồng thời cho phép thu thập được thực hiện nhanh hơn, với chi phí một số phức tạp bổ sung và phân mảnh tiềm năng.

Trong khi GC nối tiếp chỉ sử dụng một luồng để xử lý GC, GC song song sử dụng một số luồng để xử lý GC và do đó, nhanh hơn. GC này rất hữu ích khi có đủ bộ nhớ và một số lượng lớn các lõi. Nó còn được gọi là "GC thông lượng" "."