다음 Java 프로그램의 평균 실행 시간은 0.50 초에서 0.55 초입니다.
public static void main(String[] args) {
long startTime = System.nanoTime();
int n = 0;
for (int i = 0; i < 1000000000; i++) {
n += 2 * (i * i);
}
System.out.println((double) (System.nanoTime() - startTime) / 1000000000 + " s");
System.out.println("n = " + n);
}
내가 교체 2 * (i * i)
하면2 * i * i
, 그것을 실행하는 0.60 사이에 0.65 초 걸립니다. 어떻게 오세요?
프로그램의 각 버전을 15 번 실행하여 두 버전을 번갈아 실행했습니다. 결과는 다음과 같습니다.
2*(i*i) | 2*i*i
----------+----------
0.5183738 | 0.6246434
0.5298337 | 0.6049722
0.5308647 | 0.6603363
0.5133458 | 0.6243328
0.5003011 | 0.6541802
0.5366181 | 0.6312638
0.515149 | 0.6241105
0.5237389 | 0.627815
0.5249942 | 0.6114252
0.5641624 | 0.6781033
0.538412 | 0.6393969
0.5466744 | 0.6608845
0.531159 | 0.6201077
0.5048032 | 0.6511559
0.5232789 | 0.6544526
가장 빠른 실행은 2 * i * i
가장 느린 실행보다 오래 걸렸습니다 2 * (i * i)
. 이들이 동일한 효율을 가졌다면, 이런 일이 일어날 확률은보다 적을 것 1/2^15 * 100% = 0.00305%
입니다.
답변
바이트 코드 순서에 약간의 차이가 있습니다.
2 * (i * i)
:
iconst_2
iload0
iload0
imul
imul
iadd
vs 2 * i * i
:
iconst_2
iload0
imul
iload0
imul
iadd
첫눈에 차이가 없어야합니다. 하나의 슬롯을 덜 사용하기 때문에 두 번째 버전이 더 최적이라면
따라서 하위 레벨 (JIT) 1을 더 깊이 파고 들어야 합니다.
JIT는 작은 루프를 매우 적극적으로 풀리는 경향이 있습니다. 실제로 우리는이 2 * (i * i)
사건에 대해 16 배 언 롤링을 관찰합니다 .
030 B2: # B2 B3 <- B1 B2 Loop: B2-B2 inner main of N18 Freq: 1e+006
030 addl R11, RBP # int
033 movl RBP, R13 # spill
036 addl RBP, #14 # int
039 imull RBP, RBP # int
03c movl R9, R13 # spill
03f addl R9, #13 # int
043 imull R9, R9 # int
047 sall RBP, #1
049 sall R9, #1
04c movl R8, R13 # spill
04f addl R8, #15 # int
053 movl R10, R8 # spill
056 movdl XMM1, R8 # spill
05b imull R10, R8 # int
05f movl R8, R13 # spill
062 addl R8, #12 # int
066 imull R8, R8 # int
06a sall R10, #1
06d movl [rsp + #32], R10 # spill
072 sall R8, #1
075 movl RBX, R13 # spill
078 addl RBX, #11 # int
07b imull RBX, RBX # int
07e movl RCX, R13 # spill
081 addl RCX, #10 # int
084 imull RCX, RCX # int
087 sall RBX, #1
089 sall RCX, #1
08b movl RDX, R13 # spill
08e addl RDX, #8 # int
091 imull RDX, RDX # int
094 movl RDI, R13 # spill
097 addl RDI, #7 # int
09a imull RDI, RDI # int
09d sall RDX, #1
09f sall RDI, #1
0a1 movl RAX, R13 # spill
0a4 addl RAX, #6 # int
0a7 imull RAX, RAX # int
0aa movl RSI, R13 # spill
0ad addl RSI, #4 # int
0b0 imull RSI, RSI # int
0b3 sall RAX, #1
0b5 sall RSI, #1
0b7 movl R10, R13 # spill
0ba addl R10, #2 # int
0be imull R10, R10 # int
0c2 movl R14, R13 # spill
0c5 incl R14 # int
0c8 imull R14, R14 # int
0cc sall R10, #1
0cf sall R14, #1
0d2 addl R14, R11 # int
0d5 addl R14, R10 # int
0d8 movl R10, R13 # spill
0db addl R10, #3 # int
0df imull R10, R10 # int
0e3 movl R11, R13 # spill
0e6 addl R11, #5 # int
0ea imull R11, R11 # int
0ee sall R10, #1
0f1 addl R10, R14 # int
0f4 addl R10, RSI # int
0f7 sall R11, #1
0fa addl R11, R10 # int
0fd addl R11, RAX # int
100 addl R11, RDI # int
103 addl R11, RDX # int
106 movl R10, R13 # spill
109 addl R10, #9 # int
10d imull R10, R10 # int
111 sall R10, #1
114 addl R10, R11 # int
117 addl R10, RCX # int
11a addl R10, RBX # int
11d addl R10, R8 # int
120 addl R9, R10 # int
123 addl RBP, R9 # int
126 addl RBP, [RSP + #32 (32-bit)] # int
12a addl R13, #16 # int
12e movl R11, R13 # spill
131 imull R11, R13 # int
135 sall R11, #1
138 cmpl R13, #999999985
13f jl B2 # loop end P=1.000000 C=6554623.000000
스택에 “유출 된”레지스터가 1 개있는 것을 볼 수 있습니다.
그리고 2 * i * i
버전의 경우 :
05a B3: # B2 B4 <- B1 B2 Loop: B3-B2 inner main of N18 Freq: 1e+006
05a addl RBX, R11 # int
05d movl [rsp + #32], RBX # spill
061 movl R11, R8 # spill
064 addl R11, #15 # int
068 movl [rsp + #36], R11 # spill
06d movl R11, R8 # spill
070 addl R11, #14 # int
074 movl R10, R9 # spill
077 addl R10, #16 # int
07b movdl XMM2, R10 # spill
080 movl RCX, R9 # spill
083 addl RCX, #14 # int
086 movdl XMM1, RCX # spill
08a movl R10, R9 # spill
08d addl R10, #12 # int
091 movdl XMM4, R10 # spill
096 movl RCX, R9 # spill
099 addl RCX, #10 # int
09c movdl XMM6, RCX # spill
0a0 movl RBX, R9 # spill
0a3 addl RBX, #8 # int
0a6 movl RCX, R9 # spill
0a9 addl RCX, #6 # int
0ac movl RDX, R9 # spill
0af addl RDX, #4 # int
0b2 addl R9, #2 # int
0b6 movl R10, R14 # spill
0b9 addl R10, #22 # int
0bd movdl XMM3, R10 # spill
0c2 movl RDI, R14 # spill
0c5 addl RDI, #20 # int
0c8 movl RAX, R14 # spill
0cb addl RAX, #32 # int
0ce movl RSI, R14 # spill
0d1 addl RSI, #18 # int
0d4 movl R13, R14 # spill
0d7 addl R13, #24 # int
0db movl R10, R14 # spill
0de addl R10, #26 # int
0e2 movl [rsp + #40], R10 # spill
0e7 movl RBP, R14 # spill
0ea addl RBP, #28 # int
0ed imull RBP, R11 # int
0f1 addl R14, #30 # int
0f5 imull R14, [RSP + #36 (32-bit)] # int
0fb movl R10, R8 # spill
0fe addl R10, #11 # int
102 movdl R11, XMM3 # spill
107 imull R11, R10 # int
10b movl [rsp + #44], R11 # spill
110 movl R10, R8 # spill
113 addl R10, #10 # int
117 imull RDI, R10 # int
11b movl R11, R8 # spill
11e addl R11, #8 # int
122 movdl R10, XMM2 # spill
127 imull R10, R11 # int
12b movl [rsp + #48], R10 # spill
130 movl R10, R8 # spill
133 addl R10, #7 # int
137 movdl R11, XMM1 # spill
13c imull R11, R10 # int
140 movl [rsp + #52], R11 # spill
145 movl R11, R8 # spill
148 addl R11, #6 # int
14c movdl R10, XMM4 # spill
151 imull R10, R11 # int
155 movl [rsp + #56], R10 # spill
15a movl R10, R8 # spill
15d addl R10, #5 # int
161 movdl R11, XMM6 # spill
166 imull R11, R10 # int
16a movl [rsp + #60], R11 # spill
16f movl R11, R8 # spill
172 addl R11, #4 # int
176 imull RBX, R11 # int
17a movl R11, R8 # spill
17d addl R11, #3 # int
181 imull RCX, R11 # int
185 movl R10, R8 # spill
188 addl R10, #2 # int
18c imull RDX, R10 # int
190 movl R11, R8 # spill
193 incl R11 # int
196 imull R9, R11 # int
19a addl R9, [RSP + #32 (32-bit)] # int
19f addl R9, RDX # int
1a2 addl R9, RCX # int
1a5 addl R9, RBX # int
1a8 addl R9, [RSP + #60 (32-bit)] # int
1ad addl R9, [RSP + #56 (32-bit)] # int
1b2 addl R9, [RSP + #52 (32-bit)] # int
1b7 addl R9, [RSP + #48 (32-bit)] # int
1bc movl R10, R8 # spill
1bf addl R10, #9 # int
1c3 imull R10, RSI # int
1c7 addl R10, R9 # int
1ca addl R10, RDI # int
1cd addl R10, [RSP + #44 (32-bit)] # int
1d2 movl R11, R8 # spill
1d5 addl R11, #12 # int
1d9 imull R13, R11 # int
1dd addl R13, R10 # int
1e0 movl R10, R8 # spill
1e3 addl R10, #13 # int
1e7 imull R10, [RSP + #40 (32-bit)] # int
1ed addl R10, R13 # int
1f0 addl RBP, R10 # int
1f3 addl R14, RBP # int
1f6 movl R10, R8 # spill
1f9 addl R10, #16 # int
1fd cmpl R10, #999999985
204 jl B2 # loop end P=1.000000 C=7419903.000000
여기서 우리는 더 많은 “유출”과 스택에 대한 더 많은 액세스를 관찰합니다. 더 [RSP + ...]
많은 중간 결과가 보존되어야하기 때문입니다.
따라서 질문에 대한 답변은 간단합니다. JIT가 첫 번째 경우에 대해 더 최적의 어셈블리 코드를 생성하기 때문에 2 * (i * i)
보다 빠릅니다 2 * i * i
.
그러나 물론 첫 번째 버전과 두 번째 버전이 좋은 것은 아닙니다. x86-64 CPU는 최소한 SSE2를 지원하므로 루프는 벡터화의 이점을 얻을 수 있습니다.
최적화의 문제입니다. 종종 그렇듯이 너무 공격적으로 풀리고 발로 스스로를 쏠 수 있습니다.
사실, 최신 x86-64 CPU는 명령어를 마이크로 옵스 (µops)로 세분화하고 레지스터 이름 변경, µop 캐시 및 루프 버퍼와 같은 기능을 통해 루프 최적화는 단순한 성능 풀림보다 훨씬 세밀한 작업을 수행합니다. Agner Fog의 최적화 가이드에 따르면 :
평균 명령어 길이가 4 바이트를 초과하면 µop 캐시로 인한 성능 향상이 상당 할 수 있습니다. µop 캐시 사용을 최적화하는 다음 방법을 고려할 수 있습니다.
- 임계 루프가 µop 캐시에 맞도록 충분히 작아야합니다.
- 가장 중요한 루프 항목과 기능 항목을 32로 정렬하십시오.
- 불필요한 루프 언 롤링을 피하십시오.
- 추가로드 시간이있는 지침을 피하십시오
. . .
이러한로드 시간과 관련하여 , 가장 빠른 L1D 히트조차도 4주기 , 추가 레지스터 및 µop가 필요하므로 메모리에 대한 소수의 액세스조차도 꽉 찬 루프에서 성능을 저하시킵니다.
그러나 벡터화 기회로 돌아가서-얼마나 빠를 수 있는지 확인하기 위해 GCC를 사용하여 유사한 C 응용 프로그램을 컴파일하여 완전히 벡터화합니다 (AVX2 표시, SSE2 유사) 2 :
vmovdqa ymm0, YMMWORD PTR .LC0[rip]
vmovdqa ymm3, YMMWORD PTR .LC1[rip]
xor eax, eax
vpxor xmm2, xmm2, xmm2
.L2:
vpmulld ymm1, ymm0, ymm0
inc eax
vpaddd ymm0, ymm0, ymm3
vpslld ymm1, ymm1, 1
vpaddd ymm2, ymm2, ymm1
cmp eax, 125000000 ; 8 calculations per iteration
jne .L2
vmovdqa xmm0, xmm2
vextracti128 xmm2, ymm2, 1
vpaddd xmm2, xmm0, xmm2
vpsrldq xmm0, xmm2, 8
vpaddd xmm0, xmm2, xmm0
vpsrldq xmm1, xmm0, 4
vpaddd xmm0, xmm0, xmm1
vmovd eax, xmm0
vzeroupper
런타임 :
- SSE : 0.24 초 또는 2 배 더 빠름
- AVX : 0.15 초 또는 3 배 더 빠름
- AVX2 : 0.08 초 또는 5 배 더 빠름
1 JIT 생성 어셈블리 출력을 얻으려면 디버그 JVM을 가져 와서 다음을 실행하십시오.-XX:+PrintOptoAssembly
2 C 버전은 -fwrapv
플래그 로 컴파일되어 GCC에서 부호있는 정수 오버플로를 2의 보완 랩으로 처리 할 수 있습니다.
답변
곱셈이 2 * (i * i)
인 경우 JVM은 2
루프에서 곱셈을 제거하여 다음과 같지만보다 효율적인 코드를 생성 할 수 있습니다.
int n = 0;
for (int i = 0; i < 1000000000; i++) {
n += i * i;
}
n *= 2;
그러나 곱셈이 (2 * i) * i
인 경우 상수에 의한 곱셈이 더하기 전에 더 이상 JVM이 최적화하지 않습니다.
이것이 내가 생각하는 이유는 다음과 같습니다.
- 추가
if (n == 0) n = 1
곱셈을 더 이상 고려하지 않아도 결과가 동일하다는 보장이 없으므로 루프 시작시 명령문을 두 버전 모두 효율적입니다. - 최적화 된 버전 (2를 곱하여 계산)은
2 * (i * i)
버전 만큼 빠릅니다.
이러한 결론을 내리기 위해 사용한 테스트 코드는 다음과 같습니다.
public static void main(String[] args) {
long fastVersion = 0;
long slowVersion = 0;
long optimizedVersion = 0;
long modifiedFastVersion = 0;
long modifiedSlowVersion = 0;
for (int i = 0; i < 10; i++) {
fastVersion += fastVersion();
slowVersion += slowVersion();
optimizedVersion += optimizedVersion();
modifiedFastVersion += modifiedFastVersion();
modifiedSlowVersion += modifiedSlowVersion();
}
System.out.println("Fast version: " + (double) fastVersion / 1000000000 + " s");
System.out.println("Slow version: " + (double) slowVersion / 1000000000 + " s");
System.out.println("Optimized version: " + (double) optimizedVersion / 1000000000 + " s");
System.out.println("Modified fast version: " + (double) modifiedFastVersion / 1000000000 + " s");
System.out.println("Modified slow version: " + (double) modifiedSlowVersion / 1000000000 + " s");
}
private static long fastVersion() {
long startTime = System.nanoTime();
int n = 0;
for (int i = 0; i < 1000000000; i++) {
n += 2 * (i * i);
}
return System.nanoTime() - startTime;
}
private static long slowVersion() {
long startTime = System.nanoTime();
int n = 0;
for (int i = 0; i < 1000000000; i++) {
n += 2 * i * i;
}
return System.nanoTime() - startTime;
}
private static long optimizedVersion() {
long startTime = System.nanoTime();
int n = 0;
for (int i = 0; i < 1000000000; i++) {
n += i * i;
}
n *= 2;
return System.nanoTime() - startTime;
}
private static long modifiedFastVersion() {
long startTime = System.nanoTime();
int n = 0;
for (int i = 0; i < 1000000000; i++) {
if (n == 0) n = 1;
n += 2 * (i * i);
}
return System.nanoTime() - startTime;
}
private static long modifiedSlowVersion() {
long startTime = System.nanoTime();
int n = 0;
for (int i = 0; i < 1000000000; i++) {
if (n == 0) n = 1;
n += 2 * i * i;
}
return System.nanoTime() - startTime;
}
결과는 다음과 같습니다.
Fast version: 5.7274411 s
Slow version: 7.6190804 s
Optimized version: 5.1348007 s
Modified fast version: 7.1492705 s
Modified slow version: 7.2952668 s
답변
바이트 코드 : https://cs.nyu.edu/courses/fall00/V22.0201-001/jvm2.html
바이트 코드 뷰어 : https://github.com/Konloch/bytecode-viewer
내 JDK (Windows 10 64 비트, 1.8.0_65-b17)에서 다음을 재현하고 설명 할 수 있습니다.
public static void main(String[] args) {
int repeat = 10;
long A = 0;
long B = 0;
for (int i = 0; i < repeat; i++) {
A += test();
B += testB();
}
System.out.println(A / repeat + " ms");
System.out.println(B / repeat + " ms");
}
private static long test() {
int n = 0;
for (int i = 0; i < 1000; i++) {
n += multi(i);
}
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
n += multi(i);
}
long ms = (System.currentTimeMillis() - startTime);
System.out.println(ms + " ms A " + n);
return ms;
}
private static long testB() {
int n = 0;
for (int i = 0; i < 1000; i++) {
n += multiB(i);
}
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
n += multiB(i);
}
long ms = (System.currentTimeMillis() - startTime);
System.out.println(ms + " ms B " + n);
return ms;
}
private static int multiB(int i) {
return 2 * (i * i);
}
private static int multi(int i) {
return 2 * i * i;
}
산출:
...
405 ms A 785527736
327 ms B 785527736
404 ms A 785527736
329 ms B 785527736
404 ms A 785527736
328 ms B 785527736
404 ms A 785527736
328 ms B 785527736
410 ms
333 ms
왜? 바이트 코드는 다음과 같습니다.
private static multiB(int arg0) { // 2 * (i * i)
<localVar:index=0, name=i , desc=I, sig=null, start=L1, end=L2>
L1 {
iconst_2
iload0
iload0
imul
imul
ireturn
}
L2 {
}
}
private static multi(int arg0) { // 2 * i * i
<localVar:index=0, name=i , desc=I, sig=null, start=L1, end=L2>
L1 {
iconst_2
iload0
imul
iload0
imul
ireturn
}
L2 {
}
}
차이점은 다음과 같습니다. 대괄호 ( 2 * (i * i)
) 사용 :
- const 구문을 푸시
- 스택에 로컬 푸시
- 스택에 로컬 푸시
- 스택 상단에 곱하기
- 스택 상단에 곱하기
대괄호 ( 2 * i * i
) 제외 :
- const 구문을 푸시
- 스택에 로컬 푸시
- 스택 상단에 곱하기
- 스택에 로컬 푸시
- 스택 상단에 곱하기
스택에 모두 넣은 다음 다시 작업하는 것이 스택에 놓고 스택에서 작동하는 것보다 빠릅니다.
답변
Kasperd 는 허용 된 답변에 대한 의견을 물었습니다.
Java와 C 예제는 상당히 다른 레지스터 이름을 사용합니다. 두 예 모두 AMD64 ISA를 사용하고 있습니까?
xor edx, edx
xor eax, eax
.L2:
mov ecx, edx
imul ecx, edx
add edx, 1
lea eax, [rax+rcx*2]
cmp edx, 1000000000
jne .L2
나는 의견에서 이것에 대답 할만 큼 평판이 좋지 않지만 이것들은 동일한 ISA입니다. GCC 버전은 32 비트 정수 로직을 사용하고 JVM 컴파일 버전은 64 비트 정수 로직을 내부적으로 사용한다는 점을 지적 할 가치가 있습니다.
R8 ~ R15는 새로운 X86_64 레지스터 입니다. EAX to EDX는 RAX to RDX 범용 레지스터의 하위 부분입니다. 대답의 중요한 부분은 GCC 버전이 풀리지 않는다는 것입니다. 실제 머신 코드 루프 당 하나의 루프 라운드 만 실행합니다. JVM 버전에는 하나의 물리적 루프에 16 라운드의 루프가 있지만 (rustyx 답변을 기반으로하지만 어셈블리를 재 해석하지 않았습니다). 루프 바디가 실제로 16 배 더 길기 때문에 더 많은 레지스터가 사용되는 이유 중 하나입니다.
답변
질문의 환경과 직접 관련이 없지만 호기심을 위해 .NET Core 2.1, x64 릴리스 모드에서 동일한 테스트를 수행했습니다.
흥미로운 결과는 힘의 어두운면에서 발생하는 비슷한 현상을 확인합니다. 암호:
static void Main(string[] args)
{
Stopwatch watch = new Stopwatch();
Console.WriteLine("2 * (i * i)");
for (int a = 0; a < 10; a++)
{
int n = 0;
watch.Restart();
for (int i = 0; i < 1000000000; i++)
{
n += 2 * (i * i);
}
watch.Stop();
Console.WriteLine($"result:{n}, {watch.ElapsedMilliseconds} ms");
}
Console.WriteLine();
Console.WriteLine("2 * i * i");
for (int a = 0; a < 10; a++)
{
int n = 0;
watch.Restart();
for (int i = 0; i < 1000000000; i++)
{
n += 2 * i * i;
}
watch.Stop();
Console.WriteLine($"result:{n}, {watch.ElapsedMilliseconds}ms");
}
}
결과:
2 * (i * i)
- 결과 : 119860736, 438ms
- 결과 : 119860736, 433ms
- 결과 : 119860736, 437ms
- 결과 : 119860736, 435ms
- 결과 : 119860736, 436ms
- 결과 : 119860736, 435ms
- 결과 : 119860736, 435ms
- 결과 : 119860736, 439ms
- 결과 : 119860736, 436ms
- 결과 : 119860736, 437ms
2 * 나는 * 나는
- 결과 : 119860736, 417ms
- 결과 : 119860736, 417ms
- 결과 : 119860736, 417ms
- 결과 : 119860736, 418ms
- 결과 : 119860736, 418ms
- 결과 : 119860736, 417ms
- 결과 : 119860736, 418ms
- 결과 : 119860736, 416ms
- 결과 : 119860736, 417ms
- 결과 : 119860736, 418ms
답변
비슷한 결과를 얻었습니다.
2 * (i * i): 0.458765943 s, n=119860736
2 * i * i: 0.580255126 s, n=119860736
두 루프가 동일한 프로그램에 있거나 각각 별도의 실행에서 실행되는 별도의 .java 파일 /.class에있는 경우 동일한 결과를 얻었습니다 .
마지막으로, javap -c -v <.java>
각각 의 디 컴파일이 있습니다 :
3: ldc #3 // String 2 * (i * i):
5: invokevirtual #4 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
8: invokestatic #5 // Method java/lang/System.nanoTime:()J
8: invokestatic #5 // Method java/lang/System.nanoTime:()J
11: lstore_1
12: iconst_0
13: istore_3
14: iconst_0
15: istore 4
17: iload 4
19: ldc #6 // int 1000000000
21: if_icmpge 40
24: iload_3
25: iconst_2
26: iload 4
28: iload 4
30: imul
31: imul
32: iadd
33: istore_3
34: iinc 4, 1
37: goto 17
vs.
3: ldc #3 // String 2 * i * i:
5: invokevirtual #4 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
8: invokestatic #5 // Method java/lang/System.nanoTime:()J
11: lstore_1
12: iconst_0
13: istore_3
14: iconst_0
15: istore 4
17: iload 4
19: ldc #6 // int 1000000000
21: if_icmpge 40
24: iload_3
25: iconst_2
26: iload 4
28: imul
29: iload 4
31: imul
32: iadd
33: istore_3
34: iinc 4, 1
37: goto 17
참고-
java -version
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
답변
Java 11을 사용한 흥미로운 관찰 과 다음 VM 옵션을 사용하여 루프 언 롤링을 끕니다.
-XX:LoopUnrollLimit=0
2 * (i * i)
표현식 이있는 루프 는보다 컴팩트 한 기본 코드 1을 생성합니다 .
L0001: add eax,r11d
inc r8d
mov r11d,r8d
imul r11d,r8d
shl r11d,1h
cmp r8d,r10d
jl L0001
2 * i * i
버전 과 비교하여 :
L0001: add eax,r11d
mov r11d,r8d
shl r11d,1h
add r11d,2h
inc r8d
imul r11d,r8d
cmp r8d,r10d
jl L0001
자바 버전 :
java version "11" 2018-09-25
Java(TM) SE Runtime Environment 18.9 (build 11+28)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11+28, mixed mode)
벤치 마크 결과 :
Benchmark (size) Mode Cnt Score Error Units
LoopTest.fast 1000000000 avgt 5 694,868 ± 36,470 ms/op
LoopTest.slow 1000000000 avgt 5 769,840 ± 135,006 ms/op
벤치 마크 소스 코드 :
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS)
@State(Scope.Thread)
@Fork(1)
public class LoopTest {
@Param("1000000000") private int size;
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(LoopTest.class.getSimpleName())
.jvmArgs("-XX:LoopUnrollLimit=0")
.build();
new Runner(opt).run();
}
@Benchmark
public int slow() {
int n = 0;
for (int i = 0; i < size; i++)
n += 2 * i * i;
return n;
}
@Benchmark
public int fast() {
int n = 0;
for (int i = 0; i < size; i++)
n += 2 * (i * i);
return n;
}
}
1-사용 된 VM 옵션 : -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:LoopUnrollLimit=0
