2017-06-11 22 views
5

Gần đây tôi đã chơi với một số điểm chuẩn và tìm thấy kết quả rất thú vị mà tôi không thể giải thích ngay bây giờ. Dưới đây là điểm chuẩn:Tại sao Arrays.copyOf nhanh hơn 2 lần so với System.arraycopy cho mảng nhỏ?

@BenchmarkMode(Mode.Throughput) 
@Fork(1) 
@State(Scope.Thread) 
@Warmup(iterations = 10, time = 1, batchSize = 1000) 
@Measurement(iterations = 10, time = 1, batchSize = 1000) 
public class ArrayCopy { 

    @Param({"1","5","10","100", "1000"}) 
    private int size; 
    private int[] ar; 

    @Setup 
    public void setup() { 
     ar = new int[size]; 
     for (int i = 0; i < size; i++) { 
      ar[i] = i; 
     } 
    } 

    @Benchmark 
    public int[] SystemArrayCopy() { 
     final int length = size; 
     int[] result = new int[length]; 
     System.arraycopy(ar, 0, result, 0, length); 
     return result; 
    } 

    @Benchmark 
    public int[] javaArrayCopy() { 
     final int length = size; 
     int[] result = new int[length]; 
     for (int i = 0; i < length; i++) { 
      result[i] = ar[i]; 
     } 
     return result; 
    } 

    @Benchmark 
    public int[] arraysCopyOf() { 
     final int length = size; 
     return Arrays.copyOf(ar, length); 
    } 

} 

Kết quả:

Benchmark     (size) Mode Cnt  Score  Error Units 
ArrayCopy.SystemArrayCopy  1 thrpt 10 52533.503 ± 2938.553 ops/s 
ArrayCopy.SystemArrayCopy  5 thrpt 10 52518.875 ± 4973.229 ops/s 
ArrayCopy.SystemArrayCopy  10 thrpt 10 53527.400 ± 4291.669 ops/s 
ArrayCopy.SystemArrayCopy  100 thrpt 10 18948.334 ± 929.156 ops/s 
ArrayCopy.SystemArrayCopy 1000 thrpt 10 2782.739 ± 184.484 ops/s 
ArrayCopy.arraysCopyOf   1 thrpt 10 111665.763 ± 8928.007 ops/s 
ArrayCopy.arraysCopyOf   5 thrpt 10 97358.978 ± 5457.597 ops/s 
ArrayCopy.arraysCopyOf   10 thrpt 10 93523.975 ± 9282.989 ops/s 
ArrayCopy.arraysCopyOf  100 thrpt 10 19716.960 ± 728.051 ops/s 
ArrayCopy.arraysCopyOf  1000 thrpt 10 1897.061 ± 242.788 ops/s 
ArrayCopy.javaArrayCopy   1 thrpt 10 58053.872 ± 4955.749 ops/s 
ArrayCopy.javaArrayCopy   5 thrpt 10 49708.647 ± 3579.826 ops/s 
ArrayCopy.javaArrayCopy  10 thrpt 10 48111.857 ± 4603.024 ops/s 
ArrayCopy.javaArrayCopy  100 thrpt 10 18768.866 ± 445.238 ops/s 
ArrayCopy.javaArrayCopy  1000 thrpt 10 2462.207 ± 126.549 ops/s 

Vì vậy, có hai điều lạ ở đây:

  • Arrays.copyOf nhanh hơn System.arraycopy 2 lần cho nhỏ mảng (1,5 , 10 kích thước). Tuy nhiên, trên một mảng lớn có kích thước 1000 Arrays.copyOf trở nên chậm hơn gần 2 lần. Tôi biết rằng cả hai phương pháp là nội tại, vì vậy tôi mong đợi cùng một hiệu suất. Trường hợp sự khác biệt này đến từ đâu?
  • Bản sao thủ công cho mảng 1 phần tử nhanh hơn System.arraycopy. Nó không rõ ràng với tôi tại sao. Có ai biết không?

VM phiên bản: JDK 1.8.0_131, VM 25,131-b11

+2

Vì 'copyOf' sử dụng nội bộ' mảng bản đồ', điểm chuẩn của bạn là vấn đề. – Andreas

+2

@Andreas Bạn không đúng. 'Arrays.copyOf' là JVM nội tại. Khi phương thức được biên dịch JIT, mã Java trong 'Arrays.java' không được thực thi. – apangin

+0

@Andreas Bạn có thấy bất kỳ vấn đề cụ thể nào với điểm chuẩn không? Nó sử dụng một cách khôn ngoan [khuôn khổ JMH] (http://openjdk.java.net/projects/code-tools/jmh/) để tránh những cạm bẫy chuẩn. – apangin

Trả lời

3

benchmark SystemArrayCopy bạn không phải là ngữ nghĩa tương đương với arraysCopyOf.

Nó sẽ được, nếu bạn thay thế

System.arraycopy(ar, 0, result, 0, length); 

với

System.arraycopy(ar, 0, result, 0, Math.min(ar.length, length)); 

Với sự thay đổi này hiệu suất của cả hai tiêu chuẩn cũng sẽ trở thành tương tự.

Tại sao biến thể đầu tiên lại chậm hơn?

  1. Mà không biết như thế nào length liên quan đến ar.length JVM cần thực hiện giới hạn thêm kiểm tra và được chuẩn bị để ném IndexOutOfBoundsException khi length > ar.length.
  2. Điều này cũng phá vỡ tối ưu hóa để loại bỏ zeroing dư thừa. Bạn biết, mọi mảng được phân bổ phải được khởi tạo bằng số không. Tuy nhiên, JIT có thể tránh zeroing nếu nó thấy rằng mảng được lấp đầy ngay sau khi tạo. Nhưng -prof perfasm rõ ràng cho thấy rằng bản gốc SystemArrayCopy chuẩn dành nhiều thời gian thanh toán bù trừ các mảng phân bổ:

    0,84% 0x000000000365d35f: shr $0x3,%rcx 
    0,06% 0x000000000365d363: add $0xfffffffffffffffe,%rcx 
    0,69% 0x000000000365d367: xor %rax,%rax 
          0x000000000365d36a: shl $0x3,%rcx 
    21,02% 0x000000000365d36e: rep rex.W stos %al,%es:(%rdi) ;*newarray 
    

sao chép bằng tay xuất hiện nhanh hơn cho mảng nhỏ, vì không giống như System.arraycopy nó không thực hiện bất kỳ thời gian chạy các cuộc gọi đến các chức năng VM .

+1

Không phải 'ar.length' và' length' giống nhau không? 'Math.min (ar.length, length)' có hiệu lực gì? – Boann

+0

@Boann Chúng giống nhau, nhưng JVM không biết điều này. 'Math.min' cho phép trình biên dịch biết rằng' System.arraycopy' sẽ không bao giờ ném 'IndexOutOfBoundsException'. – apangin

+0

Vui lòng giải thích. Trình biên dịch không biết 'System.arraycopy()' từ một lỗ trên mặt đất, khác với tên của nó, thứ tự gọi, kết quả và mệnh đề 'throws' (mà trên thực tế nó không có). 'System.arraycopy()' chỉ nhận được kết quả của 'Math.min()' trong đối số thứ năm của nó, và không biết nó được bắt nguồn như thế nào. – EJP

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