การใช้ System.arraycopy (…) จะดีกว่าสำหรับลูปสำหรับคัดลอกอาร์เรย์หรือไม่


90

ฉันต้องการสร้างอาร์เรย์ของวัตถุใหม่โดยรวมอาร์เรย์ขนาดเล็กสองอาร์เรย์เข้าด้วยกัน

ไม่สามารถเป็นค่าว่างได้ แต่ขนาดอาจเป็น 0

ฉันไม่สามารถเลือกระหว่างสองวิธีนี้: พวกเขาเทียบเท่าหรือมีประสิทธิภาพมากกว่า (เช่น system.arraycopy () คัดลอกทั้งชิ้น)

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
System.arraycopy(publicThings, 0, things, 0, publicThings.length);
System.arraycopy(privateThings, 0, things,  publicThings.length, privateThings.length);

หรือ

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
for (int i = 0; i < things.length; i++) {
    if (i<publicThings.length){
        things[i] = publicThings[i]
    } else {
        things[i] = privateThings[i-publicThings.length]        
    }
}

ความแตกต่างเพียงอย่างเดียวคือรูปลักษณ์ของรหัสหรือไม่?

แก้ไข:ขอบคุณสำหรับคำถามที่เชื่อมโยง แต่ดูเหมือนว่าจะมีการอภิปรายที่ยังไม่ได้แก้ไข:

จะเร็วกว่าจริงไหมถ้าit is not for native types: byte [], Object [], char [] ในกรณีอื่น ๆ ทั้งหมดจะมีการดำเนินการตรวจสอบประเภทซึ่งจะเป็นกรณีของฉันและจะเทียบเท่า ... ไม่?

สำหรับคำถามที่เชื่อมโยงอื่น ๆ พวกเขากล่าวว่าthe size matters a lotสำหรับขนาด> 24 system.arraycopy () ชนะสำหรับขนาดเล็กกว่า 10 คู่มือสำหรับลูปจะดีกว่า ...

ตอนนี้ฉันสับสนจริงๆ


16
arraycopy()เป็นการโทรแบบพื้นเมืองซึ่งเร็วกว่าอย่างแน่นอน
Sotirios Delimanolis

4
คุณได้ลองเปรียบเทียบการใช้งานทั้งสองแบบที่แตกต่างกันแล้วหรือยัง
Alex


15
คุณควรเลือกสิ่งที่คุณคิดว่าอ่านง่ายที่สุดและง่ายที่สุดในการบำรุงรักษาในอนาคต เมื่อคุณพิจารณาแล้วว่านี่คือที่มาของปัญหาคอขวดเท่านั้นที่คุณควรเปลี่ยนแนวทางของคุณ
arshajii

1
อย่าคิดค้นล้อใหม่!
camickr

คำตอบ:


87
public void testHardCopyBytes()
{
    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    for(int i = 0; i < out.length; i++)
    {
        out[i] = bytes[i];
    }
}

public void testArrayCopyBytes()
{
    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    System.arraycopy(bytes, 0, out, 0, out.length);
}

ฉันรู้ว่าการทดสอบ JUnit ไม่ใช่วิธีที่ดีที่สุดสำหรับการเปรียบเทียบ แต่
testHardCopyBytes ใช้เวลา 0.157 วินาทีในการทำให้เสร็จสมบูรณ์
และ
testArrayCopyBytes ใช้เวลา 0.086 วินาทีจึงจะเสร็จสมบูรณ์

ฉันคิดว่ามันขึ้นอยู่กับเครื่องเสมือน แต่ดูเหมือนว่ามันจะคัดลอกบล็อกของหน่วยความจำแทนที่จะคัดลอกองค์ประกอบอาร์เรย์เดี่ยว สิ่งนี้จะเพิ่มประสิทธิภาพอย่างแน่นอน

แก้ไข:
ดูเหมือนว่าประสิทธิภาพของ System.arraycopy จะครอบคลุมทั้งหมด เมื่อใช้สตริงแทนไบต์และอาร์เรย์มีขนาดเล็ก (ขนาด 10) ฉันจะได้ผลลัพธ์เหล่านี้:

    String HC:  60306 ns
    String AC:  4812 ns
    byte HC:    4490 ns
    byte AC:    9945 ns

