2012-01-27 26 views
10

Tôi đang thực hiện một số thử nghiệm với escape analysis trong Java 7 để hiểu rõ hơn đối tượng nào đủ điều kiện để phân bổ stack.Đủ điều kiện để phân tích thoát/phân bổ ngăn xếp với Java 7

Đây là mã tôi đã viết để kiểm tra phân bổ stack:

import java.util.ArrayList; 
import java.util.Iterator; 


public class EscapeAnalysis { 

    private static final long TIME_TO_TEST = 10L * 1000L; // 10s 

    static class Timestamp { 
     private long millis; 
     public Timestamp(long millis) { 
      this.millis = millis; 
     } 
     public long getTime() { 
      return millis; 
     } 
     public void setTime(long time) { 
      millis = time; 
     } 
    } 

    public static void main(String[] args) { 
     long r = 0; 
     System.out.println("test1"); 
     r += test1(); 
     System.out.println("test2"); 
     r += test2(); 
     System.out.println("test3"); 
     r += test3(); 
     System.out.println("test4"); 
     r += test4(); 
     System.out.println("test5"); 
     r += test5(); 
     System.out.println("test6"); 
     r += test6(); 
     System.out.println(r); 
    } 

    public static long test1() { 
     long r = 0; 
     long start = System.currentTimeMillis(); 
     while (System.currentTimeMillis() - start < TIME_TO_TEST) { 
      r += new Timestamp(System.currentTimeMillis()).getTime(); 
     } 
     return r; 
    } 

    public static long test2() { 
     ArrayList<Integer> l = new ArrayList<Integer>(1000); 
     for (int i = 0; i < 1000; ++i) { 
      l.add(i); 
     } 
     long r = 0; 
     long start = System.currentTimeMillis(); 
     while (System.currentTimeMillis() - start < TIME_TO_TEST) { 
      for (Iterator<Integer> it = l.iterator(); it.hasNext();) { 
       r += it.next().longValue(); 
      } 
     } 
     return r; 
    } 

    public static long test3() { 
     long r = 0; 
     long start = System.currentTimeMillis(); 
     while (System.currentTimeMillis() - start < TIME_TO_TEST) { 
      Timestamp ts = new Timestamp(System.currentTimeMillis()); 
      ts.setTime(42); 
      r += ts.getTime(); 
     } 
     return r; 
    } 

    public static long test4() { 
     ArrayList<Integer> l = new ArrayList<Integer>(1000); 
     for (int i = 0; i < 1000; ++i) { 
      l.add(i); 
     } 
     long r = 0; 
     long start = System.currentTimeMillis(); 
     while (System.currentTimeMillis() - start < TIME_TO_TEST) { 
      Iterator<Integer> it = l.iterator(); 
      r += it.next().longValue(); 
      r += it.next().longValue(); 
      r += it.next().longValue(); 
      r += it.next().longValue(); 
     } 
     return r; 
    } 

    public static long test5() { 
     ArrayList<Integer> l = new ArrayList<Integer>(1000); 
     for (int i = 0; i < 1000; ++i) { 
      l.add(i); 
     } 
     long r = 0; 
     long start = System.currentTimeMillis(); 
     while (System.currentTimeMillis() - start < TIME_TO_TEST) { 
      Iterator<Integer> it = l.iterator(); 
      for (int i = 0; i < l.size(); ++i) { 
       r += it.next().longValue(); 
      } 
     } 
     return r; 
    } 

    public static long test6() { 
     long r = 0; 
     long start = System.currentTimeMillis(); 
     while (System.currentTimeMillis() - start < TIME_TO_TEST) { 
      for (Timestamp ts = new Timestamp(System.currentTimeMillis()); 
        ts.getTime() > 0; 
        ts.setTime(ts.getTime() + System.currentTimeMillis())) { 
       r += ts.getTime(); 
      } 
     } 
     return r; 
    } 

} 

Và đây là những gì nó ra với Java 7 trên Linux

java -server -version             
java version "1.7.0_02" 
Java(TM) SE Runtime Environment (build 1.7.0_02-b13) 
Java HotSpot(TM) 64-Bit Server VM (build 22.0-b10, mixed mode) 

