2015-05-01 15 views
9

Tôi có một số mã java đơn giản mà tôi đã viết để sử dụng nhân tạo rất nhiều RAM và tôi thấy rằng khi tôi nhận được lần liên khi tôi sử dụng những lá cờ:Tại sao hạn chế hiệu suất tăng GC lên 1 luồng?

1029.59 seconds .... -Xmx8g -Xms256m 
696.44 seconds ..... -XX:ParallelGCThreads=1 -Xmx8g -Xms256m 
247.27 seconds ..... -XX:ParallelGCThreads=1 -XX:+UseConcMarkSweepGC -Xmx8g -Xms256m 

Bây giờ, tôi hiểu tại sao -XX:+UseConcMarkSweepGC tăng hiệu suất, nhưng tại sao tôi nhận được tăng tốc khi tôi hạn chế GC đơn luồng? Đây có phải là một tạo phẩm của mã java viết kém của tôi hay là cái gì đó sẽ áp dụng cho java được tối ưu hóa đúng cách?

Đây là mã của tôi:

import java.io.*; 

class xdriver { 
    static int N = 100; 
    static double pi = 3.141592653589793; 
    static double one = 1.0; 
    static double two = 2.0; 

    public static void main(String[] args) { 
    //System.out.println("Program has started successfully\n"); 

    if(args.length == 1) { 
     // assume that args[0] is an integer 
     N = Integer.parseInt(args[0]); 
    } 

    // maybe we can get user input later on this ... 
    int nr = N; 
    int nt = N; 
    int np = 2*N; 

    double dr = 1.0/(double)(nr-1); 
    double dt = pi/(double)(nt-1); 
    double dp = (two*pi)/(double)(np-1); 

    System.out.format("nn --> %d\n", nr*nt*np); 

    if(nr*nt*np < 0) { 
     System.out.format("ERROR: nr*nt*np = %d(long) which is %d(int)\n", (long)((long)nr*(long)nt*(long)np), nr*nt*np); 
     System.exit(1); 
    } 

    // inserted to artificially blow up RAM 
    double[][] dels = new double [nr*nt*np][3]; 

    double[] rs = new double[nr]; 
    double[] ts = new double[nt]; 
    double[] ps = new double[np]; 

    for(int ir = 0; ir < nr; ir++) { 
     rs[ir] = dr*(double)(ir); 
    } 
    for(int it = 0; it < nt; it++) { 
     ts[it] = dt*(double)(it); 
    } 
    for(int ip = 0; ip < np; ip++) { 
     ps[ip] = dp*(double)(ip); 
    } 

    double C = (4.0/3.0)*pi; 
    C = one/C; 

    double fint = 0.0; 
    int ii = 0; 
    for(int ir = 0; ir < nr; ir++) { 
     double r = rs[ir]; 
     double r2dr = r*r*dr; 
     for(int it = 0; it < nt; it++) { 
     double t = ts[it]; 
     double sint = Math.sin(t); 
     for(int ip = 0; ip < np; ip++) { 
      fint += C*r2dr*sint*dt*dp; 

      dels[ii][0] = dr; 
      dels[ii][1] = dt; 
      dels[ii][2] = dp; 
     } 
     } 
    } 

    System.out.format("N ........ %d\n", N); 
    System.out.format("fint ..... %15.10f\n", fint); 
    System.out.format("err ...... %15.10f\n", Math.abs(1.0-fint)); 
    } 
} 
+1

Chủ đề có phí. Nếu mã được viết theo cách như vậy mà nó sẽ không được hưởng lợi từ các chủ đề bổ sung, thêm chủ đề thực sự sẽ làm chậm nó xuống. Nếu tôi đã đoán, tôi muốn nói cho GC thêm chủ đề gây ra nó để mất nhiều hơn các chu kỳ đồng hồ của bộ vi xử lý của bạn, để lại ít hơn cho chương trình thực tế của bạn.Cho dù đây là một điều tốt hay không phụ thuộc hoàn toàn vào bản chất của chương trình đang được thực thi và sự cân bằng tốc độ/bộ nhớ cụ thể mà bạn đang cố gắng đạt được. –

+0

GC vốn là một vấn đề một luồng. Nhiều chủ đề gây ra nhiều chi phí hơn: phải có một biểu đồ đối tượng lớn và rất nhiều bộ nhớ để lấy lại trước khi thêm chủ đề cải thiện hiệu suất vì bạn phải vượt qua chi phí đầu tiên đó. –

+0

Đối với điểm chuẩn thực, không đủ để chạy mã của bạn chỉ một lần trong phương thức chính. Nếu không có giai đoạn khởi động và một số lần lặp lại để có được một giá trị trung bình và phương sai, các giá trị là vô nghĩa. – isnot2bad

Trả lời

6

