Как правило, Java оптимизирует виртуальные вызовы на основе количества реализаций, встречающихся на данной стороне вызова. Это легко увидеть в результатах моего benchmark, когда вы смотрите myCode
, что является тривиальным методом, возвращающим сохраненный int
. Там тривиальный
static abstract class Base {
abstract int myCode();
}
с несколькими идентичными реализациями, такими как
static class A extends Base {
@Override int myCode() {
return n;
}
@Override public int hashCode() {
return n;
}
private final int n = nextInt();
}
С увеличением числа реализаций время вызова метода растет с 0,4 нс до 1,2 нс для двух реализаций до 11,6 нс, а затем медленно растет. Когда JVM увидела множественную реализацию, т.е. С preload=true
, тайминги немного отличаются (из-за теста instanceof
).
До сих пор все ясно, однако, hashCode
ведет себя по-другому. Особенно это в 8-10 раз медленнее в трех случаях. Любая идея почему?
UPDATE
Мне было любопытно, можно ли помочь бедным hashCode
, отправив вручную, и это может быть много.
Несколько ветвей отлично справились с работой:
if (o instanceof A) {
result += ((A) o).hashCode();
} else if (o instanceof B) {
result += ((B) o).hashCode();
} else if (o instanceof C) {
result += ((C) o).hashCode();
} else if (o instanceof D) {
result += ((D) o).hashCode();
} else { // Actually impossible, but let play it safe.
result += o.hashCode();
}
Обратите внимание, что компилятор избегает таких оптимизаций для более чем двух реализаций, поскольку большинство вызовов методов намного дороже, чем простая полевая нагрузка, и коэффициент усиления будет мал по сравнению с раздуванием кода.
Исходный вопрос "Почему JIT не оптимизирует hashCode
, как и другие методы", остается и hashCode2
доказательств, которые он действительно мог бы сделать.
ОБНОВЛЕНИЕ 2
Похоже, что bestsss прав, по крайней мере с этой записью
вызов hashCode() любого расширяемого класса Base - это то же самое, что и вызов Object.hashCode(), и это то, как он компилируется в байт-коде, если вы добавите явный хэш-код в Base, который ограничит потенциальные цели вызова, ссылающиеся на Base. хэш-код().
Я не совсем уверен в том, что происходит, но объявление Base.hashCode()
снова делает конкуренцию hashCode
.
ОБНОВЛЕНИЕ 3
ОК, поэтому конкретная реализация Base#hashCode
помогает, однако, JIT должен знать, что он никогда не вызывается, поскольку все подклассы определяют свои собственные (если другой подкласс не загружен, что может привести к деоптимизации, но это ничего нового для JIT).
Таким образом, это выглядит как пропущенная вероятность оптимизации # 1.
Обеспечение абстрактной реализации Base#hashCode
работает одинаково. Это имеет смысл, поскольку он обеспечивает, что дальнейший поиск не требуется, поскольку каждый подкласс должен предоставлять свои собственные (они не могут просто наследовать их дедушку и бабушку).
Все еще для более чем двух реализаций, myCode
намного быстрее, что компилятор должен делать что-то слишком многообразное. Может быть, упущенная вероятность оптимизации №2?