java -server -verbose:gc -XX:CompileThreshold=1 -cp bin EscapeAnalysis 
test1 
test2 
[GC 15616K->352K(59776K), 0,0014270 secs] 
[GC 15968K->288K(59776K), 0,0011790 secs] 
[GC 15904K->288K(59776K), 0,0018170 secs] 
[GC 15904K->288K(59776K), 0,0011100 secs] 
[GC 15904K->288K(57152K), 0,0019790 secs] 
[GC 15520K->320K(56896K), 0,0011670 secs] 
[GC 15232K->284K(56256K), 0,0011440 secs] 
test3 
test4 
test5 
[GC 14876K->348K(55936K), 0,0005340 secs] 
[GC 14620K->348K(56000K), 0,0004560 secs] 
[GC 14300K->316K(55296K), 0,0004680 secs] 
[GC 13948K->316K(55488K), 0,0003590 secs] 
[GC 13692K->316K(54784K), 0,0004580 secs] 
[GC 13436K->316K(54976K), 0,0005430 secs] 
[GC 13180K->316K(54272K), 0,0004500 secs] 
[GC 12924K->316K(54464K), 0,0005090 secs] 
[GC 12668K->316K(53760K), 0,0004490 secs] 
[GC 12412K->316K(53888K), 0,0004350 secs] 
[GC 12156K->316K(53312K), 0,0005060 secs] 
test6 
6737499643744733086 

Tôi đang sử dụng các bản ghi GC để biết liệu đối tượng là được phân bổ trên ngăn xếp (ý tưởng từ Escape analysis in Java) có thể không đáng tin cậy 100% nhưng có vẻ là gợi ý tốt.

Baed trên đầu ra, phân bổ ngăn xếp hoạt động cho test1, test3, test4 và test6 và không hoạt động cho test2 và test5. Tôi không hiểu tại sao điều này không làm việc với một iterator trong cho vòng mặc dù nó hoạt động

  • với một iterator bên ngoài một cho vòng lặp (xem test4),
  • với một đối tượng bên trong một cho- vòng lặp (xem test6).

Tôi đã đọc mã cho số ArrayList iterator và tôi không hiểu tại sao nó không đủ điều kiện phân bổ ngăn xếp trong thử nghiệm 2 và 5 vì nó không thoát khỏi phương pháp hiện tại cũng như chuỗi hiện tại.

Bất kỳ ý tưởng nào?

+0

bạn có thể thử sử dụng lệnh gọi threadmxbean hotspot cụ thể để nhận các byte được chỉ định theo chuỗi sau mỗi lần kiểm tra - http://docs.oracle.com/javase/6/docs/jre/api/management/extension/com/ mặt trời/quản lý/ThreadMXBean.html # getThreadAllocatedBytes (long) - như một biện pháp đáng tin cậy hơn – Matt

+0

cung cấp byte sau được phân bổ cho mỗi thử nghiệm (test1 - 28344, test2 - 29813408, test3 - 114968, test4 - 411480, test5 - 69673176, test6 - 1368) với EA trên và (test1 - 3764906168, test2 - 88636496, test3 - 3867438248, test4 - 6105335224, test5 - 68352808, test6 - 1400) với EA tắt – Matt

+3

bạn cũng có thể sử dụng bản dựng gỡ lỗi và '-XX: + UnlockDiagnosticVMOptions - XX: + PrintEscapeAnalysis -XX: + PrintEliminateAllocations' – Matt

Trả lời

8

