Большая разница в скорости эквивалентных статических и нестатических методов
В этом коде, когда я создаю объект в методе main
, а затем вызываю этот метод объектов: ff.twentyDivCount(i)
(выполняется в 16010 мс), он работает намного быстрее, чем называть его с помощью этой аннотации: twentyDivCount(i)
(выполняется в 59516 мс). Конечно, когда я запускаю его без создания объекта, я делаю метод статическим, поэтому его можно вызвать в основном.
public class ProblemFive {
// Counts the number of numbers that the entry is evenly divisible by, as max is 20
int twentyDivCount(int a) { // Change to static int.... when using it directly
int count = 0;
for (int i = 1; i<21; i++) {
if (a % i == 0) {
count++;
}
}
return count;
}
public static void main(String[] args) {
long startT = System.currentTimeMillis();;
int start = 500000000;
int result = start;
ProblemFive ff = new ProblemFive();
for (int i = start; i > 0; i--) {
int temp = ff.twentyDivCount(i); // Faster way
// twentyDivCount(i) - slower
if (temp == 20) {
result = i;
System.out.println(result);
}
}
System.out.println(result);
long end = System.currentTimeMillis();;
System.out.println((end - startT) + " ms");
}
}
EDIT: Пока кажется, что разные машины производят разные результаты, но с использованием JRE 1.8. *, где исходный результат, кажется, постоянно воспроизводится.
Ответы
Ответ 1
Используя JRE 1.8.0_45, я получаю аналогичные результаты.
Исследование:
- запуск java с параметрами
-XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining
VM показывает, что оба метода скомпилированы и встроены
- Глядя на сгенерированную сборку для самих методов, нет существенной разницы.
- Однако, как только они встают в очередь, сгенерированная сборка внутри
main
сильно отличается, причем метод экземпляра более агрессивно оптимизируется, особенно в плане разворачивания цикла
Затем я снова проверил ваш тест, но с различными настройками разворота цикла, чтобы подтвердить подозрение выше. Я запустил ваш код с помощью:
-
-XX:LoopUnrollLimit=0
, и оба метода выполняются медленно (аналогично статическому методу с параметрами по умолчанию).
-
-XX:LoopUnrollLimit=100
, и оба метода работают быстро (аналогично методу экземпляра с параметрами по умолчанию).
Как вывод, кажется, что с настройками по умолчанию JIT hotspot 1.8.0_45 не может развернуть цикл, когда метод статичен (хотя я не уверен, почему он ведет себя таким образом). Другие JVM могут давать разные результаты.
Ответ 2
Только что недоказанное предположение основано на ответе assylias.
JVM использует порог для разворачивания цикла, что составляет примерно 70. По какой-то причине статический вызов немного больше и не разворачивается.
Обновить результаты
- С
LoopUnrollLimit
в нижнем разделе 52 обе версии медленны.
- Между 52 и 71, только статическая версия работает медленно.
- Выше 71, обе версии быстры.
Это странно, поскольку я предполагал, что статический вызов немного больше во внутреннем представлении, а OP - в странном случае. Но разница составляет около 20, что не имеет смысла.
-XX:LoopUnrollLimit=51
5400 ms NON_STATIC
5310 ms STATIC
-XX:LoopUnrollLimit=52
1456 ms NON_STATIC
5305 ms STATIC
-XX:LoopUnrollLimit=71
1459 ms NON_STATIC
5309 ms STATIC
-XX:LoopUnrollLimit=72
1457 ms NON_STATIC
1488 ms STATIC
Для тех, кто хочет экспериментировать, моя версия может быть полезна.
Ответ 3
Когда это выполняется в режиме отладки, номера одинаковы для экземпляра и статических случаев. Это также означает, что JIT не хочет компилировать код в собственный код в статическом случае так же, как в случае с методом экземпляра.
Почему это так? Сложно сказать; вероятно, это будет правильно, если это будет более широкое приложение...
Ответ 4
Я немного изменил тест и получил следующие результаты:
Вывод:
Dynamic Test:
465585120
232792560
232792560
51350 ms
Static Test:
465585120
232792560
232792560
52062 ms
Примечание
Пока я тестировал их отдельно, я получил ~ 52 с для динамического и ~ 200 секунд для статического.
Это программа:
public class ProblemFive {
// Counts the number of numbers that the entry is evenly divisible by, as max is 20
int twentyDivCount(int a) { // Change to static int.... when using it directly
int count = 0;
for (int i = 1; i<21; i++) {
if (a % i == 0) {
count++;
}
}
return count;
}
static int twentyDivCount2(int a) {
int count = 0;
for (int i = 1; i<21; i++) {
if (a % i == 0) {
count++;
}
}
return count;
}
public static void main(String[] args) {
System.out.println("Dynamic Test: " );
dynamicTest();
System.out.println("Static Test: " );
staticTest();
}
private static void staticTest() {
long startT = System.currentTimeMillis();;
int start = 500000000;
int result = start;
for (int i = start; i > 0; i--) {
int temp = twentyDivCount2(i);
if (temp == 20) {
result = i;
System.out.println(result);
}
}
System.out.println(result);
long end = System.currentTimeMillis();;
System.out.println((end - startT) + " ms");
}
private static void dynamicTest() {
long startT = System.currentTimeMillis();;
int start = 500000000;
int result = start;
ProblemFive ff = new ProblemFive();
for (int i = start; i > 0; i--) {
int temp = ff.twentyDivCount(i); // Faster way
if (temp == 20) {
result = i;
System.out.println(result);
}
}
System.out.println(result);
long end = System.currentTimeMillis();;
System.out.println((end - startT) + " ms");
}
}
Я также изменил порядок теста на:
public static void main(String[] args) {
System.out.println("Static Test: " );
staticTest();
System.out.println("Dynamic Test: " );
dynamicTest();
}
И я получил это:
Static Test:
465585120
232792560
232792560
188945 ms
Dynamic Test:
465585120
232792560
232792560
50106 ms
Как вы видите, если динамический вызов вызывается перед статикой, скорость для статики резко уменьшается.
Основываясь на этом тесте:
I предположим, что все зависит от оптимизации JVM. таким образом, я просто рекомендую вам перейти с эмпирическим правилом для использования статических и динамические методы.
ПРАВИЛО THUMB:
Java: когда использовать статические методы
Ответ 5
Попробуйте:
public class ProblemFive {
public static ProblemFive PROBLEM_FIVE = new ProblemFive();
public static void main(String[] args) {
long startT = System.currentTimeMillis();
int start = 500000000;
int result = start;
for (int i = start; i > 0; i--) {
int temp = PROBLEM_FIVE.twentyDivCount(i); // faster way
// twentyDivCount(i) - slower
if (temp == 20) {
result = i;
System.out.println(result);
System.out.println((System.currentTimeMillis() - startT) + " ms");
}
}
System.out.println(result);
long end = System.currentTimeMillis();
System.out.println((end - startT) + " ms");
}
int twentyDivCount(int a) { // change to static int.... when using it directly
int count = 0;
for (int i = 1; i < 21; i++) {
if (a % i == 0) {
count++;
}
}
return count;
}
}