Лучше ли использовать System.arraycopy(...), чем цикл for для копирования массивов?

Я хочу создать новый массив объектов, объединяющий два меньших массива.

Они не могут быть нулевыми, но размер может быть 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, для выигрыша size > 24 system.arraycopy(), для менее 10, ручной для цикла лучше...

Теперь я действительно смущен.

Ответ 1

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.157s для завершения и
testArrayCopyBytes заняло 0.086s для завершения.

Я думаю, что это зависит от виртуальной машины, но похоже, что она копирует блоки памяти вместо копирования отдельных элементов массива. Это полностью увеличило бы производительность.

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

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

Вы не можете получить его быстрее!

Ответ 3

Это зависит от виртуальной машины, но System.arraycopy должен дать вам самое близкое, что вы можете получить от собственной производительности.

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

Ответ 4

Вместо того, чтобы полагаться на предположения и, возможно, устаревшую информацию, я провел несколько тестов, используя . На самом деле, Caliper поставляется с несколькими примерами, включая CopyArrayBenchmark который измеряет именно этот вопрос! Все, что вам нужно сделать, это бежать

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

Мои результаты основаны на 64-битной серверной виртуальной машине Oracle Java HotSpot (TM), 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) почти так же быстр, как Arrays.copyOf(array, array.length) и array.clone(), но не совсем так. (См. System.arraycopy() для 50000 int s.) Из-за этого и многословности вызова я бы порекомендовал System.arraycopy() если вам нужен точный контроль над тем, какие элементы куда копируются.

Вот графики времени:

Timings for copying arrays of length 5 Timings for copying arrays of length 500 Timings for copying arrays of length 50000

Ответ 5

Выполнение собственных методов, таких как 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) для копирования массива (или его частей), поэтому он невероятно быстрый, так как вы можете подтвердить здесь

Ответ 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;
}

Ответ 7

System.arraycopy() - это собственный вызов, который выполняет операцию копирования непосредственно в памяти. Копия одной копии всегда будет быстрее, чем ваш цикл