Почему System.arraycopy встроен в Java?

85

Я был удивлен, увидев в исходном коде Java, что System.arraycopy - это собственный метод.

Конечно, причина в том, что это быстрее. Но какие нативные уловки может использовать код, чтобы сделать его быстрее?

Почему бы просто не перебрать исходный массив и не скопировать каждый указатель в новый массив - конечно, это не так медленно и громоздко?

Джеймс Б.
источник

Ответы:

83

В машинном коде это можно сделать с помощью одного memcpy/ memmove, а не n отдельных операций копирования. Разница в производительности существенная.

Петер Торок
источник
8
На самом деле, только некоторые подслучаи arraycopyмогут быть реализованы с использованием memcpy/ memmove. Другие требуют проверки типа во время выполнения для каждого скопированного элемента.
Stephen C
1
@ Стивен С, интересно - почему?
Péter Török
3
@ Péter Török - рассмотрите возможность копирования из Object[]заполненного Stringобъектами в String[]. См. Последний абзац java.sun.com/javase/6/docs/api/java/lang/…
Стивен С.
3
Peter, Object [] и byte [] + char [] являются наиболее часто копируемыми, ни один из них не требует явной проверки типа. Компилятор достаточно умен, чтобы НЕ проверять без необходимости, и практически в 99,9% случаев это не так. Забавно то, что копии небольшого размера (меньше строки кэша) довольно доминируют, поэтому скорость memcpy для небольших файлов действительно важна.
bestsss
1
@jainilvachhani оба memcpyи memmoveявляются O (n), однако из-за оптимизаций fe simd они few timesбыстрее, поэтому вы можете сказать, что они O (n / x), где x зависит от оптимизаций, используемых в этих функциях
ufoq
16

Его нельзя написать на Java. Нативный код может игнорировать или исключать разницу между массивами Object и массивами примитивов. Java не может этого сделать, по крайней мере, неэффективно.

И это не может быть написано синглом memcpy()из-за семантики, необходимой для перекрывающихся массивов.

user207421
источник
5
Хорошо, memmoveтогда. Хотя я не думаю, что это имеет большое значение в контексте этого вопроса.
Péter Török
Не memmove (), см. Комментарии @Stephen C к другому ответу.
user207421 06
Я уже видел это, так как это был мой собственный ответ ;-) Но все равно спасибо.
Péter Török
1
@Geek Массивы, которые перекрываются. Если исходный и целевой массивы одинаковы и отличаются только смещения, поведение тщательно определяется, а memcpy () не соответствует.
user207421 01
1
Это не может быть написано на Java? Нельзя ли написать один общий метод для обработки подклассов Object, а затем по одному для каждого из примитивных типов?
Майкл Дорст
11

Это, конечно, зависит от реализации.

HotSpot будет рассматривать его как «внутренний» и вставлять код на сайте вызова. Это машинный код, а не медленный старый код C. Это также означает, что проблемы с сигнатурой метода в значительной степени исчезнут.

Простой цикл копирования достаточно прост, чтобы к нему можно было применить очевидные оптимизации. Например, разворачивание цикла. То, что происходит в точности, снова зависит от реализации.

Том Хотин - tackline
источник
2
это очень достойный ответ :), особенно. упоминание сущности. без них простая итерация могла бы быть быстрее, так как обычно она все равно разворачивается JIT
bestsss
4

В моих собственных тестах System.arraycopy () для копирования множественных массивов измерений в 10-20 раз быстрее, чем чередование циклов:

float[][] foo = mLoadMillionsOfPoints(); // result is a float[1200000][9]
float[][] fooCpy = new float[foo.length][foo[0].length];
long lTime = System.currentTimeMillis();
System.arraycopy(foo, 0, fooCpy, 0, foo.length);
System.out.println("native duration: " + (System.currentTimeMillis() - lTime) + " ms");
lTime = System.currentTimeMillis();

for (int i = 0; i < foo.length; i++)
{
    for (int j = 0; j < foo[0].length; j++)
    {
        fooCpy[i][j] = foo[i][j];
    }
}
System.out.println("System.arraycopy() duration: " + (System.currentTimeMillis() - lTime) + " ms");
for (int i = 0; i < foo.length; i++)
{
    for (int j = 0; j < foo[0].length; j++)
    {
        if (fooCpy[i][j] != foo[i][j])
        {
            System.err.println("ERROR at " + i + ", " + j);
        }
    }
}

Это печатает:

System.arraycopy() duration: 1 ms
loop duration: 16 ms
Jumar
источник
10
Несмотря на то, что это старый вопрос, просто для записи: это НЕ справедливый тест (не говоря уже о вопросе, имеет ли такой тест вообще смысл). System.arraycopyделает неглубокую копию (копируются только ссылки на внутренние float[]s), тогда как ваши вложенные for-loops выполняют глубокую копию ( floatby float). Изменение fooCpy[i][j]будет отражено в fooиспользовании System.arraycopy, но не будет использовать вложенные forциклы.
misberner
4

Причин несколько:

  1. JIT вряд ли создаст такой же эффективный код низкого уровня, как написанный вручную код C. Использование низкого уровня C может обеспечить множество оптимизаций, которые практически невозможно сделать для обычного JIT-компилятора.

    См. Эту ссылку для некоторых приемов и сравнения скорости рукописных реализаций C (memcpy, но принцип тот же): Отметьте это Оптимизация Memcpy улучшает скорость

  2. Версия C практически не зависит от типа и размера членов массива. Невозможно сделать то же самое в java, поскольку нет способа получить содержимое массива как необработанный блок памяти (например, указатель).

Hrvoje Prgeša
источник
1
Код Java можно оптимизировать. На самом деле происходит генерация машинного кода, который более эффективен, чем C.
Tom Hawtin - tackline
Я согласен с тем, что иногда JIT-код будет лучше оптимизировать локально, поскольку он знает, на каком процессоре он запущен. Однако, поскольку он «как раз вовремя», он никогда не сможет использовать все нелокальные оптимизации, выполнение которых требует больше времени. Кроме того, он никогда не сможет сопоставить созданный вручную код C (который также может учитывать процессор и частично сводить на нет преимущества JIT, либо путем компиляции для конкретного процессора, либо путем какой-либо проверки времени выполнения).
Hrvoje Prgeša
1
Я думаю, что команда компиляторов Sun JIT будет оспаривать многие из этих пунктов. Например, я считаю, что HotSpot выполняет глобальную оптимизацию, чтобы удалить ненужную диспетчеризацию методов, и нет причин, по которым JIT не может генерировать код для конкретного процессора. Затем есть момент, что JIT-компилятор может выполнять оптимизацию ветвей на основе поведения выполнения текущего запуска приложения.
Stephen C
@Stephen C - отличный отзыв об оптимизации ветвей, хотя вы также можете выполнить статическое профилирование производительности с компиляторами C / C ++ для достижения аналогичного эффекта. Я также думаю, что точка доступа имеет 2 режима работы - настольные приложения не будут использовать все доступные оптимизации для достижения разумного времени запуска, в то время как серверные приложения будут оптимизированы более агрессивно. В общем, вы получаете некоторые преимущества, но при этом теряете некоторые.
Hrvoje Prgeša
1
System.arrayCopy не реализован с использованием C, что в
некотором