Лучше ли использовать 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. На самом деле, 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 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()
- это собственный вызов, который выполняет операцию копирования непосредственно в памяти. Копия одной копии всегда будет быстрее, чем ваш цикл