2011-12-15 29 views
47

System.arraycopy() của Java có hiệu quả đối với mảng nhỏ hay thực tế là nó là một phương pháp gốc khiến cho nó có khả năng kém hiệu quả hơn một vòng lặp đơn giản và một cuộc gọi hàm?Là System.arraycopy của Java() hiệu quả cho các mảng nhỏ?

Phương pháp gốc có phải trả thêm chi phí hiệu năng để vượt qua một số loại cầu nối hệ thống Java không?

+10

Bạn đã thử và chạy điểm chuẩn chưa? – corsiKa

+2

Tôi rất muốn thấy một microbenchmark về điều này. –

+1

Tôi nghĩ rằng mã nguồn gốc được xây dựng không bị ảnh hưởng bởi độ trễ JNI – gd1

Trả lời

26

Mở rộng một chút về những gì Sid đã viết, rất có thể là System.arraycopy chỉ là một nội tại của JIT; có nghĩa là khi mã gọi System.arraycopy, nó có thể sẽ được gọi là thực thi JIT cụ thể (một khi các thẻ JIT System.arraycopy là "nóng") không được thực hiện thông qua giao diện JNI, vì vậy nó không phải chịu chi phí bình thường của gốc phương pháp. Nói chung, việc thực hiện các phương thức gốc có một số chi phí (đi qua giao diện JNI, cũng có thể một số hoạt động JVM nội bộ không thể xảy ra khi các phương thức gốc đang được thực thi). Nhưng không phải vì một phương thức được đánh dấu là "nguyên gốc" mà bạn đang thực sự thực hiện nó bằng cách sử dụng JNI. JIT có thể làm một số điều điên rồ. Một cách dễ nhất để kiểm tra, như đã được đề xuất, viết một điểm chuẩn nhỏ, cẩn thận với các tiêu chuẩn bình thường của các dấu ấn Java (làm nóng mã trước, tránh mã không có tác dụng phụ vì JIT chỉ tối ưu hóa nó như là một no-op, vv).

+0

@vanze, các điểm chuẩn nhỏ chạy hoàn toàn trong bộ đệm L1 chỉ là sai. – bestsss

+2

@bestsss: Anh ấy có thể có nghĩa là nhỏ về mặt số lượng mã, không nhất thiết phải nhỏ về số lượng mảng mà tôi muốn sao chép. – Gravity

+0

@Gravity, hầu hết mọi người hầu như luôn luôn nhận được các tiêu chuẩn vi sai, đặc biệt là Java. về cơ bản bạn cần phải biết loại mã nào mà JIT phát ra để viết một tiêu chuẩn vi mô thích hợp. Thông thường, cách dễ nhất là nhìn vào bộ tạo được tạo ra và so sánh kết quả so với dự kiến. – bestsss

5

Mã byte được thực thi một cách tự nhiên, vì vậy có khả năng hiệu suất sẽ tốt hơn vòng lặp.

Vì vậy, trong trường hợp vòng lặp, nó sẽ phải thực thi mã byte sẽ phải chịu phí. Trong khi mảng bản sao nên được memcopy thẳng.

+2

Có một khoản phí bổ sung để chuyển từ Java sang mã gốc. –

+0

@ AndyThomas-Cramer, không có mã nguồn gốc nào cả về ngữ cảnh chuyển sang JNI. Và lưu ý phụ: JIT đủ thông minh để tối ưu hóa bản sao mảng qua vòng lặp với cùng mã như 'System.arraycopy' – bestsss

+0

" JIT đủ thông minh để tối ưu hóa bản sao mảng qua vòng lặp với cùng mã như System.arraycopy ". -- Là nó? Về lý thuyết có ý nghĩa rất nhiều, nhưng tôi chưa từng nghe về một thứ như vậy. Bạn có thể trỏ đến một tham chiếu? – Gravity

-1

Chức năng gốc phải nhanh hơn các hàm JVM, vì không có phí VM. Tuy nhiên đối với rất nhiều (> 1000) rất nhỏ (len < 10) mảng nó có thể chậm hơn.

+0

Bạn có thể vui lòng lập phương án này không? Tại sao nó sẽ chậm hơn cho rất nhiều mảng nhỏ? –

+0

Tôi đoán vì tính năng gọi điện trên không? Trừ khi ngay cả các chức năng gốc có tính chất JIT nội tại có thể được gạch chân ... – Gravity

+0

Có phí gọi trên có thể lớn hơn đối với các chức năng gốc. Khi nó tăng lên, nó có thể lớn hơn thời gian được lưu bằng cách loại bỏ chi phí JVM – laci37

16

Đây là mối quan tâm hợp lệ. Ví dụ, trong java.nio.DirectByteBuffer.put(byte[]), tác giả cố gắng tránh một bản sao JNI cho số nhỏ các nguyên tố

// These numbers represent the point at which we have empirically 
// determined that the average cost of a JNI call exceeds the expense 
// of an element by element copy. These numbers may change over time. 
static final int JNI_COPY_TO_ARRAY_THRESHOLD = 6; 
static final int JNI_COPY_FROM_ARRAY_THRESHOLD = 6; 

cho System.arraycopy(), chúng tôi có thể kiểm tra như thế nào JDK sử dụng nó. Ví dụ: trong ArrayList, System.arraycopy() luôn được sử dụng, không bao giờ "yếu tố theo bản sao phần tử", bất kể độ dài (ngay cả khi nó là 0). Kể từ khi ArrayList là rất hiệu quả có ý thức, chúng tôi có thể lấy được rằng System.arraycopy() là cách hiệu quả nhất của mảng sao chép bất kể chiều dài.

+11

