2013-02-13 4 views
2

Я только что сделал сравнительный тест для сравнения производительности локальных переменных, переменных-членов, переменных-членов других объектов и наборов-получателей. Эталонный показатель увеличивает переменную в цикле с 10-миллионными итерациями. Вот выход:Производительность переменной доступа для Android (Dalvik)

ЭТАЛОН: местный 101, член +1697, иностранный член 151, геттер сеттер 268

Это было сделано на планшете Motorola Xoom и Android 3.2. Цифры - миллисекунды времени выполнения. Может ли кто-нибудь объяснить отклонение переменной-члена мне? Особенно по сравнению с переменными-членами другого объекта. На основе этих цифр представляется целесообразным копировать переменные-члены в локальные переменные перед использованием их значений в вычислениях. Кстати, я сделал тот же тест на HTC One X и Android 4.1, и он показал те же отклонения.

Являются ли эти цифры разумными или есть систематическая ошибка, которую я пропустил?

Вот тест функции:

private int mID; 

public void testMemberAccess() { 
    // compare access times for local variables, members, members of other classes 
    // and getter/setter functions 
    final int numIterations = 10000000; 
    final Item item = new Item(); 
    int i = 0; 

    long start = SystemClock.elapsedRealtime(); 
    for (int k = 0; k < numIterations; k++) { 
     mID++; 
    } 
    long member = SystemClock.elapsedRealtime() - start; 

    start = SystemClock.elapsedRealtime(); 
    for (int k = 0; k < numIterations; k++) { 
     item.mID++; 
    } 
    long foreignMember = SystemClock.elapsedRealtime() - start; 

    start = SystemClock.elapsedRealtime(); 
    for (int k = 0; k < numIterations; k++) { 
     item.setID(item.getID() + 1); 

    } 
    long getterSetter = SystemClock.elapsedRealtime() - start; 

    start = SystemClock.elapsedRealtime(); 
    for (int k = 0; k < numIterations; k++) { 
     i++; 
    } 
    long local = SystemClock.elapsedRealtime() - start; 

    // just make sure nothing loops aren't optimized away? 
    final int dummy = item.mID + i + mID; 
    Log.d(Game.ENGINE_NAME, String.format("BENCHMARK: local %d, member %d, foreign member %d, getter setter %d, dummy %d", 
      local, member, foreignMember, getterSetter, dummy)); 
} 

Edit:
Я ставлю каждый цикл в функции и назвал их 100 раз в случайном порядке. Результат: BENCHMARK: местный 100, участник 168, иностранный член 190, геттер сеттер 271 Выглядит хорошо, thx. Посторонний объект был создан как конечный член класса, а не внутри функций.

+1

Вы должны запускать каждый тест по-своему, чтобы избежать помех оптимизации, и вы должны попытаться изменить порядок их запуска. В идеале вы также должны контролировать GC. – assylias

+0

+1 Хороший вопрос! Заставляет вас думать. –

+0

Умный компилятор может устранить цикл и заменить его на 'i + = numIterations'. Это может даже распространиться на геттеры/сеттеры, объявленные в одном классе. Я подозреваю, что в основном это делается, чтобы сделать такие тесты, как этот отчет, абсурдно хорошими. :-) Одной из причин использования большого количества итераций является то, что JIT ничего не скомпилирует, пока код не станет «горячим», т. Е. Он много работает; вы должны, вероятно, запустить весь этот метод дважды. На вас также могут влиять настройки частоты процессора (для экономии энергии или терморегулирования). – fadden

ответ

1

Ну, я бы сказал, что оптимизатор Dalvik VM очень умный ;-) Я знаю, что Dalvik VM основана на регистрации. Я не знаю, внутренностей Dalvik VM, но я предположил бы, что следующее происходят (более или менее):

В местном случае вы инкремент локальной переменной методы внутри цикла. Оптимизатор распознает, что эта переменная недоступна до тех пор, пока цикл не будет завершен, поэтому вы можете использовать регистр и применять приращения там до тех пор, пока цикл не будет завершен, а затем сохранит значение обратно в локальную переменную. Это дает: 1 выборку, шаг регистрации 10000000 и 1 магазин.

В корпусе вы увеличиваете переменную-член в цикле. Оптимизатор не может определить, будет ли доступ к переменной-члену во время цикла (другим методом, объектом или потоком), поэтому он вынужден извлекать, увеличивать и сохранять значение обратно в переменную-член на каждой итерации цикла. Это дает: 10000000 выборок, 10000000 приращений и 10000000 операций магазина.

В случае внешний член случай, вы увеличиваете переменную-член объекта внутри цикла. Вы создали этот объект внутри метода. Оптимизатор распознает, что к этому объекту нельзя получить доступ (другим объектом, методом или потоком) до тех пор, пока цикл не будет завершен, поэтому он может использовать регистр и применять приращения там до тех пор, пока цикл не будет завершен, а затем сохранит значение обратно в переменную-член , Это дает: 1 выборку, шаг регистрации 10000000 и 1 магазин.

В геттер/сеттер случае, я буду считать, что компилятор и/или оптимизатор достаточно умен, чтобы «инлайн» геттер/сеттеры (т.е. это не реально сделать вызов метода - он заменяет item.setID(item.getID() + 1) с item.mID = item.mID + 1). Оптимизатор распознает, что вы увеличиваете переменную-член объекта внутри цикла. Вы создали этот объект внутри метода.Оптимизатор распознает, что к этому объекту нельзя получить доступ (другим объектом, методом или потоком) до тех пор, пока цикл не будет завершен, поэтому он может использовать регистр и применять приращения там до тех пор, пока цикл не будет завершен, а затем сохранит значение обратно в чужой элемент переменная. Это дает: 1 выборку, шаг регистрации 10000000 и 1 магазин.

Я не могу объяснить, почему геттер/сеттер времени в два раза иностранный член времени, но это может быть из-за времени, которое требуется оптимизатор, чтобы понять это, или что-то еще.

Интересным тестом было бы перемещение создания постороннего объекта из метода и выяснение, что это что-то изменит. Попробуйте переместить эту строку:

final Item item = new Item(); 

вне метода (т. Е. Вместо этого объявить его как частную переменную-член какого-либо объекта). Я бы предположил, что производительность будет намного хуже.

Отказ от ответственности: Я не инженер Dalvik.

+0

Отличный ответ! – slezica

0

Помимо изменения их порядка, есть и другие вещи, которые вы можете сделать для того, чтобы попытаться устранить любые помехи:

1 Исключите пограничный эффект, вычисляя первый пункт второй раз; предпочтительно с использованием другой длинной переменной.

2- Увеличьте количество итераций на 10. 1000000 кажется большим числом, но, как вы можете видеть из первого предложения; увеличение переменной 1 миллион раз так быстро на современном процессоре, что многие другие вещи, такие как заполнение различных кешей, играют важную роль.

3- Добавить ложные инструкции, такие как вставка манекена long l = SystemClock.elapsedRealtime()-start. Это поможет показать, что это 1000000 итераций действительно небольшое число.

4- Добавить ключевое слово volatile в поле mID. Вероятно, это лучший способ учесть любую оптимизацию, связанную с компилятором или процессором.

+0

Re # 4, 'volatile' несет намного больше багажа в Java, чем в C/C++. На многоядерном устройстве, которое теперь доступно почти всем устройствам Android, виртуальной машине придется добавлять блокировки памяти процессора для каждого доступа. (См. Http://developer.android.com/training/articles/smp.html.) – fadden

Смежные вопросы