นี่คือสิ่งที่ดูเหมือนว่าเมื่ออาร์เรย์มีขนาด 0x1000000 ดูเหมือนว่า System.arraycopy จะชนะด้วยอาร์เรย์ที่ใหญ่กว่า

    Strs HC:  51730575 ns
    Strs AC:  24033154 ns
    Bytes HC: 28521827 ns
    Bytes AC: 5264961 ns

แปลกมาก!

ขอขอบคุณ Daren ที่ชี้ให้เห็นว่าการอ้างอิงคัดลอกแตกต่างกัน มันทำให้ปัญหานี้น่าสนใจขึ้นมาก!


2
ขอบคุณสำหรับความพยายามของคุณ แต่คุณพลาดจุดสำคัญที่ดูเหมือนว่า: ประเภทที่ไม่ใช่เนทีฟ (สร้างคลาสสุ่มด้วยการออกแบบใด ๆ เพื่อให้อาร์เรย์มีการอ้างอิง) และขนาด ... ดูเหมือนว่าจะมีขนาดอาร์เรย์ที่เล็กกว่าการใช้งานสำหรับลูปด้วยตนเองจะเร็วกว่า ต้องการแก้ไขปัญหานี้หรือไม่?
Daren

2
โอ้ว้าวคุณพูดถูก! ที่น่าสนใจ การวางสตริงในอาร์เรย์เหล่านั้นแทนที่จะเป็นไบต์ทำให้เกิดความแตกต่างอย่างมาก: <<< testHardCopyStrs: 0.161s >>> <<< testArrayCopyStrs: 0.170s >>>
Trent Small

คุณได้รับผลลัพธ์อะไรบ้าง? ที่น่าสนใจก็คือลองใช้ array size = 10 ... ขอบคุณ! (ฉันหวังว่าฉันจะมี IDE ที่นี่ฉันกำลังเขียนโค้ดโดยไม่ต้องคอมไพเลอร์)
Daren

ตอนนี้ฉันได้รวมไว้ในการโทร System.nanoTime () และตั้งค่าขนาด = 10 เพื่อดูว่าแต่ละครั้งใช้เวลากี่นาโนวินาที ดูเหมือนว่าสำหรับอาร์เรย์ดั้งเดิมขนาดเล็กลูปจะดีกว่า สำหรับการอ้างอิง arrayCopy จะดีกว่า: <<< testHardCopyBytes: 4491 ns >>> <<< testHardCopyStrs: 56778 ns >>> <<< testArrayCopyBytes: 10265 ns >>> <<< testArrayCopyStrs: 4490 ns >>>
Trent ขนาดเล็ก

ผลลัพธ์ที่น่าสนใจมาก! ขอบคุณมาก! คุณสามารถแก้ไขคำตอบของคุณเพื่อรวมสิ่งนี้ได้หรือไม่และฉันยินดีที่จะยอมรับคำตอบเพื่อให้ทุกคนได้เห็นเป็นคนแรก ... :)
Daren

36

Arrays.copyOf(T[], int)อ่านง่ายกว่า Internaly ใช้System.arraycopy()ซึ่งเป็นการเรียกแบบเนทีฟ

เร็วกว่านี้ไม่ได้แล้ว!


ดูเหมือนว่าคุณสามารถขึ้นอยู่กับบางสิ่งได้ แต่ขอบคุณที่ชี้ให้เห็นว่า fucntion ที่ฉันไม่รู้และอ่านง่ายกว่า :)
Daren

ใช่! มันขึ้นอยู่กับบางสิ่งอย่างที่ @Svetoslav Tsolov กล่าว ฉันแค่อยากจะชี้ให้เห็น Arrays.copyOf
Philipp Sander

2
copyOfไม่สามารถแทนที่ได้เสมอarraycopyไป แต่เหมาะสำหรับกรณีการใช้งานนี้
Blaisorblade

1
หมายเหตุหากคุณกำลังดูประสิทธิภาพการทำงานสิ่งนี้จะไม่รวดเร็วเท่ากับ System.arraycopy () เนื่องจากต้องมีการจัดสรรหน่วยความจำ หากเป็นแบบวนซ้ำการจัดสรรซ้ำ ๆ จะนำไปสู่การรวบรวมขยะซึ่งจะเป็นผลงานที่ยอดเยี่ยม
Will Calderwood