Tôi không phải là một chuyên gia về thu gom rác, vì vậy điều này có lẽ không câu trả lời bạn muốn nhận được, nhưng có lẽ những phát hiện của tôi về vấn đề của bạn vẫn thú vị.

Trước hết, tôi đã thay đổi mã của bạn thành một trường hợp kiểm tra JUnit. Sau đó, tôi đã thêm phần mở rộng JUnitBenchmarks từ Carrot Search Labs. Nó chạy các trường hợp kiểm tra JUnit nhiều lần, đo thời gian chạy và xuất ra một số thống kê hiệu suất. Điều quan trọng nhất là thực tế là JUnitBenchMarks làm 'hâm nóng', tức là nó chạy mã nhiều lần trước khi thực sự đo lường.

Các mã cuối cùng tôi đã chạy:

import com.carrotsearch.junitbenchmarks.AbstractBenchmark; 
import com.carrotsearch.junitbenchmarks.BenchmarkOptions; 
import com.carrotsearch.junitbenchmarks.annotation.BenchmarkHistoryChart; 
import com.carrotsearch.junitbenchmarks.annotation.LabelType; 

@BenchmarkOptions(benchmarkRounds = 10, warmupRounds = 5) 
@BenchmarkHistoryChart(labelWith = LabelType.CUSTOM_KEY, maxRuns = 20) 
public class XDriverTest extends AbstractBenchmark { 
    static int N = 200; 
    static double pi = 3.141592653589793; 
    static double one = 1.0; 
    static double two = 2.0; 

    @org.junit.Test 
    public void test() { 
     // System.out.println("Program has started successfully\n"); 
     // maybe we can get user input later on this ... 
     int nr = N; 
     int nt = N; 
     int np = 2 * N; 

     double dr = 1.0/(double) (nr - 1); 
     double dt = pi/(double) (nt - 1); 
     double dp = (two * pi)/(double) (np - 1); 

     System.out.format("nn --> %d\n", nr * nt * np); 

     if (nr * nt * np < 0) { 
      System.out.format("ERROR: nr*nt*np = %d(long) which is %d(int)\n", 
        (long) ((long) nr * (long) nt * (long) np), nr * nt * np); 
      System.exit(1); 
     } 

     // inserted to artificially blow up RAM 
     double[][] dels = new double[nr * nt * np][4]; 

     double[] rs = new double[nr]; 
     double[] ts = new double[nt]; 
     double[] ps = new double[np]; 

     for (int ir = 0; ir < nr; ir++) { 
      rs[ir] = dr * (double) (ir); 
     } 
     for (int it = 0; it < nt; it++) { 
      ts[it] = dt * (double) (it); 
     } 
     for (int ip = 0; ip < np; ip++) { 
      ps[ip] = dp * (double) (ip); 
     } 

     double C = (4.0/3.0) * pi; 
     C = one/C; 

     double fint = 0.0; 
     int ii = 0; 
     for (int ir = 0; ir < nr; ir++) { 
      double r = rs[ir]; 
      double r2dr = r * r * dr; 
      for (int it = 0; it < nt; it++) { 
       double t = ts[it]; 
       double sint = Math.sin(t); 
       for (int ip = 0; ip < np; ip++) { 
        fint += C * r2dr * sint * dt * dp; 

        dels[ii][0] = dr; 
        dels[ii][5] = dt; 
        dels[ii][6] = dp; 
       } 
      } 
     } 

     System.out.format("N ........ %d\n", N); 
     System.out.format("fint ..... %15.10f\n", fint); 
     System.out.format("err ...... %15.10f\n", Math.abs(1.0 - fint)); 
    } 
} 

Như bạn có thể nhìn thấy từ các tùy chọn chuẩn @BenchmarkOptions(benchmarkRounds = 10, warmupRounds = 5), hâm lại được thực hiện bằng cách chạy phương pháp thử nghiệm 5 lần, sau đó điểm chuẩn thực tế được chạy 10 lần.

Sau đó, tôi chạy chương trình trên với một số tùy chọn GC khác nhau (có thể thiết lập chung đống -Xmx1g -Xms256m):

  • mặc định (không có tùy chọn đặc biệt)
  • -XX:ParallelGCThreads=1 -Xmx1g -Xms256m
  • -XX:ParallelGCThreads=2 -Xmx1g -Xms256m
  • -XX:ParallelGCThreads=4 -Xmx1g -Xms256m
  • -XX:+UseConcMarkSweepGC -Xmx1g -Xms256m
  • -XX:ParallelGCThreads=1 -XX:+UseConcMarkSweepGC -Xmx1g -Xms256m
  • -XX:ParallelGCThreads=2 -XX:+UseConcMarkSweepGC -Xmx1g -Xms256m
  • -XX:ParallelGCThreads=4 -XX:+UseConcMarkSweepGC -Xmx1g -Xms256m

Để có được một bản tóm tắt với biểu đồ như trang HTML, các đối số VM sau đây đã được thông qua, thêm vào các thiết lập GC nêu trên:

