为什么在x64java中long比int慢?
我正在Surface Pro 2平板电脑上运行Windows 8.1 x64和Java 7 update 45 x64(未安装32位Java) 当i的类型为long时,下面的代码需要1688ms;当i为int时,下面的代码需要109ms。为什么long(64位类型)在具有64位JVM的64位平台上比int慢一个数量级 我唯一的猜测是,CPU添加64位整数比添加32位整数需要更长的时间,但这似乎不太可能。我怀疑Haswell没有使用ripple进位加法器 顺便说一句,我在Eclipse开普勒SR1中运行这个为什么在x64java中long比int慢?,java,performance,32bit-64bit,long-integer,Java,Performance,32bit 64bit,Long Integer,我正在Surface Pro 2平板电脑上运行Windows 8.1 x64和Java 7 update 45 x64(未安装32位Java) 当i的类型为long时,下面的代码需要1688ms;当i为int时,下面的代码需要109ms。为什么long(64位类型)在具有64位JVM的64位平台上比int慢一个数量级 我唯一的猜测是,CPU添加64位整数比添加32位整数需要更长的时间,但这似乎不太可能。我怀疑Haswell没有使用ripple进位加法器 顺便说一句,我在Eclipse开普勒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;
}
}
公共类主{
私有静态长i=Integer.MAX_值;
公共静态void main(字符串[]args){
System.out.println(“启动循环”);
long startTime=System.currentTimeMillis();
而(!decrementAndCheck()){
}
long-endTime=System.currentTimeMillis();
System.out.println(“在”+(endTime-startTime)+“ms”中完成了循环);
}
私有静态布尔递减检查(){
return--i<0;
}
}
<>编辑:这里是由VS 2013(以下)相同系统编译的等效C++代码的结果。长:72265ms int:74656ms这些结果处于调试32位模式
在64位释放模式下:长:875ms长:906ms内:1047ms
这表明我观察到的结果是JVM优化的古怪,而不是CPU的限制
#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;
}
#包括“stdafx.h”
#包括“iostream”
#包括“windows.h”
#包括“限制.h”
长i=INT_MAX;
使用名称空间std;
布尔递减检查(){
return--i<0;
}
int _tmain(int argc,_TCHAR*argv[]
{
coutJava虚拟机中的基本数据单位是字。选择正确的字大小取决于JVM的实现。JVM实现应选择32位的最小字大小。它可以选择更高的字大小以提高效率。64位JVM只选择64位字没有任何限制
底层架构没有规定字的大小也应该相同。JVM逐字读取/写入数据。这就是为什么它可能比int花费更长时间的原因
您可以在同一主题上找到更多信息。JVM堆栈是以字为单位定义的,字的大小是实现细节,但必须至少为32位宽。JVM实现者可以使用64位字,但字节码不能依赖于此,因此必须格外小心地处理具有长
或双
值的操作。尤其是在类型int
上定义的
对于您的代码,反汇编很有指导意义。以下是Oracle JDK 7编译的int
版本的字节码:
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)中。然后,它将与零分支进行比较并返回
带有长
的版本有点复杂:
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没有将long
与零进行比较的指令,因此它必须推送常量nt0L
到堆栈(9)上,进行常规long
比较(10),然后对该计算的值进行分支
以下是两种可能的情况:
- JVM完全遵循字节码路径。在这种情况下,它在
long
版本中做更多的工作,推送和弹出几个额外的值,这些值在虚拟托管堆栈上,而不是在真正的硬件辅助CPU堆栈上。如果是这种情况,在预热后您仍然会看到显著的性能差异
- JVM意识到它可以优化这段代码。在这种情况下,优化掉一些实际上不必要的推送/比较逻辑需要额外的时间。如果是这种情况,那么在预热后,您会发现性能差别很小
我建议您消除JIT启动的影响,并在最后一个条件不是零的情况下尝试这一点,以迫使JVM在int
上执行与long
相同的比较,我没有64位机器来测试,但相当大的差异表明还有更多比工作中稍长的字节码
我在32位1.7.0_45上看到long/int(4400对4800ms)的时间非常接近
这只是一个猜测,但我强烈怀疑这是内存未对齐惩罚的结果。若要确认/否认此怀疑,请尝试在声明I之前添加一个公共静态int dummy=0。这将在内存布局中向下推I 4个字节,并可能使其正确对齐以获得更好的性能。已确认不会导致问题e
编辑:这背后的原因是虚拟机可能不会随意对字段重新排序添加填充以实现最佳对齐,因为这可能会干扰JNI(并非如此)。对于记录,此版本会进行粗略的“预热”:
公共类长速度{
私有静态长i=Integer.MAX_值;
私有静态int j=整数.MAX_值;
公共静态void main(字符串[]args){
对于(int x=0;x<10;x++){
runLong();
runWord();
}
}
私有静态void runLong(){
System.out.println(“启动长循环”);
i=整数。最大值;
long startTime=System.currentTimeMillis();
而(!decrementAndCheckI()){
}
long-endTime=System.currentTimeMillis();
println(“在”+(endTime-startTime)+“ms”中完成了长循环);
}
专用静态vo
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;
}
}
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 */
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
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;
}
}
boolean decrementAndCheckLong() {
lo = lo - 1l;
return lo < -1l;
}
timeIntDecrements 195,266,845.000
timeLongDecrements 2,321,447,978.000
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;
}
}