1
@PhilippSander เพียงเพื่อตรวจสอบว่าฉันไม่ได้โง่ฉันเพิ่มรหัสเพื่อคัดลอกอาร์เรย์ 1MB ในลูปเกมของฉันซึ่งแทบไม่เคยยิง GC เลย ด้วย Array.copyOf () DVM ของฉันโทรหา GC 5 ครั้งต่อวินาทีและเกมก็ล้ามาก ฉันคิดว่ามันปลอดภัยที่จะบอกว่ามีการจัดสรรหน่วยความจำเกิดขึ้น
Will Calderwood

17

ขึ้นอยู่กับเครื่องเสมือน แต่ System.arraycopy ควรให้คุณได้ใกล้เคียงที่สุดกับประสิทธิภาพดั้งเดิม

ฉันทำงานเป็นนักพัฒนา java สำหรับระบบฝังตัวมา 2 ปีแล้ว (ซึ่งประสิทธิภาพเป็นสิ่งสำคัญมาก) และทุกที่ที่สามารถใช้ System.arraycopy ได้ฉันมักจะใช้มัน / เห็นว่ามันใช้ในโค้ดที่มีอยู่ มักจะชอบมากกว่าการวนซ้ำเมื่อประสิทธิภาพเป็นปัญหา หากประสิทธิภาพไม่ใช่ปัญหาใหญ่ฉันจะดำเนินการต่อ อ่านง่ายกว่ามาก


สิ่งต่างๆเช่นขนาดและประเภทของอาร์เรย์ (พื้นฐานเทียบกับที่สืบทอดมา) ดูเหมือนจะส่งผลต่อ preformance
Daren

2
ใช่มันไม่ใช่ 'การแสดงพื้นเมือง' ซึ่งเป็นเหตุผลว่าทำไมฉันถึงบอกว่าฉัน 'ส่วนใหญ่' ใช้มันในที่ที่ฉันทำได้ (คุณจะสังเกตเห็นว่าส่วนใหญ่ชนะการคัดลอกแบบวนซ้ำ) ฉันเดาว่าเหตุผลคือ: เมื่อเป็นอาร์เรย์ขนาดเล็กของประเภทดั้งเดิม 'cost-to-call' จะใหญ่กว่าการเพิ่มประสิทธิภาพ การใช้ JNI สามารถลดประสิทธิภาพด้วยเหตุผลเดียวกันนั่นคือโค้ดเนทีฟเองนั้นเร็ว แต่การเรียกใช้จากกระบวนการ java - ไม่มากนัก

การแก้ไขเล็กน้อย Arrays.copy ไม่ใช่ JNI มันเป็นสิ่งที่อยู่ภายใน Intrinsics เร็วกว่า JNI มาก คอมไพลเลอร์ JIT เปลี่ยนเป็นอินทรินซิคอย่างไรและอย่างไรขึ้นอยู่กับ JVM / คอมไพเลอร์ที่ใช้
Nitsan Wakart

1
Arrays.copyไม่มีอยู่Arrays.copyOfเป็นฟังก์ชันไลบรารี
Blaisorblade

14

แทนที่จะพึ่งพาการคาดเดาและข้อมูลที่อาจล้าสมัยฉันใช้เกณฑ์มาตรฐานบางอย่าง . ในความเป็นจริงคาลิปเปอร์มาพร้อมกับตัวอย่างบางส่วนรวมถึงCopyArrayBenchmarkที่วัดคำถามนี้! สิ่งที่คุณต้องทำคือวิ่ง

mvn exec:java -Dexec.mainClass=com.google.caliper.runner.CaliperMain -Dexec.args=examples.CopyArrayBenchmark

ผลลัพธ์ของฉันอิงตาม Java HotSpot (TM) 64-Bit Server VM ของ Oracle, 1.8.0_31-b13 ซึ่งทำงานบน MacBook Pro กลางปี ​​2010 (macOS 10.11.6 พร้อม Intel Arrandale i7, 8 GiB RAM) ฉันไม่เชื่อว่าการโพสต์ข้อมูลเวลาดิบจะมีประโยชน์ แต่ฉันจะสรุปข้อสรุปด้วยการแสดงภาพประกอบที่สนับสนุน

