Сравнительный анализ небольших массивов по сравнениюСписки на Java:Мой код бенчмаркинга неверен?
-
19-09-2019 - |
Вопрос
Отказ от ответственности: Я просмотрел через этот вопрос и этот вопрос но они оба потерпели неудачу из-за мелких деталей и общего ненужные опасения по поводу оптимизации.Мне действительно нужна вся производительность, которую я могу получить в моем текущем приложении, которое является получением-обработкой-отправкой MIDI-данных в режиме реального времени.Кроме того, его необходимо масштабировать настолько хорошо, насколько это возможно.
Я сравниваю array
производительность при большом количестве операций чтения для небольших списков, чтобы ArrayList
а также к тому, чтобы просто иметь переменные под рукой.Я нахожу, что массив превосходит ArrayList
в 2,5 раза и даже лучше, чем просто иметь ссылки на объекты.
Что я хотел бы знать, так это:
- Мой бенчмарк в порядке? Я изменил порядок тестов и количество запусков без изменений.Я также использовал миллисекунды вместо наносекунд, но безрезультатно.
- Должен ли я указывать какие-либо параметры Java, чтобы минимизировать эту разницу?
- Если эта разница реальна, то в данном случае разве я не должен предпочесть
Test[]
ДляArrayList<Test>
в этой ситуации и ввести код, необходимый для их преобразования? Очевидно, что я читаю гораздо больше, чем пишу.
JVM - это Java 1.6.0_17 на OSX, и она определенно работает в режиме горячей точки.
public class ArraysVsLists {
static int RUNS = 100000;
public static void main(String[] args) {
long t1;
long t2;
Test test1 = new Test();
test1.thing = (int)Math.round(100*Math.random());
Test test2 = new Test();
test2.thing = (int)Math.round(100*Math.random());
t1 = System.nanoTime();
for (int i=0; i<RUNS; i++) {
test1.changeThing(i);
test2.changeThing(i);
}
t2 = System.nanoTime();
System.out.println((t2-t1) + " How long NO collection");
ArrayList<Test> list = new ArrayList<Test>(1);
list.add(test1);
list.add(test2);
// tried this too: helps a tiny tiny bit
list.trimToSize();
t1= System.nanoTime();
for (int i=0; i<RUNS; i++) {
for (Test eachTest : list) {
eachTest.changeThing(i);
}
}
t2 = System.nanoTime();
System.out.println((t2-t1) + " How long collection");
Test[] array = new Test[2];
list.toArray(array);
t1= System.nanoTime();
for (int i=0; i<RUNS; i++) {
for (Test test : array) {
test.changeThing(i);
}
}
t2 = System.nanoTime();
System.out.println((t2-t1) + " How long array ");
}
}
class Test {
int thing;
int thing2;
public void changeThing(int addThis) {
thing2 = addThis + thing;
}
}
Решение
Микробенчмарки очень, очень сложно получить прямо на такой платформе, как Java.Вам определенно нужно выделить код для сравнительного анализа в отдельные методы, запустить их несколько тысяч раз в качестве прогрева, а затем измерить.Я сделал это (код ниже), и в результате прямой доступ через ссылки становится в три раза быстрее, чем через массив, но сбор данных все равно происходит медленнее в 2 раза.
Эти цифры основаны на параметрах JVM -server -XX:+DoEscapeAnalysis
.Без -server
, использование коллекции является радикально медленнее (но, как ни странно, прямой доступ и доступ к массиву выполняются немного быстрее, что указывает на то, что происходит что-то странное). -XX:+DoEscapeAnalysis
дает еще 30% ускорения для коллекции, но очень большой вопрос, будет ли это работать так же хорошо для вашего реального производственного кода.
В целом мой вывод был бы следующим:забудьте о микробенчмарках, они слишком легко могут ввести в заблуждение.Измерьте как можно ближе к производственному коду без необходимости переписывать все ваше приложение.
import java.util.ArrayList;
public class ArrayTest {
static int RUNS_INNER = 1000;
static int RUNS_WARMUP = 10000;
static int RUNS_OUTER = 100000;
public static void main(String[] args) {
long t1;
long t2;
Test test1 = new Test();
test1.thing = (int)Math.round(100*Math.random());
Test test2 = new Test();
test2.thing = (int)Math.round(100*Math.random());
for(int i=0; i<RUNS_WARMUP; i++)
{
testRefs(test1, test2);
}
t1 = System.nanoTime();
for(int i=0; i<RUNS_OUTER; i++)
{
testRefs(test1, test2);
}
t2 = System.nanoTime();
System.out.println((t2-t1)/1000000.0 + " How long NO collection");
ArrayList<Test> list = new ArrayList<Test>(1);
list.add(test1);
list.add(test2);
// tried this too: helps a tiny tiny bit
list.trimToSize();
for(int i=0; i<RUNS_WARMUP; i++)
{
testColl(list);
}
t1= System.nanoTime();
for(int i=0; i<RUNS_OUTER; i++)
{
testColl(list);
}
t2 = System.nanoTime();
System.out.println((t2-t1)/1000000.0 + " How long collection");
Test[] array = new Test[2];
list.toArray(array);
for(int i=0; i<RUNS_WARMUP; i++)
{
testArr(array);
}
t1= System.nanoTime();
for(int i=0; i<RUNS_OUTER; i++)
{
testArr(array);
}
t2 = System.nanoTime();
System.out.println((t2-t1)/1000000.0 + " How long array ");
}
private static void testArr(Test[] array)
{
for (int i=0; i<RUNS_INNER; i++) {
for (Test test : array) {
test.changeThing(i);
}
}
}
private static void testColl(ArrayList<Test> list)
{
for (int i=0; i<RUNS_INNER; i++) {
for (Test eachTest : list) {
eachTest.changeThing(i);
}
}
}
private static void testRefs(Test test1, Test test2)
{
for (int i=0; i<RUNS_INNER; i++) {
test1.changeThing(i);
test2.changeThing(i);
}
}
}
class Test {
int thing;
int thing2;
public void changeThing(int addThis) {
thing2 = addThis + thing;
}
}
Другие советы
Ваш бенчмарк действителен только в том случае, если ваш фактический вариант использования соответствует бенчмарковому коду, т. е.очень мало операций над каждым элементом, так что время выполнения в значительной степени определяется временем доступа, а не самими операциями.Если это так, то да, вы должны использовать массивы, если производительность критична.Однако, если ваш реальный вариант использования включает в себя гораздо больше фактических вычислений на элемент, то время доступа на элемент станет намного менее значительным.
Вероятно, это недействительно.Если я понимаю, как работают JIT-компиляторы, компиляция метода не повлияет на вызов этого метода, который уже выполняется.С тех пор как main
метод вызывается только один раз, в конечном итоге он будет интерпретирован, и поскольку большая часть работы выполняется в теле этого метода, полученные вами цифры не будут особенно свидетельствовать о нормальном выполнении.
Эффекты компиляции JIT могут каким-то образом объяснить, почему случай отсутствия коллекций был медленнее, чем случай с массивами.Этот результат противоречит интуиции и ставит под сомнение другой результат теста, о котором вы сообщили.