Saya menjalankan Windows 8.1 x64 dengan pembaruan Java 7 45 x64 (tidak ada Java 32 bit yang diinstal) di tablet Surface Pro 2.
Kode di bawah ini membutuhkan waktu 1688ms ketika tipe i adalah panjang dan 109ms ketika i adalah sebuah int. Mengapa long (tipe 64 bit) urutan besarnya lebih lambat daripada int pada platform 64 bit dengan 64 bit JVM?
Satu-satunya spekulasi saya adalah bahwa CPU membutuhkan waktu lebih lama untuk menambahkan integer 64 bit daripada yang 32 bit, tetapi itu tampaknya tidak mungkin. Saya menduga Haswell tidak menggunakan penambah ripple-carry.
Saya menjalankan ini di Eclipse Kepler 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;
}
}
Sunting: Berikut adalah hasil dari kode C ++ setara yang dikompilasi oleh VS 2013 (di bawah), sistem yang sama. panjang: 72265ms int: 74656ms Hasil tersebut dalam mode debug 32 bit.
Dalam mode rilis 64 bit: panjang: 875ms panjang panjang: 906ms int: 1047ms
Ini menunjukkan bahwa hasil yang saya amati adalah keanehan pengoptimalan JVM daripada batasan 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;
}
Edit: Coba ini lagi di Java 8 RTM, tidak ada perubahan signifikan.
sumber
currentTimeMillis()
, menjalankan kode yang dapat dengan mudah dioptimalkan sepenuhnya, dll. Berbau hasil yang tidak dapat diandalkan.long
sebagai penghitung loop, karena kompiler JIT mengoptimalkan loop keluar, ketika saya menggunakan fileint
. Seseorang perlu melihat pembongkaran kode mesin yang dihasilkan.Jawaban:
JVM saya melakukan hal yang cukup mudah ini ke loop dalam saat Anda menggunakan
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 */
Ini curang, sulit, saat Anda menggunakan
int
s; pertama-tama ada beberapa kekeliruan yang saya klaim tidak saya pahami tetapi sepertinya pengaturan untuk loop yang tidak diputar: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 */
lalu loop yang tidak digulung itu sendiri:
0x00007f3dc290b640: add $0xfffffffffffffff0,%ecx 0x00007f3dc290b643: mov %ecx,0x258(%r10) 0x00007f3dc290b64a: cmp %r11d,%ecx 0x00007f3dc290b64d: jg 0x00007f3dc290b640
kemudian kode pembongkaran untuk loop yang tidak digulung, itu sendiri merupakan tes dan loop lurus:
0x00007f3dc290b64f: cmp $0xffffffffffffffff,%ecx 0x00007f3dc290b652: jle 0x00007f3dc290b662 0x00007f3dc290b654: dec %ecx 0x00007f3dc290b656: mov %ecx,0x258(%r10) 0x00007f3dc290b65d: cmp $0xffffffffffffffff,%ecx 0x00007f3dc290b660: jg 0x00007f3dc290b654
Jadi itu berjalan 16 kali lebih cepat untuk int karena JIT membuka gulungan
int
loop 16 kali, tetapi tidak membuka gulunganlong
sama sekali.Untuk kelengkapannya, berikut ini kode yang sebenarnya saya coba:
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; } }
Tempat pembuangan perakitan dibuat menggunakan opsi
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
. Perhatikan bahwa Anda perlu mengotak-atik instalasi JVM Anda agar ini berfungsi untuk Anda juga; Anda perlu meletakkan beberapa pustaka bersama secara acak di tempat yang tepat atau itu akan gagal.sumber
long
versinya lebih lambat, tetapiint
versinya lebih cepat. Itu masuk akal. Kemungkinan tidak banyak upaya yang diinvestasikan dalam membuatlong
ekspresi JIT mengoptimalkan .gcc
digunakan-f
sebagai saklar baris perintah untuk "bendera", danunroll-loops
pengoptimalan dihidupkan dengan mengatakan-funroll-loops
. Saya hanya menggunakan "buka gulungan" untuk menjelaskan pengoptimalan.i-=16
, yang tentu saja 16x lebih cepat.Tumpukan JVM didefinisikan dalam istilah kata - kata , yang ukurannya adalah detail implementasi tetapi harus memiliki lebar setidaknya 32 bit. Pelaksana JVM dapat menggunakan kata 64-bit, tetapi bytecode tidak dapat mengandalkan ini, sehingga operasi dengan nilai
long
ataudouble
harus ditangani dengan ekstra hati-hati. Secara khusus, instruksi cabang integer JVM didefinisikan tepat pada jenisnyaint
.Dalam kasus kode Anda, pembongkaran bersifat instruktif. Berikut bytecode untuk
int
versi yang dikompilasi oleh 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
Perhatikan bahwa JVM akan memuat nilai statis Anda
i
(0), kurangi satu (3-4), duplikat nilai pada tumpukan (5), dan dorong kembali ke variabel (6). Ia kemudian melakukan perbandingan-dengan-nol cabang dan kembali.Versi dengan
long
sedikit lebih rumit: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
Pertama, ketika JVM menduplikasi nilai baru pada stack (5), JVM harus menduplikasi dua stack word. Dalam kasus Anda, sangat mungkin bahwa ini tidak lebih mahal daripada menduplikasi satu, karena JVM bebas menggunakan kata 64-bit jika nyaman. Namun, Anda akan melihat bahwa logika cabang lebih panjang di sini. JVM tidak memiliki instruksi untuk membandingkan
long
dengan nol, sehingga memiliki untuk mendorong konstan0L
ke dalam stack (9), melakukan umumlong
perbandingan (10), dan kemudian cabang pada nilai yang perhitungan.Berikut dua skenario yang masuk akal:
long
versi, mendorong dan memunculkan beberapa nilai tambahan, dan ini ada di tumpukan terkelola virtual , bukan tumpukan CPU yang dibantu perangkat keras yang sebenarnya. Jika demikian, Anda masih akan melihat perbedaan kinerja yang signifikan setelah pemanasan.Saya sarankan Anda menulis microbenchmark yang benar untuk menghilangkan efek memiliki JIT kick in, dan juga mencoba ini dengan kondisi akhir yang tidak nol, untuk memaksa JVM melakukan perbandingan yang sama pada
int
yang dilakukannya denganlong
.sumber
== 0
, yang tampaknya merupakan bagian besar yang tidak proporsional dari hasil benchmark. Menurut saya, OP mencoba mengukur rentang operasi yang lebih umum, dan jawaban ini menunjukkan bahwa tolok ukur sangat condong ke salah satu operasi tersebut.Unit dasar data di Java Virtual Machine adalah kata. Memilih ukuran kata yang tepat ditinggalkan setelah implementasi JVM. Implementasi JVM harus memilih ukuran kata minimal 32 bit. Itu dapat memilih ukuran kata yang lebih tinggi untuk mendapatkan efisiensi. Tidak ada batasan bahwa 64 bit JVM harus memilih 64 bit word saja.
Arsitektur yang mendasari tidak mengatur bahwa ukuran kata juga harus sama. JVM membaca / menulis data kata demi kata. Inilah alasan mengapa mungkin butuh waktu lebih lama untuk waktu yang lama daripada int .
Di sini Anda dapat menemukan lebih banyak tentang topik yang sama.
sumber
Saya baru saja menulis patokan menggunakan caliper .
The hasil yang cukup konsisten dengan kode asli: 12x speedup ~ untuk menggunakan
int
lebihlong
. Tampaknya loop membuka gulungan yang dilaporkan oleh tmyklebu atau sesuatu yang sangat mirip sedang terjadi.timeIntDecrements 195,266,845.000 timeLongDecrements 2,321,447,978.000
Ini adalah kode saya; perhatikan bahwa ia menggunakan snapshot yang baru dibuat
caliper
, karena saya tidak tahu cara membuat kode terhadap rilis beta yang ada.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; } }
sumber
Sebagai catatan, versi ini melakukan "pemanasan" yang kasar:
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; } }
Waktu keseluruhan meningkat sekitar 30%, tetapi rasio antara keduanya kurang lebih tetap sama.
sumber
int
20ish kali lebih cepat) dengan kode ini.Sebagai catatan:
jika saya menggunakan
boolean decrementAndCheckLong() { lo = lo - 1l; return lo < -1l; }
(mengubah "l--" menjadi "l = l - 1l") performa lama meningkat ~ 50%
sumber
Saya tidak memiliki mesin 64 bit untuk diuji, tetapi perbedaan yang agak besar menunjukkan bahwa ada lebih dari sekedar bytecode yang sedikit lebih panjang di tempat kerja.
Saya melihat waktu yang sangat dekat untuk long / int (4400 vs 4800ms) pada 1.7.0_45 32-bit saya.
Ini hanya tebakan , tetapi saya sangat curiga bahwa ini adalah efek dari hukuman misalignment memory. Untuk mengkonfirmasi / menyangkal kecurigaan, coba tambahkan public static int dummy = 0; sebelum deklarasi i. Itu akan mendorong saya ke bawah sebesar 4 byte dalam tata letak memori dan dapat membuatnya selaras dengan benar untuk kinerja yang lebih baik.Dipastikan tidak menyebabkan masalah.EDIT:
Alasan di balik ini adalah bahwa VM tidak dapat menyusun ulang kolom dengan mudah menambahkan padding untuk penyelarasan yang optimal, karena hal itu dapat mengganggu JNI.(Bukan kasusnya).sumber