Ответ 1
Я провел некоторое расследование. Не существует законного способа создания неинициализированного массива в Java. Даже JNI NewXxxArray создает инициализированные массивы. Таким образом, невозможно точно узнать стоимость обнуления массива. Тем не менее, я сделал некоторые измерения:
1) Создание 1000 байт-массивов с разным размером массива
long t0 = System.currentTimeMillis();
for(int i = 0; i < 1000; i++) {
// byte[] a1 = new byte[1];
byte[] a1 = new byte[1000000];
}
System.out.println(System.currentTimeMillis() - t0);
на моем ПК он дает < 1 мс для байта [1] и ~ 500 мс для байта [1000000]. Звучит впечатляюще.
2) У нас нет быстрого (родного) метода в JDK для заполнения массивов, Arrays.fill слишком медленный, поэтому давайте посмотрим, по крайней мере, сколько 1000 копий массива размером 1000 000 с помощью родной System.arraycopy
byte[] a1 = new byte[1000000];
byte[] a2 = new byte[1000000];
for(int i = 0; i < 1000; i++) {
System.arraycopy(a1, 0, a2, 0, 1000000);
}
Это 700 мс.
Это дает мне основания полагать, что a) создание длинных массивов дорогое b) это кажется дорогостоящим из-за бесполезной инициализации.
3) Возьмем sun.misc.Unsafe http://www.javasourcecode.org/html/open-source/jdk/jdk-6u23/sun/misc/Unsafe.html. Он защищен от внешнего использования, но не слишком много.
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe)f.get(null);
Вот стоимость теста на распределение памяти
for(int i = 0; i < 1000; i++) {
long m = u.allocateMemory(1000000);
}
Требуется < 1 мс, если вы помните, для нового байта [1000000] потребовалось 500 мс.
4) У Unsafe нет прямых методов работы с массивами. Он должен знать поля классов, но отражение не отображает полей в массиве. Внутри массивов мало информации, я полагаю, что это JVM/платформа. Тем не менее, это, как и любой другой заголовок + поля Java Object. На моем ПК /JVM это выглядит как
header - 8 bytes
int length - 4 bytes
long bufferAddress - 8 bytes
Теперь, используя Unsafe, я создам байт [10], выделим буфер с 10 байтами памяти и использую его как мои элементы массива:
byte[] a = new byte[10];
System.out.println(Arrays.toString(a));
long mem = unsafe.allocateMemory(10);
unsafe.putLong(a, 12, mem);
System.out.println(Arrays.toString(a));
он печатает
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[8, 15, -114, 24, 0, 0, 0, 0, 0, 0]
Вы можете видеть, что данные массива thay не инициализируются.
Теперь я изменю длину массива (хотя он все еще указывает на 10-байтовую память)
unsafe.putInt(a, 8, 1000000);
System.out.println(a.length);
показывает 1000000. Это было просто для того, чтобы доказать, что идея работает.
Теперь тест производительности. Я создам пустой массив байтов a1, распределяю буфер в 1000000 байт, присваиваю этот буфер a1 набору a1.length = 10000000
long t0 = System.currentTimeMillis();
for(int i = 0; i < 1000; i++) {
byte[] a1 = new byte[0];
long mem1 = unsafe.allocateMemory(1000000);
unsafe.putLong(a1, 12, mem);
unsafe.putInt(a1, 8, 1000000);
}
System.out.println(System.currentTimeMillis() - t0);
требуется 10 мс.
5) В С++ есть malloc и alloc, malloc просто выделяет блок памяти, calloc также инициализирует его нулями.
каст
...
JNIEXPORT void JNICALL Java_Test_malloc(JNIEnv *env, jobject obj, jint n) {
malloc(n);
}
java
private native static void malloc(int n);
for (int i = 0; i < 500; i++) {
malloc(1000000);
}
Результаты malloc - 78 мс; calloc - 468 мс
Выводы
- Кажется, что создание массива Java медленное из-за ненужного обнуления элемента.
-
Мы не можем изменить его, но Oracle может. Не нужно ничего менять в JLS, просто добавьте собственные методы в java.lang.reflect.Array, например
public static native xxx [] newUninitialziedXxxArray (int size);
для всех примитивных числовых типов (байт - двойной) и char. Его можно использовать по всему JDK, например, в java.util.Arrays
public static int[] copyOf(int[] original, int newLength) {
int[] copy = Array.newUninitializedIntArray(newLength);
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
...
или java.lang.String
public String concat(String str) {
...
char[] buf = Array.newUninitializedCharArray(count + otherLen);
getChars(0, count, buf, 0);
...