Подтвердить что ты не робот

Java: Getter и setter быстрее, чем прямой доступ?

Я тестировал производительность трассировщика Java-лучей, с которым я пишу VisualVM 1.3.7 на своем нетбуке Linux. Я измерил с помощью профилировщика.
Для удовольствия я тестировал, есть ли разница между использованием геттеров и сеттеров и прямым доступом к полям. Геттеры и сеттеры являются стандартным кодом без добавления.

Я не ожидал никаких различий. Но код прямого доступа был медленнее.

Здесь образец, который я тестировал в Vector3D:

public float dot(Vector3D other) {
    return x * other.x + y * other.y + z * other.z;
}

Время: 1542 мс /1 000 000 вызовов

public float dot(Vector3D other) {
    return getX() * other.getX() + getY() * other.getY() + getZ() * other.getZ();
}

Время: 1453 мс /1 000 000 вызовов

Я не тестировал его в микро-бенчмарке, но в лучей. Как я протестировал код:

  • Я начал программу с первого кода и настроил его. Трассировщик луча еще не работает.
  • Я начал профайлер и подождал некоторое время после того, как была выполнена инициализация.
  • Я запустил трассировщик лучей.
  • Когда VisualVM показал достаточно вызовов, я остановил профайлер и немного подождал.
  • Я закрыл программу трассировки луча.
  • Я заменил первый код вторым и повторил описанные выше шаги после компиляции.

Я выполнил хотя бы 20 000 000 вызовов для обоих кодов. Я закрыл любую программу, в которой я не нуждался. Я установил свой процессор на производительность, поэтому мои часы процессора были максимальными. все время.
Как возможно, что второй код на 6% быстрее?

4b9b3361

Ответ 1

Спасибо, что помогли мне ответить на этот вопрос. В конце концов, я нашел ответ.

Во-первых, Богемский прав: С PrintAssembly Я проверил предположение что сгенерированные коды сборки идентичны. И да, хотя байт-коды различны, сгенерированные коды идентичны.
Итак masterxilo прав: Профилировщик должен быть виновником. Но masterxilo догадывается о временных заборах и более инструментальном коде не может быть правдой; оба кода идентичны в конце.

Итак, есть еще вопрос: как возможно, что второй код, кажется, на 6% быстрее в профилировщике?

Ответ заключается в том, как измеряется VisualVM: Перед началом профилирования вам нужны данные калибровки. Это используется для удаления служебных данных вызванный профилировщиком.
Хотя данные калибровки верны, окончательный расчет измерения - нет. VisualVM видит вызовы метода в байт-коде. Но он не видит, что компилятор JIT удаляет эти вызовы при оптимизации.
Таким образом, он удаляет несуществующие накладные расходы. И вот как выглядит разница.

Ответ 2

Я сделал несколько микро-бенчмаркинга с большим количеством разогрева JVM и нашел , что два подхода занимают одинаковое количество времени выполнения.

Это происходит потому, что компилятор JIT встроил метод getter с прямым доступом к полю, что делает их идентичным байт-кодом.

Ответ 3

Если вы не прошли курс статистики, всегда есть разница в производительности программы независимо от того, насколько хорошо он написан. Причина, по которой эти два метода работают примерно с одинаковой скоростью, состоит в том, что поля аксессуаров делают только одно: они возвращают определенное поле. Поскольку в методе доступа больше ничего не происходит, обе тактики в значительной степени делают то же самое; однако, если вы не знаете об инкапсуляции, то насколько хорошо, что программист скрывает данные (поля или атрибуты) от пользователя, основным правилом инкапсуляции является не раскрытие внутренних данных пользователю. Изменение поля как общедоступного означает, что любой другой класс может получить доступ к этим полям и может быть очень опасен для пользователя. Вот почему я всегда рекомендую программистам Java использовать методы доступа и мутаторов, чтобы поля не попадали в чужие руки.

Если вам интересно узнать, как получить доступ к частному полю, вы можете использовать отражение, которое фактически обращается к данным определенного класса, чтобы вы могли его мутировать, если вы действительно должны это делать. Предположим, что вы знаете, что класс java.lang.String содержит частное поле типа char [] (то есть массив char). Он скрыт от пользователя, поэтому вы не можете напрямую обращаться к полю. (Кстати, метод java.lang.String.toCharArray() обращается к вам в поле.) Если вы хотите последовательно обращаться к каждому символу и хранить каждый символ в коллекции (для простоты почему-то не Java. util.List?), то вот как использовать отражение в этом случае:

/**
    This method iterates through each character in a <code>String</code> and places each of them into a <code>java.util.List</code> of type <code>Character</code>.
    @param str The <code>String</code> to extract from.
    @param list The list to store each character into. (This is necessary because the compiler knows not which <code>List</code> to use, so it will automatically clear the list anyway.)
*/
public static void extractStringData(String str, List<Character> list) throws IllegalAccessException, NoSuchFieldException
{
    java.lang.reflect.Field value = String.class.getDeclaredField("value");
    value.setAccessible(true);
    char[] data = (char[]) value.get(str);
    for(char ch : data) list.add(ch);
}

Как побочный элемент, обратите внимание, что отражение требует большой производительности из вашей программы. Если есть поле, метод или внутренний или вложенный класс, которые вы должны получить по любой причине (что в любом случае маловероятно), вы должны использовать отражение. Основная причина, по которой отражение отвлекает драгоценные результаты, связано с относительно бесчисленными исключениями, которые он бросает. Я рад помочь!