Tôi cho rằng một phần của câu hỏi là liệu 'System.arraycopy()' có đi qua JNI hay không. Như một người nào đó đã chỉ ra, thực tế là nó được khai báo là 'native' không có nghĩa gì cả bởi vì JVM được phép có tất cả các loại tối ưu hóa đặc biệt. – Gravity

+0

Tôi đã nghĩ chính xác suy nghĩ tương tự về lớp 'ArrayList', ngay cả khi tôi hỏi câu hỏi này:' ArrayList', được cho là một lớp được tối ưu hóa tốt, sẽ không làm bất cứ thứ gì không tốn kém. Tuy nhiên, có lẽ các tác giả đã tìm ra hiệu suất của các 'ArrayLists' nhỏ không quan trọng lắm (vì nó là các hoạt động trên các tập dữ liệu lớn, tốn kém nhất), hoặc có thể không hiệu quả thống nhất trên tất cả các JVM. Tôi sẽ phải chuẩn bị để được thuyết phục. – Gravity

+2

họ sẽ quan tâm đến hiệu suất của * nhiều * danh sách mảng nhỏ. do đó, mỗi danh sách mảng nhỏ phải hoạt động tốt. – irreputable

22

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

public void test(int copySize, int copyCount, int testRep) { 
    System.out.println("Copy size = " + copySize); 
    System.out.println("Copy count = " + copyCount); 
    System.out.println(); 
    for (int i = testRep; i > 0; --i) { 
     copy(copySize, copyCount); 
     loop(copySize, copyCount); 
    } 
    System.out.println(); 
} 

public void copy(int copySize, int copyCount) { 
    int[] src = newSrc(copySize + 1); 
    int[] dst = new int[copySize + 1]; 
    long begin = System.nanoTime(); 
    for (int count = copyCount; count > 0; --count) { 
     System.arraycopy(src, 1, dst, 0, copySize); 
     dst[copySize] = src[copySize] + 1; 
     System.arraycopy(dst, 0, src, 0, copySize); 
     src[copySize] = dst[copySize]; 
    } 
    long end = System.nanoTime(); 
    System.out.println("Arraycopy: " + (end - begin)/1e9 + " s"); 
} 

public void loop(int copySize, int copyCount) { 
    int[] src = newSrc(copySize + 1); 
    int[] dst = new int[copySize + 1]; 
    long begin = System.nanoTime(); 
    for (int count = copyCount; count > 0; --count) { 
     for (int i = copySize - 1; i >= 0; --i) { 
      dst[i] = src[i + 1]; 
     } 
     dst[copySize] = src[copySize] + 1; 
     for (int i = copySize - 1; i >= 0; --i) { 
      src[i] = dst[i]; 
     } 
     src[copySize] = dst[copySize]; 
    } 
    long end = System.nanoTime(); 
    System.out.println("Man. loop: " + (end - begin)/1e9 + " s"); 
} 

public int[] newSrc(int arraySize) { 
    int[] src = new int[arraySize]; 
    for (int i = arraySize - 1; i >= 0; --i) { 
     src[i] = i; 
    } 
    return src; 
} 

Từ thử nghiệm của tôi, gọi test() với copyCount = 10000000 (1e7) hoặc cao hơn cho phép khởi động phải đạt được trong thời gian copy/loop cuộc gọi đầu tiên, vì vậy sử dụng testRep = 5 là đủ; Với copyCount = 1000000 (1e6) nhu cầu khởi động ít nhất 2 hoặc 3 lần lặp nên testRep sẽ được tăng lên để có được kết quả khả dụng.

Với cấu hình của tôi (CPU Intel Core 2 Duo E8500 @ 3.16GHz, Java SE 1.6.0_35-b10 và Eclipse 3.7.2) nó xuất hiện từ benchmark rằng:

  • Khi copySize = 24, System.arraycopy() và vòng tay mất gần như cùng một thời điểm (đôi khi một là rất nhẹ nhanh hơn so với khác, thời điểm khác đó là ngược lại),
  • Khi copySize < 24, vòng tay là nhanh hơn so với System.arraycopy() (nhanh hơn với copySize = 23 hơi, thực sự nhanh hơn với copySize < 5),
  • Khi copySize> 24, System.arraycopy() là nhanh hơn so với vòng tay (hơi nhanh hơn với copySize = 25, tỷ lệ thời gian vòng lặp/mảng thời gian tăng lên khi tăng copySize).

Lưu ý: Tôi không phải là người bản ngữ tiếng Anh, vui lòng tha lỗi tất cả các lỗi ngữ pháp/từ vựng của tôi.

+1

Tôi nhận được kết quả khác nhau. Nó không có vẻ quan trọng ngay cả với các mảng kích thước nhỏ. Các mảng lớn vào hàng nghìn, hệ thống chiếu phim nhanh hơn rất nhiều. các mảng nhỏ tôi thấy không khác .. – RickHigh

+1

Tôi cũng có các kết quả khác nhau. Với kích thước mảng là 5000 System.arrayCopy nhanh hơn khoảng 4 lần và kích thước mảng chỉ bằng 5, nó vẫn nhanh hơn khoảng 20%. Nó dường như thậm chí ra ở một kích thước của 2. Vì vậy, tôi không nghĩ rằng có bao giờ là một lý do không sử dụng System.arrayCopy – nikdeapen

+1

@nikdeapen dường như tình hình thay đổi qua nhiều năm –

7

System.arraycopy sử dụng thao tác memmove để di chuyển từ và lắp ráp để di chuyển các kiểu nguyên thủy khác trong C đằng sau hiện trường. Vì vậy, nó làm cho nỗ lực của nó tốt nhất để di chuyển nhiều như hiệu quả nó có thể đạt được.

+1

Bạn có thể muốn báo giá cho * mà * triển khai ... – Pacerier

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