EA là thứ mà trình biên dịch C2 phân tích dựa trên IR nó tạo ra do đó bạn cần nó để biên dịch phương thức trước khi tận hưởng lợi ích. Mỗi bài kiểm tra được gọi một lần duy nhất để không có cơ hội cho nó để biên dịch. Chi tiết về EA và C2 IR trong internals hotspot wiki (https://wikis.oracle.com/display/HotSpotInternals/Overview+of+Ideal,+C2 's + cao cấp + + trung gian + đại diện và https://wikis.oracle.com/display/HotSpotInternals/EscapeAnalysis)

Dưới đây là một phiên bản mà cố gắng để hiển thị các tác động

import com.sun.management.ThreadMXBean; 

import java.lang.management.ManagementFactory; 
import java.util.ArrayList; 
import java.util.Iterator; 


public class EscapeAnalysisTest { 

    private static final long TIME_TO_TEST = 10L * 1000L; // 10s 

    static class Timestamp { 
     private long millis; 

     public Timestamp(long millis) { 
      this.millis = millis; 
     } 

     public long getTime() { 
      return millis; 
     } 

     public void setTime(long time) { 
      millis = time; 
     } 
    } 

    public static void main(String[] args) { 
     System.out.println("****"); 
     doIt(); 
     System.out.println("****"); 
     doIt(); 
     System.out.println("****"); 
     doIt(); 
     System.out.println("****"); 
     doIt(); 
     System.out.println("****"); 
    } 

    private static void doIt() { 
     final ThreadMXBean mxbean = (ThreadMXBean) ManagementFactory.getThreadMXBean(); 
     final long tid = Thread.currentThread().getId(); 
     long r = 0; 
     final long allocPre = mxbean.getThreadAllocatedBytes(tid); 
     r += test1(); 
     long alloc1 = mxbean.getThreadAllocatedBytes(tid); 
     System.out.println("test1 - " + (alloc1 - allocPre)); 
     r += test2(); 
     final long alloc2 = mxbean.getThreadAllocatedBytes(tid); 
     System.out.println("test2 - " + (alloc2 - alloc1)); 
     r += test3(); 
     final long alloc3 = mxbean.getThreadAllocatedBytes(tid); 
     System.out.println("test3 - " + (alloc3 - alloc2)); 
     r += test4(); 
     final long alloc4 = mxbean.getThreadAllocatedBytes(tid); 
     System.out.println("test4 - " + (alloc4 - alloc3)); 
     r += test5(); 
     final long alloc5 = mxbean.getThreadAllocatedBytes(tid); 
     System.out.println("test5 - " + (alloc5 - alloc4)); 
     r += test6(); 
     final long alloc6 = mxbean.getThreadAllocatedBytes(tid); 
     System.out.println("test6 - " + (alloc6 - alloc5)); 
     System.out.println(r); 
    } 

    public static long test1() { 
     long r = 0; 
     long start = System.currentTimeMillis(); 
     while (System.currentTimeMillis() - start < TIME_TO_TEST) { 
      r += new Timestamp(System.currentTimeMillis()).getTime(); 
     } 
     return r; 
    } 

    public static long test2() { 
     ArrayList<Integer> l = new ArrayList<Integer>(1000); 
     for (int i = 0; i < 1000; ++i) { 
      l.add(i); 
     } 
     long r = 0; 
     long start = System.currentTimeMillis(); 
     while (System.currentTimeMillis() - start < TIME_TO_TEST) { 
      for (Iterator<Integer> it = l.iterator(); it.hasNext();) { 
       r += it.next().longValue(); 
      } 
     } 
     return r; 
    } 

    public static long test3() { 
     long r = 0; 
     long start = System.currentTimeMillis(); 
     while (System.currentTimeMillis() - start < TIME_TO_TEST) { 
      Timestamp ts = new Timestamp(System.currentTimeMillis()); 
      ts.setTime(42); 
      r += ts.getTime(); 
     } 
     return r; 
    } 

    public static long test4() { 
     ArrayList<Integer> l = new ArrayList<Integer>(1000); 
     for (int i = 0; i < 1000; ++i) { 
      l.add(i); 
     } 
     long r = 0; 
     long start = System.currentTimeMillis(); 
     while (System.currentTimeMillis() - start < TIME_TO_TEST) { 
      Iterator<Integer> it = l.iterator(); 
      r += it.next().longValue(); 
      r += it.next().longValue(); 
      r += it.next().longValue(); 
      r += it.next().longValue(); 
     } 
     return r; 
    } 

    public static long test5() { 
     ArrayList<Integer> l = new ArrayList<Integer>(1000); 
     for (int i = 0; i < 1000; ++i) { 
      l.add(i); 
     } 
     long r = 0; 
     long start = System.currentTimeMillis(); 
     while (System.currentTimeMillis() - start < TIME_TO_TEST) { 
      Iterator<Integer> it = l.iterator(); 
      for (int i = 0; i < l.size(); ++i) { 
       r += it.next().longValue(); 
      } 
     } 
     return r; 
    } 

    public static long test6() { 
     long r = 0; 
     long start = System.currentTimeMillis(); 
     while (System.currentTimeMillis() - start < TIME_TO_TEST) { 
      for (Timestamp ts = new Timestamp(System.currentTi()); 
       ts.getTime() > 0; 
       ts.setTime(ts.getTime() + System.currentTimeMillis())) { 
       r += ts.getTime(); 
      } 
     } 
     return r; 
    } 

} 

mà tạo ra sau đầu ra khi chạy với -server -XX:CompileThreshold=1

**** 
test1 - 109048 
test2 - 89243416 
test3 - 16664 
test4 - 42840 
test5 - 71982168 
test6 - 1400 
-5351026995119026839 
**** 
test1 - 16432 
test2 - 85921464 
test3 - 16664 
test4 - 42840 
test5 - 66777600 
test6 - 1368 
7844020592566674506 
**** 
test1 - 48 
test2 - 18256 
test3 - 272 
test4 - 18264 
test5 - 18264 
test6 - 272 
-2137858376905291730 
**** 
test1 - 48 
test2 - 18256 
test3 - 272 
test4 - 18264 
test5 - 18264 
test6 - 272 
3273987624143297143 
**** 

một sự nguy hiểm ở đây là tổng hợp của phương pháp này đã thay đổi nó về cơ bản hơn, tôi đã không cố gắng để bảo vệ chống lại điều này một số sử dụng cái LogCompilation hoặc PrintCompilation có thể được yêu cầu để kiểm tra.

+0

Kudos cho bạn @Matt, điều này hoạt động tuyệt vời. Dựa trên lời giải thích của bạn, tôi đã cố gắng sửa đổi hàm test2 ban đầu của mình để di chuyển vòng lặp for đến hàm và ngăn xếp phân bổ đã làm việc từ lần chạy phương thức đầu tiên. – jpountz

+0

điều tôi không hiểu là lý do tại sao số lượng phân bổ cao hơn rất nhiều với EA ngay cả trước khi biên dịch. Cần cào cằm nhiều hơn trên cái đó. – Matt

2

Phân tích thoát dựa nhiều vào nội tuyến của các cuộc gọi hàm.

Giống như với bất kỳ microbenchmark khác - đặc biệt là trên VM máy chủ - cần phải khởi động. Nếu bạn loại bỏ -XX:CompileThreshold=1 và thực hiện thử nghiệm chính trong vòng lặp, bạn sẽ thấy rằng sau 1-2 lần lặp lại nó sẽ ngừng thu gom rác vì trình biên dịch thu thập đủ thông tin lược tả để nội tuyến các phương thức và sau đó thực hiện phân tích thoát.

2

Tôi chỉ điều tra cùng một điều, nhưng đối với Java 8. Tôi đặt câu trả lời của tôi trong một câu hỏi trùng lặp như tôi đã không tìm thấy một trong những thời gian này.

Tóm tắt từ the full answer:

Đầu tiên, nó phụ thuộc vào việc triển khai. Câu trả lời này áp dụng cho OpenJDK 1.8 và có lẽ cũng là Oracle JVM 1.8. Thứ hai, như những người khác đã tuyên bố, phân bổ stack chỉ xảy ra khi một phương pháp được biên dịch bởi trình biên dịch C2, điều này chỉ xảy ra khi phương thức đã được gọi là đủ thời gian.

Nếu vậy, các đối tượng có thể được ngăn xếp phân bổ nếu

  • tất cả các công dụng của nó đều được sắp xếp
  • nó không bao giờ giao cho bất kỳ lĩnh vực tĩnh hoặc đối tượng, chỉ để biến địa phương
  • tại mỗi điểm trong chương trình, mà các biến cục bộ chứa tham chiếu đến đối tượng phải là thời gian JIT xác định được, và không phụ thuộc vào bất kỳ luồng điều khiển không thể đoán trước nào.
  • Nếu đối tượng là một mảng, kích thước của nó phải là hằng số JIT và lập chỉ mục vào nó phải sử dụng hằng số thời gian JIT.

Đặc biệt nội tuyến không thể dự đoán được nếu bạn không biết một số điểm đặc biệt của Hotspot. Xem câu trả lời được liên kết để biết một số chi tiết.

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