Чому System.arraycopy є рідною мовою Java?


84

Я був здивований, побачивши у джерелі Java, що System.arraycopy є рідним методом.

Звичайно, причина в тому, що це швидше. Але які рідні хитрощі може використовувати код, що робить його швидшим?

Чому б просто не прокрутити вихідний масив і не скопіювати кожен вказівник на новий масив - безумовно, це не так повільно і громіздко?

Відповіді:


82

У власному коді це можна зробити за допомогою одного memcpy/ memmove, на відміну від n окремих операцій копіювання. Різниця у продуктивності суттєва.


@Peter, тож у межах власного коду ти можеш користуватися моделлю пам'яті Java? (Я ніколи не мав жодної причини робити будь-яку рідну малярку)
Джеймс Б,

8
Насправді, лише деякі підслужби arraycopyможуть бути реалізовані за допомогою memcpy/ memmove. Інші вимагають перевірки типу виконання для кожного скопійованого елемента.
Stephen C

1
@Stephen C, цікаво - чому це?
Péter Török

3
@ Péter Török - розгляньте можливість копіювання із Object[]заповненого Stringоб’єктами на a String[]. Дивіться останній абзац java.sun.com/javase/6/docs/api/java/lang/…
Стівен С

3
Пітер, Object [] та байт [] + char [] є найбільш часто копіюваними, жоден з них не вимагає явної перевірки типу. Компілятор досить розумний, щоб НЕ перевіряти, якщо це не потрібно, і практично в 99,9% випадків це не так. Забавна частина - невеликі за розміром копії (менше рядка кеш-пам'яті) є домінуючими, тому "memcpy" для швидкого використання невеликих розмірів справді важливо.
bestsss

16

Це неможливо написати на Java. Власний код здатний ігнорувати або видаляти різницю між масивами Object та масивами примітивів. Java не може цього зробити, принаймні не ефективно.

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


5
Чудово, так memmoveтоді. Хоча я не думаю, що це сильно впливає на контекст цього питання.
Péter Török

Також не memmove (), див. Коментарі @Stephen C до іншої відповіді.
Маркіз Лорнський,

Це вже бачив, оскільки це випадково була моєю власною відповіддю ;-) Але все одно дякую.
Péter Török

1
@Geek Масиви, які перекриваються. Якщо масиви джерела та цілі та однакові та лише зміщення відрізняються, поведінка ретельно визначається, і memcpy () не відповідає.
Маркіз Лорнський,

1
Це неможливо написати на Java? Хіба не можна написати один загальний метод для обробки підкласів Object, а потім один для кожного з примітивних типів?
Michael Dorst

10

Це, звичайно, залежить від реалізації.

HotSpot розгляне це як "власне" і вставить код на сайт дзвінка. Це машинний код, а не повільний старий код С. Це також означає, що проблеми з підписом методу здебільшого зникають.

Простий цикл копіювання досить простий, щоб до нього можна було застосувати очевидні оптимізації. Наприклад, розгортання циклу. Саме те, що відбувається, знову залежить від реалізації.


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

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

9
Незважаючи на те, що це запитання старе, лише для запису: це НЕ справедливий орієнтир (не кажучи вже про питання, чи мав би такий орієнтир насамперед сенс). System.arraycopyробить неглибоку копію (копіюються лише посилання на внутрішні float[]s), тоді як вкладені forцикли виконують глибоку копію ( floatby float). Зміна до fooCpy[i][j]буде відображено у fooвикористанні System.arraycopy, але не використовуватиме вкладених forциклів.
misberner

4

Є кілька причин:

  1. JIT навряд чи створить такий ефективний низькорівневий код, як написаний вручну код C. Використання низького рівня С може забезпечити багато оптимізацій, які майже неможливо зробити для загального компілятора JIT.

    Перегляньте це посилання, щоб переглянути деякі хитрощі та порівняння швидкості рукописних реалізацій C (memcpy, але принцип той самий): Перевірте це Оптимізація Memcpy покращує швидкість

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


1
Код Java може бути оптимізований. Насправді те, що насправді відбувається, - це генерується машинний код, який є більш ефективним, ніж C.
Том Хоутін - таклін

Я згоден з тим, що іноді JITed-код буде краще локально оптимізований, оскільки він знає, на якому процесорі він працює. Однак, оскільки це "вчасно", він ніколи не зможе використовувати всі ті нелокальні оптимізації, на виконання яких потрібно більше часу. Крім того, він ніколи не зможе відповідати ручно створеному коду С (що також може враховувати процесор і частково заперечувати переваги JIT, або шляхом складання для конкретного процесора, або шляхом перевірки виконання).
Hrvoje Prgeša

1
Я думаю, що команда укладачів Sun JIT заперечувала б багато з цих пунктів. Наприклад, я вважаю, що HotSpot робить глобальну оптимізацію для видалення непотрібної диспетчеризації методів, і немає жодної причини, чому JIT не може генерувати специфічний код процесора. Тоді справа полягає в тому, що компілятор JIT може виконати оптимізацію гілок на основі поведінки виконання поточного запуску програми.
Стівен С

@Stephen C - чудовий момент щодо оптимізації гілок, хоча ви також можете виконати статичне профілювання продуктивності за допомогою компіляторів C / C ++, щоб досягти подібного ефекту. Я також думаю, що точка доступу має 2 режими роботи - настільні програми не будуть використовувати всі доступні оптимізації для досягнення розумного часу запуску, тоді як серверні програми будуть оптимізовані більш агресивно. Загалом, ви отримуєте деякі переваги, але також втрачаєте деякі.
Hrvoje Prgeša

1
System.arrayCopy не реалізовано за допомогою C, що начебто робить цю відповідь недійсною
Ніцан Вакарт,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.