Я использую Windows 8.1 x64 с обновлением Java 7 45 x64 (32-разрядная версия Java не установлена) на планшете Surface Pro 2.
Приведенный ниже код занимает 1688 мсек, если тип i является длинным, и 109 мсек, когда я является целым. Почему long (64-битный тип) на порядок медленнее, чем int на 64-битной платформе с 64-битной JVM?
Мое единственное предположение состоит в том, что процессору требуется больше времени для добавления 64-битного целого числа, чем 32-битного, но это кажется маловероятным. Я подозреваю, что Haswell не использует сумматоры с волновым переносом.
Я запускаю это в Eclipse Kepler SR1, кстати.
public class Main {
private static long i = Integer.MAX_VALUE;
public static void main(String[] args) {
System.out.println("Starting the loop");
long startTime = System.currentTimeMillis();
while(!decrementAndCheck()){
}
long endTime = System.currentTimeMillis();
System.out.println("Finished the loop in " + (endTime - startTime) + "ms");
}
private static boolean decrementAndCheck() {
return --i < 0;
}
}
Изменить: вот результаты эквивалентного кода C ++, скомпилированного VS 2013 (ниже), той же системы. длинный: 72265 мс int: 74656 мс Эти результаты были в 32-битном режиме отладки.
В 64-битном режиме выпуска: длинный: 875 мс long long: 906 мс int: 1047 мс
Это говорит о том, что результатом, который я наблюдал, является странность оптимизации JVM, а не ограничения ЦП.
#include "stdafx.h"
#include "iostream"
#include "windows.h"
#include "limits.h"
long long i = INT_MAX;
using namespace std;
boolean decrementAndCheck() {
return --i < 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
cout << "Starting the loop" << endl;
unsigned long startTime = GetTickCount64();
while (!decrementAndCheck()){
}
unsigned long endTime = GetTickCount64();
cout << "Finished the loop in " << (endTime - startTime) << "ms" << endl;
}
Изменить: просто попробовал это еще раз в Java 8 RTM, никаких существенных изменений.
источник
currentTimeMillis()
исполняемый код, который можно тривиально оптимизировать полностью, и т. Д. Пахнет ненадежными результатами.long
в качестве счетчика цикла, потому что компилятор JIT оптимизировал цикл, когда я использовалint
. Нужно будет посмотреть на дизассемблирование сгенерированного машинного кода.Ответы:
Моя JVM делает это с внутренним циклом довольно просто, когда вы используете
long
s:0x00007fdd859dbb80: test %eax,0x5f7847a(%rip) /* fun JVM hack */ 0x00007fdd859dbb86: dec %r11 /* i-- */ 0x00007fdd859dbb89: mov %r11,0x258(%r10) /* store i to memory */ 0x00007fdd859dbb90: test %r11,%r11 /* unnecessary test */ 0x00007fdd859dbb93: jge 0x00007fdd859dbb80 /* go back to the loop top */
Это жестко обманывает, когда вы используете
int
s; во-первых, есть некоторая странность, которую я не понимаю, но похоже на настройку для развернутого цикла:0x00007f3dc290b5a1: mov %r11d,%r9d 0x00007f3dc290b5a4: dec %r9d 0x00007f3dc290b5a7: mov %r9d,0x258(%r10) 0x00007f3dc290b5ae: test %r9d,%r9d 0x00007f3dc290b5b1: jl 0x00007f3dc290b662 0x00007f3dc290b5b7: add $0xfffffffffffffffe,%r11d 0x00007f3dc290b5bb: mov %r9d,%ecx 0x00007f3dc290b5be: dec %ecx 0x00007f3dc290b5c0: mov %ecx,0x258(%r10) 0x00007f3dc290b5c7: cmp %r11d,%ecx 0x00007f3dc290b5ca: jle 0x00007f3dc290b5d1 0x00007f3dc290b5cc: mov %ecx,%r9d 0x00007f3dc290b5cf: jmp 0x00007f3dc290b5bb 0x00007f3dc290b5d1: and $0xfffffffffffffffe,%r9d 0x00007f3dc290b5d5: mov %r9d,%r8d 0x00007f3dc290b5d8: neg %r8d 0x00007f3dc290b5db: sar $0x1f,%r8d 0x00007f3dc290b5df: shr $0x1f,%r8d 0x00007f3dc290b5e3: sub %r9d,%r8d 0x00007f3dc290b5e6: sar %r8d 0x00007f3dc290b5e9: neg %r8d 0x00007f3dc290b5ec: and $0xfffffffffffffffe,%r8d 0x00007f3dc290b5f0: shl %r8d 0x00007f3dc290b5f3: mov %r8d,%r11d 0x00007f3dc290b5f6: neg %r11d 0x00007f3dc290b5f9: sar $0x1f,%r11d 0x00007f3dc290b5fd: shr $0x1e,%r11d 0x00007f3dc290b601: sub %r8d,%r11d 0x00007f3dc290b604: sar $0x2,%r11d 0x00007f3dc290b608: neg %r11d 0x00007f3dc290b60b: and $0xfffffffffffffffe,%r11d 0x00007f3dc290b60f: shl $0x2,%r11d 0x00007f3dc290b613: mov %r11d,%r9d 0x00007f3dc290b616: neg %r9d 0x00007f3dc290b619: sar $0x1f,%r9d 0x00007f3dc290b61d: shr $0x1d,%r9d 0x00007f3dc290b621: sub %r11d,%r9d 0x00007f3dc290b624: sar $0x3,%r9d 0x00007f3dc290b628: neg %r9d 0x00007f3dc290b62b: and $0xfffffffffffffffe,%r9d 0x00007f3dc290b62f: shl $0x3,%r9d 0x00007f3dc290b633: mov %ecx,%r11d 0x00007f3dc290b636: sub %r9d,%r11d 0x00007f3dc290b639: cmp %r11d,%ecx 0x00007f3dc290b63c: jle 0x00007f3dc290b64f 0x00007f3dc290b63e: xchg %ax,%ax /* OK, fine; I know what a nop looks like */
затем сам развернутый цикл:
0x00007f3dc290b640: add $0xfffffffffffffff0,%ecx 0x00007f3dc290b643: mov %ecx,0x258(%r10) 0x00007f3dc290b64a: cmp %r11d,%ecx 0x00007f3dc290b64d: jg 0x00007f3dc290b640
затем код разрыва для развернутого цикла, сам по себе тест и прямой цикл:
0x00007f3dc290b64f: cmp $0xffffffffffffffff,%ecx 0x00007f3dc290b652: jle 0x00007f3dc290b662 0x00007f3dc290b654: dec %ecx 0x00007f3dc290b656: mov %ecx,0x258(%r10) 0x00007f3dc290b65d: cmp $0xffffffffffffffff,%ecx 0x00007f3dc290b660: jg 0x00007f3dc290b654
Таким образом, для целых чисел это происходит в 16 раз быстрее, потому что JIT развернул
int
цикл 16 раз, но не развернулlong
цикл вообще.Для полноты, вот код, который я на самом деле пробовал:
public class foo136 { private static int i = Integer.MAX_VALUE; public static void main(String[] args) { System.out.println("Starting the loop"); for (int foo = 0; foo < 100; foo++) doit(); } static void doit() { i = Integer.MAX_VALUE; long startTime = System.currentTimeMillis(); while(!decrementAndCheck()){ } long endTime = System.currentTimeMillis(); System.out.println("Finished the loop in " + (endTime - startTime) + "ms"); } private static boolean decrementAndCheck() { return --i < 0; } }
Сборочные дампы были созданы с использованием опций
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
. Обратите внимание, что вам нужно возиться с установкой JVM, чтобы эта работа также работала для вас; вам нужно поместить какую-то случайную разделяемую библиотеку точно в нужное место, иначе она выйдет из строя.источник
long
версия медленнее, а скорее, чемint
версия быстрее. Это имеет смысл. Вероятно, не так много усилий было вложено в созданиеlong
выражений оптимизации JIT .gcc
использует-f
в качестве переключателя командной строки для «флага», иunroll-loops
оптимизация включается, говоря-funroll-loops
. Я просто использую слово «развернуть» для описания оптимизации.i-=16
, что, конечно, в 16 раз быстрее.Стек JVM определяется словами , размер которых является деталью реализации, но должен быть не менее 32 битов. Разработчик JVM может использовать 64-битные слова, но байт-код не может полагаться на это, поэтому операции со значениями
long
илиdouble
должны обрабатываться с особой осторожностью. В частности, инструкции целочисленного ветвления JVM определены именно для этого типаint
.В случае вашего кода поучительно дизассемблирование. Вот байт-код для
int
версии, скомпилированной Oracle JDK 7:private static boolean decrementAndCheck(); Code: 0: getstatic #14 // Field i:I 3: iconst_1 4: isub 5: dup 6: putstatic #14 // Field i:I 9: ifge 16 12: iconst_1 13: goto 17 16: iconst_0 17: ireturn
Обратите внимание, что JVM загрузит значение вашего статического
i
(0), вычтет единицу (3-4), продублирует значение в стеке (5) и вернет его в переменную (6). Затем он выполняет ветвь сравнения с нулем и возвращается.Версия с
long
немного сложнее:private static boolean decrementAndCheck(); Code: 0: getstatic #14 // Field i:J 3: lconst_1 4: lsub 5: dup2 6: putstatic #14 // Field i:J 9: lconst_0 10: lcmp 11: ifge 18 14: iconst_1 15: goto 19 18: iconst_0 19: ireturn
Во-первых, когда JVM дублирует новое значение в стеке (5), она должна дублировать два слова стека. В вашем случае вполне возможно, что это не дороже, чем дублирование, поскольку JVM может свободно использовать 64-битное слово, если это удобно. Однако вы заметите, что здесь логика ветвления длиннее. В JVM нет инструкции для сравнения a
long
с нулем, поэтому она должна поместить константу0L
в стек (9), выполнить общееlong
сравнение (10), а затем перейти к значению этого вычисления.Вот два возможных сценария:
long
версии, вставляя и выталкивая несколько дополнительных значений, и они находятся в виртуальном управляемом стеке , а не в реальном стеке процессора с аппаратной поддержкой. Если это так, вы все равно увидите значительную разницу в производительности после прогрева.Я рекомендую вам написать правильный микробенчмарк, чтобы устранить эффект включения JIT, а также попробовать это с конечным условием, отличным от нуля, чтобы заставить JVM провести такое же сравнение
int
, что и сlong
.источник
== 0
, что, по-видимому, является непропорционально большой частью результатов тестов. Мне кажется более вероятным, что OP пытается измерить более общий диапазон операций, и этот ответ указывает на то, что эталонный тест сильно смещен в сторону только одной из этих операций.Базовая единица данных в виртуальной машине Java - слово. Выбор правильного размера слова остается после реализации JVM. Реализация JVM должна выбирать минимальный размер слова 32 бита. Он может выбрать больший размер слова для повышения эффективности. Также нет никаких ограничений, что 64-битная JVM должна выбирать только 64-битное слово.
Базовая архитектура не требует, чтобы размер слова был одинаковым. JVM читает / записывает данные пословно. Это причина, по которой это может занять больше времени, чем int .
Здесь вы можете найти больше по той же теме.
источник
Я только что написал тест с помощью штангенциркуля .
Эти результаты вполне согласуются с исходным кодом: а ~ 12x ускорение для использования
int
болееlong
. Кажется, конечно, что происходит разворачивание цикла, о котором сообщает tmyklebu или что-то очень похожее.timeIntDecrements 195,266,845.000 timeLongDecrements 2,321,447,978.000
Это мой код; обратите внимание, что он использует только что созданный снимок
caliper
, поскольку я не мог понять, как кодировать их существующую бета-версию.package test; import com.google.caliper.Benchmark; import com.google.caliper.Param; public final class App { @Param({""+1}) int number; private static class IntTest { public static int v; public static void reset() { v = Integer.MAX_VALUE; } public static boolean decrementAndCheck() { return --v < 0; } } private static class LongTest { public static long v; public static void reset() { v = Integer.MAX_VALUE; } public static boolean decrementAndCheck() { return --v < 0; } } @Benchmark int timeLongDecrements(int reps) { int k=0; for (int i=0; i<reps; i++) { LongTest.reset(); while (!LongTest.decrementAndCheck()) { k++; } } return (int)LongTest.v | k; } @Benchmark int timeIntDecrements(int reps) { int k=0; for (int i=0; i<reps; i++) { IntTest.reset(); while (!IntTest.decrementAndCheck()) { k++; } } return IntTest.v | k; } }
источник
Для справки, эта версия делает грубую "разминку":
public class LongSpeed { private static long i = Integer.MAX_VALUE; private static int j = Integer.MAX_VALUE; public static void main(String[] args) { for (int x = 0; x < 10; x++) { runLong(); runWord(); } } private static void runLong() { System.out.println("Starting the long loop"); i = Integer.MAX_VALUE; long startTime = System.currentTimeMillis(); while(!decrementAndCheckI()){ } long endTime = System.currentTimeMillis(); System.out.println("Finished the long loop in " + (endTime - startTime) + "ms"); } private static void runWord() { System.out.println("Starting the word loop"); j = Integer.MAX_VALUE; long startTime = System.currentTimeMillis(); while(!decrementAndCheckJ()){ } long endTime = System.currentTimeMillis(); System.out.println("Finished the word loop in " + (endTime - startTime) + "ms"); } private static boolean decrementAndCheckI() { return --i < 0; } private static boolean decrementAndCheckJ() { return --j < 0; } }
Общее время улучшается примерно на 30%, но соотношение между ними остается примерно таким же.
источник
int
в 20 раз быстрее) с этим кодом.Для записей:
если я использую
boolean decrementAndCheckLong() { lo = lo - 1l; return lo < -1l; }
(изменено "l--" на "l = l - 1l") длительная работа улучшается на ~ 50%
источник
У меня нет 64-битной машины для тестирования, но довольно большая разница говорит о том, что работает не только чуть более длинный байт-код.
Я вижу очень близкие времена для long / int (4400 против 4800 мс) на моем 32-битном 1.7.0_45.
Это только предположение , но я сильно подозреваю, что это результат штрафа за рассогласование памяти. Чтобы подтвердить / опровергнуть подозрение, попробуйте добавить публичный статический int dummy = 0; до объявления i. Это подтолкнет i вниз на 4 байта в макете памяти и может правильно выровнять его для повышения производительности.Подтверждено, что проблема не возникает.РЕДАКТИРОВАТЬ:
Причина этого в том, что виртуальная машина не может переупорядочивать поля на досуге, добавляя заполнение для оптимального выравнивания, поскольку это может мешать JNI.(Не тот случай).источник
Вероятно, это связано с тем, что JVM проверяет точки безопасности при использовании long (несчетный цикл), а не делает это для int (счетный цикл).
Некоторые ссылки: https://stackoverflow.com/a/62557768/14624235
https://stackoverflow.com/a/58726530/14624235
http://psy-lob-saw.blogspot.com/2016/02/wait-for-it-counteduncounted-loops.html
источник