x64 Java에서 int보다 긴 이유는 무엇입니까?
Surface Pro 2 태블릿에서 Java 7 업데이트 45 x 64(32비트 Java 미설치)를 사용하여 Windows 8.1 x 64를 실행하고 있습니다.
아래 코드는 i타입이 long일 경우 1688ms, int일 경우 109ms가 소요됩니다.64비트 JVM을 사용하는 64비트 플랫폼에서 롱(64비트 타입)이 int보다 느린 이유는 무엇입니까?
CPU가 32비트 정수보다 64비트 정수를 추가하는 데 더 오래 걸릴 것으로 생각되지만 그럴 가능성은 거의 없습니다.해스웰은 리플 캐리어 애더를 사용하지 않는 것 같아요.
이클립스 케플러 SR1, BTW에서 실행 중입니다.
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;
}
}
편집: VS 2013(아래)에서 컴파일한 동일 시스템 C++ 코드의 결과는 다음과 같습니다.long: 72265ms int: 74656ms 이러한 결과는 debug 32비트모드입니다
64비트 릴리즈 모드: 롱: 875ms 길이: 906ms int: 1047ms
이는 CPU 제한이 아니라 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에서 다시 시도했지만 큰 변화는 없습니다.
하면 JVM은 작업을 합니다.longs:
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 */
, , it, it가 it it it you 를 사용하면잘 되지 않습니다.int수 몇
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
가 ints를 풀었기 .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:+PrintAssemblyJVM의 인스톨도 조작할 필요가 있는 것에 주의해 주세요.랜덤 공유 라이브러리를 올바른 장소에 배치하지 않으면 실패합니다.
JVM 스택은 단어 단위로 정의되며, 그 크기는 구현 세부 사항이지만 폭이 32비트 이상이어야 합니다.JVM 구현자는 64비트 단어를 사용할 수 있지만 바이트코드는 이에 의존할 수 없기 때문에 다음과 같은 작업을 수행할 수 없습니다.long ★★★★★★★★★★★★★★★★★」double값은 특별히 주의해서 다루어야 합니다.특히 JVM 정수 브랜치명령어는 정확히 다음 유형에 정의되어 있습니다.int.
코드의 경우 분해가 도움이 됩니다. 여기 , 라고 하다의 입니다.int7에 : 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 ( 스택(하여 (6 넣습니다. (0) 1은 (3-4)는 (6)는 (6)는 (6)은 (6)은 (6)은 (6)은 (으)로 합니다.그런 다음 0과 비교 분기가 실행되어 돌아갑니다.
" " 가 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을 .long붙여서 0을 .0L에 대해 인 '9'를 실행합니다.long비교(10) 후 해당 계산 값을 분기합니다.
다음으로 두 가지 가능성이 있는 시나리오를 제시하겠습니다.
- JVM은 바이트 코드 경로를 정확히 따르고 있습니다.더 요.
long버전, 푸시 및 팝핑된 몇 가지 추가 값이 실제 하드웨어 지원 CPU 스택이 아닌 가상 관리 스택에 있습니다.이 경우에도 워밍업 후에도 성능 차이가 크게 나타납니다. - JVM은 이 코드를 최적화할 수 있음을 인식합니다.이 경우 실제로 불필요한 푸시/비교 논리의 일부를 최적화하려면 시간이 더 걸립니다.이 경우 워밍업 후 성능 차이는 거의 없습니다.
올바른 마이크로벤치마크를 작성하여 JIT가 제로가 아닌 최종 조건으로 이 테스트를 실시하여 JVM을 강제로 비교하도록 할 것을 권장합니다.intlong.
Java Virtual Machine의 기본 데이터 단위는 word입니다.적절한 워드 사이즈를 선택하는 것은 JVM의 실장 후에 남습니다.JVM 구현에서는 최소 32비트의 워드사이즈를 선택해야 합니다.더 큰 단어 크기를 선택하여 효율성을 높일 수 있습니다.64비트 JVM이 64비트 워드만을 선택해야 한다는 제한도 없습니다.
기본 아키텍처에서는 단어 크기도 동일해야 한다고 규정하지 않습니다.JVM은 데이터를 한 단어씩 읽고 씁니다.이것이 int보다 시간이 오래 걸릴 수 있는 이유입니다.
여기에서 같은 주제에 대한 자세한 내용을 찾을 수 있습니다.
방금 캘리퍼로 벤치마크를 작성했습니다.
결과는 원래 코드와 상당히 일치합니다.사용 시 속도가 최대 12배 향상됩니다.int에 걸쳐서longtmyklebu 등이 보고한 루프 언롤링이 진행되고 있는 것 같습니다.
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% 개선되지만, 둘 사이의 비율은 거의 동일합니다.
기록의 경우:
내가 사용한다면
boolean decrementAndCheckLong() {
lo = lo - 1l;
return lo < -1l;
}
("l--" ~ "l = l - 1l") 긴 성능 최대 50% 향상
이는 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
테스트할 수 있는 64비트 머신은 없지만, 다소 큰 차이는 작업 중인 바이트 코드보다 약간 긴 바이트 코드가 더 많은 바이트 코드가 있음을 나타냅니다.
32비트 1.7.0_45에서는 long/int(4400ms와 4800ms)에 매우 가까운 시간이 표시됩니다.
추측에 불과하지만 메모리 미스 얼라인먼트 패널티의 영향이라고 생각합니다. 의심을 확인/확정하려면 i 선언 전에 public static int dummy = 0을 추가해 봅니다. 그러면 메모리 레이아웃에서 i가 4바이트 내려가고 성능이 향상될 수 있습니다.문제의 원인이 아님을 확인.
편집: 최적의 정렬을 위해 VM이 여유롭게 필드를 재정렬할 수 없기 때문에 JNI에 방해가 될 수 있습니다(그렇지 않습니다).
언급URL : https://stackoverflow.com/questions/19844048/why-is-long-slower-than-int-in-x64-java
'source' 카테고리의 다른 글
| Java 선택 안 함: varargs 파라미터에 대한 일반 배열 작성 선택 안 함 (0) | 2022.11.11 |
|---|---|
| Angular 변수에서 iframe src 속성을 설정하는 방법JS (0) | 2022.11.02 |
| 정수를 int로 변환하는 방법 (0) | 2022.11.02 |
| Java 어레이의 프리미티브는 스택 또는 힙에 저장되어 있습니까? (0) | 2022.11.02 |
| URL과 일치시키는 데 적합한 정규 표현은 무엇입니까? (0) | 2022.11.02 |