Является ли Java System.arraycopy() эффективным для небольших массивов?

Является ли Java System.arraycopy() эффективным для небольших массивов или же тот факт, что его собственный метод делает его, по-видимому, менее эффективным, чем простой цикл и вызов функции?

Собственные методы несут дополнительные накладные расходы на производительность для пересечения какого-либо моста Java-системы?

Ответ 1

Размножая немного на то, что написал Сид, очень вероятно, что System.arraycopy является только встроенным JIT; что при вызове кода System.arraycopy он скорее всего будет вызывать реализацию JIT-специфики (как только теги JIT System.arraycopy будут "горячими" ), которые не выполняются через интерфейс JNI, поэтому он не несет нормальные накладные расходы нативных методов.

В общем случае выполнение собственных методов имеет некоторые накладные расходы (через интерфейс JNI, также некоторые внутренние операции JVM не могут произойти при выполнении собственных методов). Но это не потому, что метод помечен как "native", который вы фактически выполняете с помощью JNI. JIT может делать некоторые сумасшедшие вещи.

Самый простой способ проверить, как было предложено, написать небольшой тест, быть осторожным с обычными оговорками Java-микрообъектов (сначала разогреть код, избегать кода без побочных эффектов, поскольку JIT просто оптимизирует его как no-op и т.д.).

Ответ 2

Вот мой тестовый код:

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

Из моих тестов вызов test() с помощью copyCount= 10000000 (1e7) или выше позволяет достичь разминки во время первого вызова copy/loop, поэтому достаточно использовать testRep= 5; При copyCount= 1000000 (1e6) для разминки требуется как минимум 2 или 3 итерации, поэтому testRep должно быть увеличено для получения полезных результатов.

С моей конфигурацией (процессор Intel Core 2 Duo E8500 @3,16 ГГц, Java SE 1.6.0_35-b10 и Eclipse 3.7.2), из таблицы видно, что:

  • Когда copySize= 24, System.arraycopy(), и ручной цикл принимает почти одно и то же время (иногда один из них немного быстрее, чем другой, а другой раз наоборот),
  • Когда copySize < 24, ручной цикл быстрее, чем System.arraycopy() (немного быстрее с copySize= 23, действительно быстрее с copySize < 5),
  • Когда copySize > 24, System.arraycopy() работает быстрее, чем ручная петля (немного быстрее с copySize= 25, увеличивается время цикла/время arraycopy с увеличением copySize).

Примечание: Я не владею английским языком, прошу извинить все ошибки грамматики/словаря.

Ответ 3

Это актуальная проблема. Например, в java.nio.DirectByteBuffer.put(byte[]) автор пытается избежать копирования JNI для небольшого количества элементов

// These numbers represent the point at which we have empirically
// determined that the average cost of a JNI call exceeds the expense
// of an element by element copy.  These numbers may change over time.
static final int JNI_COPY_TO_ARRAY_THRESHOLD   = 6;
static final int JNI_COPY_FROM_ARRAY_THRESHOLD = 6;

Для System.arraycopy() мы можем изучить, как использует JDK. Например, в ArrayList всегда используется System.arraycopy(), а не "копирование по элементам" независимо от длины (даже если это 0). Поскольку ArrayList обладает высокой производительностью, мы можем получить, что System.arraycopy() является наиболее эффективным способом копирования массивов независимо от длины.

Ответ 4

System.arraycopy используйте операцию memmove для перемещения слов и сборки для перемещения других примитивных типов в C за сценой. Поэтому он прилагает все усилия, чтобы двигаться настолько эффективно, насколько это возможно.

Ответ 5

Байт-коды выполняются изначально, так что вероятность того, что производительность будет лучше, чем цикл.

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

Ответ 6

Нативные функции должны быть быстрее, чем функции JVM, поскольку накладные расходы VM отсутствуют. Однако для большого количества ( > 1000) очень малых (len < 10) массивов это может быть медленнее.