Производительность памяти Java VM. Является ли Array быстрее, чем считывает Array?
Я выполнил короткий тест на длинном массиве в java с довольно странными результатами. Кажется, что последовательные чтения со случайными записями быстрее - в полтора раза - чем случайные чтения с последовательной записью. Кто-нибудь подсказывает, почему?
Вот два метода, которые записывают массив из нескольких длин (запускается с -Xmx2G или около того) случайным образом, когда вы читаете последовательно и читаете последовательно при записи случайным образом:
import java.util.Random;
public class Scratch {
static Random random = new Random();
static long[] arr = new long[100000000];
static void seqReadRandWrite() {
for(int i=0;i<arr.length;i++) {
int at = random.nextInt(arr.length);
arr[at] = arr[i];
}
}
static void seqWriteRandRead() {
for(int i=0;i<arr.length;i++) {
int at = random.nextInt(arr.length);
arr[i] = arr[at];
}
}
public static void main(String[] args) throws Exception {
seqWriteRandRead(); // warm up
long nanos = System.nanoTime();
seqReadRandWrite();
System.out.println("Time: " + (System.nanoTime()-nanos) + "ns");
nanos = System.nanoTime();
seqWriteRandRead();
System.out.println("Time: " + (System.nanoTime()-nanos) + "ns");
}
}
результаты на моем ноутбуке
Время: 2774662168ns
Время: 6059499068ns
Это означает, что он в два раза быстрее записывается случайным образом по сравнению с чтением.. или? Разве мой ноутбук сломан?
ps: это не претендует на то, чтобы быть эталоном, хотя большинство пунктов в связанных советах относительно бенчмаркинга охвачены. Даже если я запускаю уже 200 000 000 операций несколько раз, рестаты остаются довольно постоянными. Кажется (кажется!), Что перемещение памяти из случайных позиций в последовательные блоки происходит медленнее, чем перемещение памяти из последовательных позиций в случайные блоки, по крайней мере, с памятью этого размера и вышеописанным способом ее выполнения. и мне интересно, почему?
Ответы
Ответ 1
В вашем тесте есть цифры, которые не соответствуют "Они имеют смысл?" контрольная работа. В такой ситуации вы всегда должны удвоить/утроить/четверть проверить свою методологию... ПЕРЕД обработкой чисел как истинным отражением реальности.
Написание надежных тестов сложно. И в случае с Java это особенно сложно, поскольку некоторые аспекты платформы Java могут вводить систематические искажения в ваши контрольные измерения... если вы специально не разрешаете/не компенсируете их.
Но правило "проверить свою методологию" применяется ко всем экспериментам... особенно тем, которые дают результаты, которые, похоже, не имеют смысла. (Как нейтрино, движущиеся быстрее света...)
Другое дело, что после того, как вы перепишете контрольный показатель для учета смешающих факторов, вы все равно можете увидеть неожиданные цифры. Проблема в том, что производительность таких тестов, скорее всего, будет чувствительна к таким вещам, как размер кэшей L1 и L2, размер строк кэша, относительная скорость разных уровней памяти... и их взаимодействие с точными последовательностями инструкции, которые эталонный результат производит в узких петлях.
Эти вещи сложны, трудно анализируются и могут давать противоречивое поведение. И неудивительно (для меня), что разные машины дают разные измеренные характеристики.
Таким образом, даже если цифры реальны, по-прежнему небезопасно делать какие-либо общие выводы о скорости чтения и записи из этого теста. Даже если вы ограничиваете их только своим ноутбуком.
Ответ 2
Таким образом, заголовок вопроса немного неверен. По-видимому, истина заключается в том, что в некоторых средах (например, my и OP) случайные массивы записываются быстрее, чем случайный массив читает. Но обратите внимание, что это не относится к некоторым другим людям.
Основываясь на @JustinKSU comment, я отделил чтение и запись и обнаружил, что случайные записи быстрее, чем случайные. Результаты приведены ниже. Похоже, что это причина, и коллективное мнение здесь похоже на то, что прочтение пропусков на кеше более дорогое, чем пропуски с записью (если вообще есть кеширование, связанное с записью).
В производстве, хотя там, где есть другая деятельность, горячая точка может сыграть свою роль.
/cygdrive/c/Java/jdk1.7.0/bin/javac.exe Scratch.java && /cygdrive/c/Java/jdk1.7.0/bin/java Scratch
Starting
seqRead: 1273719725ns
seqRead: 1243055271ns
seqRead: 1245022497ns
seqRead: 1242868527ns
seqRead: 1241655611ns
randRead: 6900959912ns
randRead: 6965196004ns
randRead: 7379623094ns
randRead: 7020390995ns
randRead: 6938997617ns
seqWrite: 1266963940ns
seqWrite: 1250599487ns
seqWrite: 1246471685ns
seqWrite: 1230472648ns
seqWrite: 1246975416ns
randWrite: 3898382192ns
randWrite: 3897441137ns
randWrite: 3939947844ns
randWrite: 4207906037ns
randWrite: 4103594207ns
Compilation finished at Thu Jan 31 14:38:57
Модифицированный код выглядит следующим образом:
import java.util.Random;
public class Scratch {
static Random random = new Random();
static long[] arr = new long[100000000];
static void seqReadRandWrite() {
for(int i=0;i<arr.length;i++) {
int at = Math.abs(random.nextInt() % arr.length);
arr[at] = arr[i];
}
}
static void seqWriteRandRead() {
for(int i=0;i<arr.length;i++) {
int at = Math.abs(random.nextInt() % arr.length);
arr[i] = arr[at];
}
}
static void seqRead() {
int x = 0;
for(int i=0;i<arr.length;i++) {
int at = Math.abs(random.nextInt() % arr.length);
x += arr[i];
}
}
static void randRead() {
int x = 0;
for(int i=0;i<arr.length;i++) {
int at = Math.abs(random.nextInt() % arr.length);
x += arr[at];
}
}
static void seqWrite() {
for(int i=0;i<arr.length;i++) {
int at = Math.abs(random.nextInt() % arr.length);
arr[i] = at;
}
}
static void randWrite() {
for(int i=0;i<arr.length;i++) {
int at = Math.abs(random.nextInt() % arr.length);
arr[at] = at;
}
}
public static void main(String[] args) throws Exception {
// seqWriteRandRead(); // warm up
System.out.println("Starting");
long nanos = -1;
/*
for (int i = 0; i < 5; i++) {
nanos = System.nanoTime();
seqWriteRandRead();
System.out.println("WriteRandRead Time: " + (System.nanoTime()-nanos) + "ns");
nanos = System.nanoTime();
seqReadRandWrite();
System.out.println("ReadRandWrite Time: " + (System.nanoTime()-nanos) + "ns");
}
*/
for (int i = 0; i < 5; i++) {
nanos = System.nanoTime();
seqRead();
System.out.println("seqRead: " + (System.nanoTime()-nanos) + "ns");
}
for (int i = 0; i < 5; i++) {
nanos = System.nanoTime();
randRead();
System.out.println("randRead: " + (System.nanoTime()-nanos) + "ns");
}
for (int i = 0; i < 5; i++) {
nanos = System.nanoTime();
seqWrite();
System.out.println("seqWrite: " + (System.nanoTime()-nanos) + "ns");
}
for (int i = 0; i < 5; i++) {
nanos = System.nanoTime();
randWrite();
System.out.println("randWrite: " + (System.nanoTime()-nanos) + "ns");
}
}
}
UPDATE
@tomcarchrae сделал тот же тест на Linux, что значительно отличалось от результатов. Ниже, первый столбец - это номера из моего теста, а второй - от Tom's:
seqRead: 1273719725ns 2810487542ns
seqRead: 1243055271ns 2780504580ns
seqRead: 1245022497ns 2746663894ns
seqRead: 1242868527ns 2746094469ns
seqRead: 1241655611ns 2763107970ns
randRead: 6900959912ns 23093543703ns
randRead: 6965196004ns 22458781637ns
randRead: 7379623094ns 24421031646ns
randRead: 7020390995ns 25880250599ns
randRead: 6938997617ns 26873823898ns
seqWrite: 1266963940ns 4226886722ns
seqWrite: 1250599487ns 4537680602ns
seqWrite: 1246471685ns 3880372295ns
seqWrite: 1230472648ns 4160499114ns
seqWrite: 1246975416ns 4008607447ns
randWrite: 3898382192ns 25985349107ns
randWrite: 3897441137ns 22259835568ns
randWrite: 3939947844ns 22556465742ns
randWrite: 4207906037ns 22143959163ns
randWrite: 4103594207ns 21737397817ns
Ответ 3
Я считаю, что этот тест абсолютно бесполезен для вас. Существует множество параметров измерений, которые вы не описали, и то, как вы приближаетесь к этой проблеме, полностью не описано. Чтобы вообще сделать вывод о скорости реализации виртуальных машин, компьютеров, скорости ОЗУ, программного обеспечения, которое вы обрабатываете одновременно, типа объектов или простых вещей, которые вы копируете, и т.д., Вы должны узнать о методическом методе. Этот вопрос не подлежит обсуждению. Вы должны сузить, какие конкретные обстоятельства вы хотите знать о скорости.
В частности, вы не можете делать никаких заключений при использовании случайных чисел. Это значительно увеличивает проблему наилучшего, худшего или среднего случая сложности.
Пожалуйста, проверьте сложность алгоритмов, затем продолжите поиск, как сделать научные измерения производительности во время выполнения. Надеюсь, я немного помогу.
Этот первый ответ потрясающий и поможет вам понять. Как написать правильный микро-тест в Java?
С уважением,
Ответ 4
Ответ в предыдущих комментариях и сводится к эффектам доступа к памяти. Этот блог post описывает эффекты случайных чтений. Писания не страдают аналогичным образом.
Это не проблема Java (или даже проблема с языком), а реальность аппаратного обеспечения, на котором вы работаете (и общая реальность). Это не значит, что вы должны его игнорировать! Хотя ваш первоначальный тест, возможно, был испорчен, он по-прежнему попадает в реальную проблему для некоторого программного обеспечения, поэтому при этом это ценный урок.
Вывод заключается не в том, что чтение стоит дороже, чем пишет. Это то, что случайный доступ к памяти плохо обслуживается аппаратным обеспечением. В основном это связано с тем, что производительность LinkedList намного хуже, чем ArrayList для последовательного доступа, они имеют одинаковую вычислительную сложность, но доступ к массиву воспроизводится с помощью аппаратной силы, где нет связанного списка.
Ответ 5
Ваш эксперимент сломан, а не ваш ноутбук. См. Здесь для обсуждения и некоторые инструменты, которые помогут измерить производительность: Библиотека синхронизации производительности Java
Ниже приведены некоторые результаты, которые заключают контракт с вами. Также я изменил ваш код, чтобы быть более строгим и осторожным в том, как он измеряет.
Моя среда - Linux (Mint 14, основанный на Ubuntu 12.10) с использованием Sun JDK 1.6.0_38
С 1.5G кучи для большого примера, т.е. -Xmx1512
Примечание: интересно. Может быть, мой результат отличается от другого, потому что размер массива ниже. Будет повторно запускаться и обновляться.
Нет: результат аналогичен, в среднем нет большой разницы. Но интереснее отличие от короткого пробега, то есть 21092.5 (/10 = 2109.2) против 1645.2, что может быть медленнее из-за пейджинга в памяти.
результат с static long[] arr = new long[100000000];
(исходный размер массива)
Write: DescriptiveStatistics: n: 10 min: 20893.0 max: 22190.0 среднее значение: 21092.5 std dev: 390.90727800848117 медиана: 20953,5 асимметрия: 3.0092198852491543 kurtosis: 9.264808973899097
Читать: DescriptiveStatistics: n: 10 min: 21668.0 max: 22736.0 среднее значение: 21892.5 std dev: 318.31509546359877 медиана: 21766,5 асимметрия: 2.5034216544466124 куртоз: 6.560838306717343
Я не вижу огромной разницы в чтении и записи. Я изменил эксперимент, чтобы измерить 10 раз на немного меньшем массиве (результат - такое же количество чтения/записи). Не забудьте повторно запустить с большим размером массива или размером выборки.
Write: DescriptiveStatistics: n: 10 min: 1584.0 max: 1799.0 среднее значение: 1645.2 std dev: 59.51619760853156 медиана: 1634,5 асимметрия: 2.137918517160786 куртоз: 5.764166551997385
Читать: DescriptiveStatistics: n: 10 мин: 1568.0 макс: 2202.0 среднее значение: 1689.0 std dev: 186.93908693000031 медиана: 1623,0 асимметрия: 2.770215113912315 kurtosis: 8.12245132320571
Ниже приведена измененная версия вашего кода, в которой больше образцов:
import java.util.Random;
import org.apache.commons.lang.time.StopWatch;
import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
public class Test {
static Random random = new Random();
// static long[] arr = new long[100000000];
static long[] arr = new long[10000000];
static void seqReadRandWrite() {
for (int i = 0; i < arr.length; i++) {
int at = Math.abs(random.nextInt()) % arr.length;
arr[at] = arr[i];
}
}
static void seqWriteRandRead() {
for (int i = 0; i < arr.length; i++) {
int at = Math.abs(random.nextInt()) % arr.length;
arr[i] = arr[at];
}
}
public static void main(String[] args) throws Exception {
StopWatch timer = new StopWatch();
int count = 10;
// warm up
for (int i=0; i<3; i++){
seqReadRandWrite();
}
DescriptiveStatistics write = new DescriptiveStatistics();
for (int i=0; i<count; i++){
timer.reset();
timer.start();
seqReadRandWrite();
timer.stop();
write.addValue(timer.getTime());
}
System.out.println("Write: " + write);
// warm up
for (int i=0; i<3; i++){
seqWriteRandRead();
}
DescriptiveStatistics read = new DescriptiveStatistics();
for (int i=0; i<count; i++){
timer.reset();
timer.start();
seqWriteRandRead();
timer.stop();
read.addValue(timer.getTime());
}
System.out.println("Read: " + read);
}
}
Ответ 6
на моем ПК: (ns per r/w)
seq read : 1.4
rnd read : 10x.x
seq write: 3.3
rnd write: 10x.x
и seqReadRandWrite и seqWriteRandRead одинаково быстрые на 100 нс на цикл.
поэтому это может зависеть от аппаратного обеспечения. также настройки VM. попробуйте java -server
и посмотрите, улучшится ли скорость.