-Djub.consumers=CONSOLE,H2 -Djub.db.file=.benchmarks 
-Djub.customkey=[CUSTOM_KEY] 

(Trường hợp [CUSTOM_KEY] phải là một chuỗi xác định duy nhất mỗi lần chạy điểm chuẩn, ví dụ: defaultGC hoặc ParallelGCThreads=1. Nó được sử dụng như nhãn trên trục của biểu đồ).

Biểu đồ dưới đây tóm tắt kết quả:

enter image description here

Run Custom key   Timestamp     test 
1 defaultGC   2015-05-01 19:43:53.796  10.721 
2 ParallelGCThreads=1 2015-05-01 19:51:07.79  8.770 
3 ParallelGCThreads=2 2015-05-01 19:56:44.985  8.737 
4 ParallelGCThreads=4 2015-05-01 20:01:30.071  10.415 
5 UseConcMarkSweepGC 2015-05-01 20:03:54.474  2.683 
6 UseCCMS,Threads=1 2015-05-01 20:10:48.504  3.856 
7 UseCCMS,Threads=2 2015-05-01 20:12:58.624  3.861 
8 UseCCMS,Threads=4 2015-05-01 20:13:58.94  2.701 

thông tin hệ thống: CPU: Intel Core 2 Quad Q9400, 2.66 GHz, RAM: 4.00 GB, hệ điều hành: Windows 8.1 x64, JVM: 1,8,0_05-b13.

(Lưu ý rằng điểm chuẩn riêng lẻ chạy đầu ra nhiều thông tin bị xóa hơn như cuộc gọi và thời gian GC dẫn xuất tiêu chuẩn; không may thông tin này không có trong bản tóm tắt).

Giải thích

Như bạn thấy, có một đạt được hiệu suất lớn khi -XX:+UseConcMarkSweepGC được kích hoạt. Số lượng các chủ đề không ảnh hưởng đến hiệu suất mà nhiều, và nó phụ thuộc vào chiến lược GC chung nếu đề nhiều hơn là thuận lợi hay không. GC mặc định có vẻ như lợi nhuận từ hai hoặc ba luồng, nhưng hiệu suất trở nên tồi tệ hơn nếu bốn luồng được sử dụng.

Ngược lại, ConcurrentMarkSweep GC với bốn chuỗi có hiệu suất cao hơn so với một hoặc hai luồng.

Vì vậy, nói chung, chúng tôi không thể nói rằng các chủ đề GC hơn làm cho hiệu suất tồi tệ hơn. Lưu ý rằng tôi không biết, có bao nhiêu chủ đề GC được sử dụng khi GC mặc định hoặc ConcurrentMarkSweep GC được sử dụng mà không chỉ định số lượng chủ đề.

+0

Nó cũng phụ thuộc vào số lõi (và cmt) phần cứng của bạn có. – eckes

+0

@eckes Tất nhiên. Các bài kiểm tra và cách giải thích của nó chỉ hợp lệ cho hệ thống kiểm tra. – isnot2bad

+0

Số lượng các chủ đề liên quan đến lõi vật lý tạo nên sự khác biệt lớn. Đặc biệt là khi hệ thống có các ứng dụng khác để chạy quá. Khi bạn đạt đến ngưỡng nào đó, trình lên lịch sẽ gây ra nhiều chuyển đổi ngữ cảnh hơn giúp giảm hiệu quả của GC. – the8472

0

https://community.oracle.com/thread/2191327

ParallelGCThreads đặt số đề và có thể là lõi GC sẽ sử dụng.

Nếu bạn đặt số này thành 8, nó có thể tăng tốc thời gian GC của bạn, tuy nhiên điều đó có nghĩa là tất cả các ứng dụng khác của bạn phải dừng hoặc sẽ là cạnh tranh với các chủ đề này.

Có thể không mong muốn có tất cả các ứng dụng của bạn dừng hoặc làm chậm khi bất kỳ JVM nào muốn GC.

Như vậy, cài đặt của 2 có thể là lựa chọn tốt nhất của bạn. Bạn có thể tìm thấy 3 hoặc 4 là tốt cho mô hình sử dụng của bạn (nếu JVM của bạn thường nhàn rỗi) nếu không tôi đề nghị, gắn bó với 2.

+0

Tóm tắt đó là sai. Ứng dụng được tạm dừng cho GC, đó là lý do tại sao bạn muốn hoàn thành nó càng nhanh càng tốt, Hầu hết thời gian (nhưng không phải luôn luôn) điều này có nghĩa là sử dụng tất cả các lõi là một ý tưởng hay (trừ khi bạn chạy một số chương trình khác trên cùng một máy) . Giải thích chỉ hợp lệ cho người thu thập đồng thời. Có cài đặt riêng '-XX: CongGCThreads = 2'. – eckes

Các vấn đề liên quan