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

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

Единственная разница во внешнем виде кода?

EDIT: спасибо за связанный вопрос, но, похоже, у них есть нерешенное обсуждение:

Действительно ли это быстрее, если it is not for native types: byte [], Object [], char []? во всех остальных случаях выполняется проверка типа, что было бы в моем случае и было бы эквивалентно ... нет?

По другому связанному вопросу они говорят, что the size matters a lotдля размера> 24 побеждает system.arraycopy (), для менее 10 лучше использовать ручной цикл ...

Теперь я очень запутался.

Дарен
источник
16
arraycopy()это родной вызов, который, безусловно, быстрее.
Сотириос Делиманолис
4
Вы пробовали тестировать две разные реализации?
Alex
5
Взгляните сюда: stackoverflow.com/questions/2772152/…
Сотириос Делиманолис
15
Вам следует выбрать тот, который вы считаете наиболее читаемым и легким в обслуживании в будущем. Только когда вы определили, что это источник узкого места, вам следует изменить свой подход.
arshajii 05
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

Как странно!

Спасибо, Дарен, за указание на то, что ссылки копируются по-другому. Это сделало эту задачу гораздо более интересной!

Трент Смолл
источник
2
Спасибо за ваши усилия, но вы пропустили, казалось бы, важные моменты: неродные типы (создайте случайный класс с anythign, чтобы массив содержал ссылки) и размер ... кажется, для меньшего размера массива, ручной цикл for выполняется быстрее. Хотите это исправить?
Дарен
2
Ого, ты прав! Это интересно. Размещение строк в этих массивах вместо байтов имеет огромное значение: <<< testHardCopyStrs: 0.161s >>> <<< testArrayCopyStrs: 0.170s >>>
Трент Смолл
Какие результаты вы получаете тогда? также интересно было бы попробовать с размером массива = 10 ... спасибо! (Хотел бы я иметь здесь свою IDE, я кодирую без компилятора).
Дарен
Что ж, теперь я обернул их в несколько вызовов System.nanoTime () и установил size = 10, чтобы увидеть, сколько наносекунд занимает каждый. Похоже, что для небольших примитивных массивов лучше использовать циклы; для ссылок лучше arrayCopy .: <<< testHardCopyBytes: 4491 нс >>> <<< testHardCopyStrs: 56778 нс >>> <<< testArrayCopyBytes: 10265 нс >>> <<< testArrayCopyStrs: 4490 нс >>>
Трент Small
Очень интересные результаты! Спасибо огромное! не могли бы вы отредактировать свой ответ, включив это, и я с радостью приму его, чтобы он остался первым, чтобы все увидели ... вы уже проголосовали за меня. :)
Дарен
36

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

Быстрее не получишь!

Филипп Сандер
источник
Кажется, вы можете полагаться на несколько вещей, но спасибо, что указали на эту функцию, о которой я не знал и которую действительно легче читать. :)
Дарен
да! как сказал @Svetoslav Tsolov, это зависит от многих вещей. Я просто хотел указать на Arrays.copyOf
Филипп Сандер
2
copyOfне всегда можно заменить arraycopy, но он подходит для этого варианта использования.
Blaisorblade
1
NB. Если вы смотрите на производительность, то это не будет так быстро, как System.arraycopy (), поскольку требует выделения памяти. Если это в цикле, то повторное выделение памяти приведет к сборке мусора, что существенно снизит производительность.
Уилл Колдервуд,
1
@PhilippSander Чтобы убедиться, что я не дурак, я добавил код для копирования массива размером 1 МБ в свой игровой цикл, который почти никогда не запускает сборщик мусора. С помощью Array.copyOf () мой DVM вызывал сборку мусора 5 раз в секунду, и игра стала очень медленной. Я думаю, можно с уверенностью сказать, что происходит выделение памяти.
Уилл Колдервуд,
17

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

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


источник
Такие вещи, как размер и тип массива (базовый или унаследованный), похоже, влияют на преформацию.
Дарен
2
Да, это не «родная производительность» как таковая, поэтому я сказал, что «в основном» использую ее там, где могу (вы заметите, что это в основном побеждает копирование цикла). Я предполагаю, что причина в том, что когда это небольшой массив примитивного типа, «стоимость звонка» больше, чем прирост производительности. Использование JNI может снизить производительность по той же причине - сам собственный код работает быстро, но вызов его из процесса java - не так уж и много.
Незначительное исправление, Arrays.copy - это не JNI, это внутреннее свойство. Встроенные функции намного быстрее, чем JNI. Как и когда JIT-компилятор превращает его во внутреннюю, зависит от используемой JVM / компилятора.
Nitsan Wakart
1
Arrays.copyне существует, Arrays.copyOfэто библиотечная функция.
Blaisorblade
14

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

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

Мои результаты основаны на 64-разрядной серверной виртуальной машине Oracle Java HotSpot ™, 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

200_success
источник
3
Есть что-то странное в копировании int? Кажется странным, что arraycopy будет медленным на 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) для копирования массива (или его частей), поэтому он невероятно быстр, как вы можете подтвердить здесь

Рахул Трипати
источник
В этом коде используется int []. Можете ли вы попробовать его с помощью String [] (инициализируется разными значениями: «1», «2» и т.д., поскольку они неизменяемы
Дарен
1
JNI очень медленный . System.arraycopyне использует его.
Чай Т. Рекс
Нет, 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;
}
DimaD
источник
4

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

Нандкумар Текале
источник
3
Я читал, что для неродных типов (любого созданного класса, такого как мой) это может быть не так эффективно ... а для небольших размеров (мой случай) руководство для цикла может быть лучше ... не забудьте прокомментировать?
Дарен
Действительно, System.arraycopy () имеет некоторые накладные расходы, поэтому для небольших массивов (n = ~ 10) цикл на самом деле быстрее
RecursiveExceptionException