สรุป:

  • การเขียนforลูปแบบแมนนวลเพื่อคัดลอกแต่ละองค์ประกอบลงในอาร์เรย์ที่สร้างอินสแตนซ์ใหม่จะไม่มีประโยชน์ไม่ว่าจะเป็นอาร์เรย์แบบสั้นหรืออาร์เรย์แบบยาว
  • Arrays.copyOf(array, array.length)และarray.clone()ทั้งสองอย่างรวดเร็วอย่างต่อเนื่อง เทคนิคทั้งสองนี้มีประสิทธิภาพเกือบเหมือนกัน คุณจะเลือกอันไหนเป็นเรื่องของรสนิยม
  • System.arraycopy(src, 0, dest, 0, src.length)เกือบจะเร็วพอ ๆ กับและแต่ก็ไม่ค่อยสม่ำเสมอนัก (ดูกรณี 50000 วินาที) ด้วยเหตุนี้และความฟุ่มเฟือยของการโทรฉันจึงขอแนะนำหากคุณต้องการการควบคุมที่ดีว่าองค์ประกอบใดถูกคัดลอกไปที่ไหนArrays.copyOf(array, array.length)array.clone()intSystem.arraycopy()

นี่คือแผนการกำหนดเวลา:

การกำหนดเวลาสำหรับการคัดลอกอาร์เรย์ที่มีความยาว 5 การกำหนดเวลาสำหรับการคัดลอกอาร์เรย์ที่มีความยาว 500 การกำหนดเวลาสำหรับการคัดลอกอาร์เรย์ที่มีความยาว 50000


3
มีอะไรแปลก ๆ เกี่ยวกับการคัดลอก int หรือไม่? มันดูแปลกที่ arraycopy จะช้าใน ints ที่สเกลใหญ่
whaleberg

6

การเรียกใช้เมธอดดั้งเดิมเช่นArrays.copyOf(T[], int)จะมีค่าใช้จ่ายบางส่วน แต่ก็ไม่ได้หมายความว่าจะไม่เร็วเท่าที่คุณกำลังดำเนินการโดยใช้ JNI

วิธีที่ง่ายที่สุดคือเขียนเกณฑ์มาตรฐานและทดสอบ

คุณสามารถตรวจสอบว่าArrays.copyOf(T[], int)เร็วกว่าforลูปปกติของคุณ

รหัสมาตรฐานจากที่นี่ : -

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;
}

System.arraycopy()ใช้ JNI (Java Native Interface) เพื่อคัดลอกอาร์เรย์ (หรือบางส่วน) ดังนั้นจึงรวดเร็วอย่างเห็นได้ชัดดังที่คุณสามารถยืนยันได้ ที่นี่


รหัสนี้ใช้ int [] คุณสามารถลองใช้กับ String [] (เริ่มต้นด้วยค่าที่แตกต่างกัน: "1", "2" ฯลฯ เนื่องจากไม่เปลี่ยนรูป
Daren

1
JNI ช้ามาก System.arraycopyไม่ใช้มัน
Chai T. Rex

ไม่มีSystem.arraycopyไม่ได้ใช้ JNI ซึ่งเป็นเพียงสำหรับการโทรห้องสมุดของบุคคลที่สาม แต่เป็นการเรียกแบบเนทีฟซึ่งหมายความว่ามีการใช้งานเนทีฟใน VM สำหรับมัน
spheenik

6

เป็นไปไม่ได้ที่Arrays.copyOfจะเร็วกว่าSystem.arraycopyนี้เนื่องจากเป็นการใช้งานcopyOf:

public static int[] copyOf(int[] original, int newLength) {
    int[] copy = new int[newLength];
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

4

System.arraycopy()เป็นการเรียกแบบเนทีฟซึ่งคัดลอกการทำงานโดยตรงที่หน่วยความจำ สำเนาหน่วยความจำเดียวจะเร็วกว่าการวนซ้ำของคุณเสมอ


3
ฉันอ่านแล้วว่าสำหรับประเภทที่ไม่ใช่เนทีฟ (คลาสที่สร้างขึ้นเช่นของฉัน) อาจไม่มีประสิทธิภาพเท่านี้ ... และสำหรับขนาดเล็ก (กรณีของฉัน) คู่มือสำหรับลูปจะดีกว่า ... สนใจที่จะแสดงความคิดเห็น?
Daren

อันที่จริง System.arraycopy () มีค่าใช้จ่ายบางส่วนดังนั้นสำหรับอาร์เรย์ขนาดเล็ก (n = ~ 10) การวนซ้ำเร็วขึ้นจริง
RecursiveExceptionException
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.