Чи краще використовувати System.arraycopy (…), ніж цикл for для копіювання масивів?


89

Я хочу створити новий масив об'єктів, складаючи два менших масиви.

Вони не можуть бути нульовими, але розмір може бути 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]        
    }
}

Єдина різниця - вигляд коду?

EDIT: дякую за зв’язане запитання, але, схоже, у них невирішена дискусія:

Це справді швидше, якщо 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 с.

Я думаю, це залежить від віртуальної машини, але схоже, що він копіює блоки пам'яті, а не копіює окремі елементи масиву. Це абсолютно збільшило б продуктивність.

EDIT:
Схоже, продуктивність 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

Як своєрідно!

Дякую, Дарен, що ти вказав, що посилання копіюються по-різному. Це зробило це набагато цікавішою проблемою!


2
Дякуємо за ваші зусилля, але ви пропустили важливі, здавалося б, моменти: нетипові типи (створіть випадковий клас з будь-яким знаком, щоб масив містив посилання) та розмір ... здається, для менших розмірів масиву ручний for-loop швидший. Хочете це виправити?
Дарен

2
Ой вау, ти маєш рацію! Це відсотки. Розміщення рядків у цих масивах замість байтів має величезну різницю: <<< testHardCopyStrs: 0.161s >>> <<< testArrayCopyStrs: 0.170s >>>
Трент Малий

Які результати ви отримуєте тоді? також цікаво було б спробувати з розміром масиву = 10 ... спасибі! (Я хотів би мати тут свою IDE, я кодую без компілятора).
Daren

Ну, тепер я обернув їх у деякі виклики System.nanoTime () і встановив size = 10, щоб побачити, скільки наносекунд займає кожен. Схоже на невеликі примітивні масиви, петлі краще; для посилань краще arrayCopy .: <<< testHardCopyBytes: 4491 ns >>> <<< testHardCopyStrs: 56778 ns >>> <<< testArrayCopyBytes: 10265 ns >>> <<< testArrayCopyStrs: 4490 ns >>>
Trent Невеликий

Дуже цікаві результати! Дуже дякую! чи можете ви, будь ласка, відредагувати свою відповідь, щоб включити це, і я із задоволенням прийму його, щоб він залишався першим, щоб побачити ... Ви вже отримали мій голос. :)
Дарен

36

Arrays.copyOf(T[], int)читається легше. Внутрішньо він використовує System.arraycopy()місцевий дзвінок.

Ви не можете отримати це швидше!


здається, ви можете залежати від цілком кількох речей, але спасибі, що вказали на те, що я не знав і справді легше читати. :)
Дарен

так! це залежить від декількох речей, як сказав @Svetoslav Tsolov. я просто хотів вказати на Arrays.copyOf
Philipp Sander

2
copyOfне завжди може замінити arraycopy, але це підходить для цього випадку використання.
Blaisorblade

1
Примітка. Якщо ви дивитесь на продуктивність, це не буде так швидко, як System.arraycopy (), оскільки вимагає виділення пам'яті. Якщо це відбувається в циклі, то повторне розподіл призведе до збору сміття, що стане масовим хітом продуктивності.
Уілл Калдервуд,

1
@PhilippSander Просто для того, щоб перевірити, що я не дурний, я додав код до копії масиву розміром 1 Мб у моєму ігровому циклі, який майже ніколи не запускає GC. За допомогою Array.copyOf () мій DVM дзвонив GC 5 разів на секунду, і гра стала надзвичайно відсталою. Я думаю, можна з упевненістю сказати, що відбувається розподіл пам’яті.
Уілл Калдервуд

17

Це залежить від віртуальної машини, але System.arraycopy повинен надати вам найближчі результати, які ви можете досягти.

Я працював 2 роки розробником Java для вбудованих систем (де продуктивність має величезний пріоритет), і скрізь можна було використовувати System.arraycopy, я в основному використовував її / бачив, як вона використовується в існуючому коді. Це завжди краще, ніж цикли, коли продуктивність є проблемою. Якщо продуктивність не є серйозною проблемою, я б пішов із циклами. Читати набагато легше.


Такі речі, як розмір та тип масиву (базовий проти успадкованого), схоже, впливають на попередню підготовку.
Дарен

2
Так, це не "рідна продуктивність" сама по собі, саме тому я сказав, що "переважно" використовую її там, де можу (ви помітите, що вона в основному виграє при копіюванні циклу). Я думаю, причина в тому: коли це невеликий масив примітивного типу, "вартість дзвінка" більша, ніж підвищення продуктивності. Використання JNI може погіршити продуктивність з тієї ж причини - власний код сам по собі швидкий, але виклик його з Java-процесу - не настільки.

Незначні виправлення, Arrays.copy - це не JNI, це внутрішнє значення. Власність набагато швидша, ніж JNI. Як і коли JIT-компілятор перетворює його на внутрішній, залежить від використовуваного JVM / компілятора.
Ніцан Вакарт,

1
Arrays.copyне існує, Arrays.copyOfє функцією бібліотеки.
Blaisorblade

12

Замість того, щоб покладатися на спекуляції та, можливо, застарілу інформацію, я провів деякі тести, використовуючи . Насправді, Caliper містить кілька прикладів, зокрема, CopyArrayBenchmarkщо вимірює саме це питання! Все, що вам потрібно зробити - це бігти

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

Мої результати базуються на 64-бітній серверній машині Java HotSpot (TM) Java Oracle, 1.8.0_31-b13, що працює на MacBook Pro середини 2010 року (macOS 10.11.6 з Intel Arrandale i7, 8 ГіБ оперативної пам'яті). Я не вважаю, що корисно розміщувати необроблені дані часу. Швидше, я підсумую висновки з допоміжними візуалізаціями.

Підсумовуючи:

  • Написання ручного 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? Дивно, що ця масивна копія буде повільною на островах у великих масштабах.
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 [], ви можете спробувати його за допомогою рядка [] (ініціалізований різними значеннями: "1", "2" тощо, оскільки вони незмінні
Дарен,

1
JNI дуже повільний . System.arraycopyне використовує його.
Chai T. Rex,

Ні, System.arraycopyне використовує JNI, який призначений лише для виклику сторонніх бібліотек. Натомість це власний виклик, а це означає, що у ВМ для нього є власна реалізація.
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()це власний виклик, який виконує операцію копіювання безпосередньо в пам'ять. Одномісна копія пам’яті завжди буде швидшою, ніж цикл for


3
Я читав, що для не рідних типів (будь-який створений клас, такий як мій) це може бути не настільки ефективно ... і для розмірів невеликих розмірів (у моєму випадку) керівництво для циклу може бути кращим ... доглядати за коментарем?
Дарен

Дійсно, System.arraycopy () має деякі накладні витрати, тому для малих масивів (n = ~ 10) цикл насправді швидший
RecursiveExceptionException
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.