Di java, apakah lebih efisien menggunakan byte atau short daripada int dan float daripada double?

91

Saya perhatikan saya selalu menggunakan int dan doubles tidak peduli seberapa kecil atau besar angkanya. Jadi di java, apakah lebih efisien untuk digunakan byteatau shortdaripada intdan floatdaripada double?

Jadi asumsikan saya memiliki program dengan banyak int dan ganda. Akankah layak untuk melalui dan mengubah int saya menjadi byte atau short jika saya tahu jumlahnya akan cocok?

Saya tahu java tidak memiliki tipe unsigned tetapi adakah hal tambahan yang dapat saya lakukan jika saya tahu angkanya hanya akan positif?

Yang saya maksud dengan efisien adalah pemrosesan. Saya berasumsi bahwa pengumpul sampah akan jauh lebih cepat jika semua variabel berukuran setengah dan perhitungan itu mungkin juga akan lebih cepat. (Saya kira karena saya sedang mengerjakan android, saya perlu sedikit khawatir tentang ram juga)

(Saya akan berasumsi pengumpul sampah hanya berurusan dengan Objek dan bukan primitif tetapi masih menghapus semua primitif di objek yang ditinggalkan, kan?)

Saya mencobanya dengan aplikasi android kecil yang saya miliki tetapi tidak benar-benar melihat perbedaannya sama sekali. (Meskipun saya tidak "secara ilmiah" mengukur apa pun.)

Apakah saya salah dalam menganggap itu harus lebih cepat dan lebih efisien? Saya benci menjalani dan mengubah segalanya dalam program besar untuk mengetahui bahwa saya telah membuang-buang waktu.

Apakah ini layak dilakukan dari awal ketika saya memulai proyek baru? (Maksud saya, saya pikir setiap sedikit akan membantu tetapi sekali lagi jika demikian, mengapa sepertinya tidak ada yang melakukannya.)

DisibioAaron
sumber

Jawaban:

109

Apakah saya salah dalam menganggap itu harus lebih cepat dan lebih efisien? Saya benci menjalani dan mengubah segalanya dalam program besar untuk mengetahui bahwa saya telah membuang-buang waktu.

Jawaban singkat

Ya kamu salah Dalam kebanyakan kasus, tidak ada bedanya dalam hal ruang yang digunakan.

Tidak ada gunanya mencoba mengoptimalkan ini ... kecuali Anda memiliki bukti jelas bahwa pengoptimalan diperlukan. Dan jika Anda perlu mengoptimalkan penggunaan memori bidang objek secara khusus, Anda mungkin perlu melakukan tindakan lain (yang lebih efektif).

Jawaban yang lebih panjang

Java Virtual Machine memodelkan tumpukan dan bidang objek menggunakan offset yang (berlaku) kelipatan dari ukuran sel primitif 32 bit. Jadi ketika Anda mendeklarasikan variabel lokal atau bidang objek sebagai (katakanlah) a byte, variabel / bidang akan disimpan dalam sel 32 bit, seperti file int.

Ada dua pengecualian untuk ini:

  • longdan doublenilai membutuhkan 2 sel 32-bit primitif
  • array tipe primitif direpresentasikan dalam bentuk yang dikemas, sehingga (misalnya) array byte menampung 4 byte per 32bit kata.

Jadi mungkin perlu untuk mengoptimalkan penggunaan longdan double... dan array primitif yang besar. Tapi secara umum tidak.

Secara teori, JIT mungkin dapat mengoptimalkan ini, tetapi dalam praktiknya saya belum pernah mendengar JIT yang melakukannya. Salah satu hambatannya adalah bahwa JIT biasanya tidak dapat berjalan sampai ada instance kelas yang sedang dikompilasi telah dibuat. Jika JIT mengoptimalkan tata letak memori, Anda dapat memiliki dua (atau lebih) "ragam" objek dari kelas yang sama ... dan itu akan menimbulkan kesulitan besar.


Revisitasi

Melihat hasil benchmark dalam jawaban @ meriton, tampaknya menggunakan shortdan bytealih-alih intmenimbulkan penalti kinerja untuk perkalian. Memang, jika Anda menganggap operasinya terisolasi, hukumannya signifikan. (Anda seharusnya tidak menganggap mereka dalam isolasi ... tapi itu topik lain.)

Saya pikir penjelasannya adalah bahwa JIT mungkin melakukan perkalian menggunakan instruksi perkalian 32bit dalam setiap kasus. Tetapi dalam kasus bytedan short, ia mengeksekusi instruksi tambahan untuk mengubah nilai perantara 32 bit menjadi byteatau shortdi setiap iterasi loop. (Secara teori, konversi itu bisa dilakukan sekali di akhir loop ... tapi saya ragu pengoptimal akan bisa mengetahuinya.)

Bagaimanapun, ini menunjukkan masalah lain dengan beralih ke shortdan bytesebagai pengoptimalan. Ini bisa membuat kinerja buruk ... di suatu algoritma yang aritmatika dan menghitung intensif.

Stephen C
sumber
30
+1 tidak dioptimalkan kecuali Anda memiliki bukti yang jelas tentang masalah kinerja
Bohemian
Erm, mengapa JVM harus menunggu kompilasi JIT untuk mengemas tata letak memori kelas? Karena jenis bidang ditulis ke file kelas, tidak bisakah JVM memilih tata letak memori pada waktu pemuatan kelas, lalu menyelesaikan nama bidang sebagai byte daripada offset kata?
meriton
@meriton - Aku cukup yakin bahwa tata letak objek yang ditentukan pada waktu beban kelas, dan mereka tidak berubah setelah itu. Lihat bagian "cetak halus" dari jawaban saya. Jika tata letak memori yang sebenarnya berubah ketika kode itu JITed, akan sangat sulit bagi JVM untuk menangani. (Ketika saya mengatakan JIT mungkin mengoptimalkan tata letak, itu hipotetis dan tidak praktis ... yang bisa menjelaskan mengapa saya belum pernah mendengar JIT benar-benar melakukannya.)
Stephen C
Aku tahu. Saya hanya mencoba menunjukkan bahwa meskipun tata letak memori sulit untuk diubah setelah objek dibuat, JVM mungkin masih mengoptimalkan tata letak memori sebelum itu, yaitu pada waktu buka kelas. Dengan kata lain, bahwa spesifikasi JVM menggambarkan perilaku JVM dengan offset kata tidak selalu berarti bahwa JVM harus diimplementasikan dengan cara itu - meskipun kemungkinan besar demikian.
Meriton
@meriton - Spesifikasi JVM berbicara tentang "penyeimbangan kata mesin virtual" dalam bingkai / objek lokal. Bagaimana ini dipetakan ke offset mesin fisik TIDAK ditentukan. Memang, itu tidak dapat menentukannya ... karena mungkin ada persyaratan penyelarasan bidang khusus perangkat keras.
Stephen C
29

Itu tergantung pada implementasi JVM, serta perangkat keras yang mendasarinya. Kebanyakan perangkat keras modern tidak akan mengambil byte tunggal dari memori (atau bahkan dari cache tingkat pertama), yaitu menggunakan tipe primitif yang lebih kecil umumnya tidak mengurangi konsumsi bandwidth memori. Demikian juga, CPU modern memiliki ukuran word 64 bit. Mereka dapat melakukan operasi pada lebih sedikit bit, tetapi itu bekerja dengan membuang bit ekstra, yang juga tidak lebih cepat.

Satu-satunya keuntungan adalah tipe primitif yang lebih kecil dapat menghasilkan tata letak memori yang lebih ringkas, terutama saat menggunakan array. Ini menghemat memori, yang dapat meningkatkan lokalitas referensi (sehingga mengurangi jumlah cache yang hilang) dan mengurangi overhead pengumpulan sampah.

Namun secara umum, menggunakan tipe primitif yang lebih kecil tidak lebih cepat.

Untuk mendemonstrasikannya, perhatikan tolok ukur berikut:

package tools.bench;

import java.math.BigDecimal;

public abstract class Benchmark {

    final String name;

    public Benchmark(String name) {
        this.name = name;
    }

    abstract int run(int iterations) throws Throwable;

    private BigDecimal time() {
        try {
            int nextI = 1;
            int i;
            long duration;
            do {
                i = nextI;
                long start = System.nanoTime();
                run(i);
                duration = System.nanoTime() - start;
                nextI = (i << 1) | 1; 
            } while (duration < 100000000 && nextI > 0);
            return new BigDecimal((duration) * 1000 / i).movePointLeft(3);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }   

    @Override
    public String toString() {
        return name + "\t" + time() + " ns";
    }

    public static void main(String[] args) throws Exception {
        Benchmark[] benchmarks = {
            new Benchmark("int multiplication") {
                @Override int run(int iterations) throws Throwable {
                    int x = 1;
                    for (int i = 0; i < iterations; i++) {
                        x *= 3;
                    }
                    return x;
                }
            },
            new Benchmark("short multiplication") {                   
                @Override int run(int iterations) throws Throwable {
                    short x = 0;
                    for (int i = 0; i < iterations; i++) {
                        x *= 3;
                    }
                    return x;
                }
            },
            new Benchmark("byte multiplication") {                   
                @Override int run(int iterations) throws Throwable {
                    byte x = 0;
                    for (int i = 0; i < iterations; i++) {
                        x *= 3;
                    }
                    return x;
                }
            },
            new Benchmark("int[] traversal") {                   
                @Override int run(int iterations) throws Throwable {
                    int[] x = new int[iterations];
                    for (int i = 0; i < iterations; i++) {
                        x[i] = i;
                    }
                    return x[x[0]];
                }
            },
            new Benchmark("short[] traversal") {                   
                @Override int run(int iterations) throws Throwable {
                    short[] x = new short[iterations];
                    for (int i = 0; i < iterations; i++) {
                        x[i] = (short) i;
                    }
                    return x[x[0]];
                }
            },
            new Benchmark("byte[] traversal") {                   
                @Override int run(int iterations) throws Throwable {
                    byte[] x = new byte[iterations];
                    for (int i = 0; i < iterations; i++) {
                        x[i] = (byte) i;
                    }
                    return x[x[0]];
                }
            },
        };
        for (Benchmark bm : benchmarks) {
            System.out.println(bm);
        }
    }
}

yang dicetak di buku catatan saya yang agak lama (menambahkan spasi untuk menyesuaikan kolom):

int       multiplication    1.530 ns
short     multiplication    2.105 ns
byte      multiplication    2.483 ns
int[]     traversal         5.347 ns
short[]   traversal         4.760 ns
byte[]    traversal         2.064 ns

Seperti yang Anda lihat, perbedaan kinerja cukup kecil. Mengoptimalkan algoritma jauh lebih penting daripada pilihan tipe primitif.

manfaat
sumber
3
Daripada mengatakan "paling menonjol saat menggunakan array", saya pikir mungkin lebih sederhana untuk mengatakannya shortdan bytelebih efisien bila disimpan dalam array yang cukup besar untuk masalah (semakin besar array, semakin besar perbedaan efisiensi; a byte[2]mungkin lebih atau kurang efisien daripada int[2], tetapi tidak cukup untuk menjadi masalah dengan cara apa pun), tetapi nilai-nilai individual disimpan secara lebih efisien sebagai int.
supercat
2
Apa yang saya periksa: Tolok ukur tersebut selalu menggunakan int ('3') sebagai faktor atau operan tugas (varian loop, kemudian dicor). Apa yang saya lakukan adalah menggunakan faktor yang diketik / operan penugasan tergantung pada jenis nilai l: int mult 76.481 ns int mult (diketik) 72.581 ns mult pendek 87.908 ns mult pendek (diketik) 90.772 ns byte mult 87.859 ns byte mult (diketik) 89.524 ns int [] trav 88.905 ns int [] trav (diketik) 89.126 ns short [] trav 10.563 ns short [] trav (typed) 10.039 ns byte [] trav 8.356 ns byte [] trav (typed) 8.338 ns Saya rasa ada banyak casting yang tidak perlu. tes tersebut dijalankan pada tab android.
Bondax
5

Menggunakan bytealih-alih intdapat meningkatkan kinerja jika Anda menggunakannya dalam jumlah besar. Ini eksperimennya:

import java.lang.management.*;

public class SpeedTest {

/** Get CPU time in nanoseconds. */
public static long getCpuTime() {
    ThreadMXBean bean = ManagementFactory.getThreadMXBean();
    return bean.isCurrentThreadCpuTimeSupported() ? bean
            .getCurrentThreadCpuTime() : 0L;
}

public static void main(String[] args) {
    long durationTotal = 0;
    int numberOfTests=0;

    for (int j = 1; j < 51; j++) {
        long beforeTask = getCpuTime();
        // MEASURES THIS AREA------------------------------------------
        long x = 20000000;// 20 millions
        for (long i = 0; i < x; i++) {
                           TestClass s = new TestClass(); 

        }
        // MEASURES THIS AREA------------------------------------------
        long duration = getCpuTime() - beforeTask;
        System.out.println("TEST " + j + ": duration = " + duration + "ns = "
                + (int) duration / 1000000);
        durationTotal += duration;
        numberOfTests++;
    }
    double average = durationTotal/numberOfTests;
    System.out.println("-----------------------------------");
    System.out.println("Average Duration = " + average + " ns = "
            + (int)average / 1000000 +" ms (Approximately)");


}

}

Kelas ini menguji kecepatan pembuatan file TestClass. Setiap tes melakukannya 20 juta kali dan ada 50 tes.

Inilah TestClass:

 public class TestClass {
     int a1= 5;
     int a2= 5; 
     int a3= 5;
     int a4= 5; 
     int a5= 5;
     int a6= 5; 
     int a7= 5;
     int a8= 5; 
     int a9= 5;
     int a10= 5; 
     int a11= 5;
     int a12=5; 
     int a13= 5;
     int a14= 5; 
 }

Saya telah menjalankan SpeedTestkelas dan pada akhirnya mendapatkan ini:

 Average Duration = 8.9625E8 ns = 896 ms (Approximately)

Sekarang saya mengubah int menjadi byte di TestClass dan menjalankannya lagi. Inilah hasilnya:

 Average Duration = 6.94375E8 ns = 694 ms (Approximately)

Saya percaya percobaan ini menunjukkan bahwa jika Anda menggunakan sejumlah besar variabel, menggunakan byte alih-alih int dapat meningkatkan efisiensi

WVrock
sumber
4
Perhatikan bahwa tolok ukur ini hanya mengukur biaya yang terkait dengan alokasi dan konstruksi, dan hanya kasus kelas dengan banyak bidang individu. Jika operasi aritmatika / pembaruan dilakukan di lapangan, hasil @ meriton menunjukkan bahwa bytebisa >> lebih lambat << dari int.
Stephen C
Benar, saya seharusnya mengatakannya lebih baik untuk mengklarifikasi.
WVrock
2

byte umumnya dianggap 8 bit. pendek umumnya dianggap 16 bit.

Dalam lingkungan "murni", yang bukan java karena semua implementasi byte dan long, dan short, dan hal menyenangkan lainnya biasanya tersembunyi dari Anda, byte memanfaatkan ruang dengan lebih baik.

Namun, komputer Anda mungkin bukan 8 bit, dan mungkin juga bukan 16 bit. ini berarti bahwa untuk mendapatkan 16 atau 8 bit secara khusus, perlu menggunakan "tipu muslihat" yang membuang-buang waktu untuk berpura-pura memiliki kemampuan untuk mengakses jenis-jenis tersebut bila diperlukan.

Pada titik ini, itu tergantung pada bagaimana perangkat keras diimplementasikan. Namun dari yang pernah saya pikirkan, kecepatan terbaik dicapai dari menyimpan hal-hal dalam potongan yang nyaman untuk digunakan CPU Anda. Prosesor 64 bit suka berurusan dengan elemen 64 bit, dan apa pun yang kurang dari itu sering kali memerlukan "keajaiban teknik" untuk berpura-pura suka berurusan dengan elemen tersebut.

Dmitry
sumber
3
Saya tidak yakin apa yang Anda maksud dengan "sihir teknik" ... kebanyakan / semua prosesor modern memiliki instruksi cepat untuk memuat byte dan memperpanjangnya, untuk menyimpan satu dari register lebar penuh, dan melakukan lebar byte atau aritmetika lebar pendek dalam porsi register lebar penuh. Jika Anda benar, akan masuk akal, jika memungkinkan, untuk mengganti semua int dengan long pada prosesor 64-bit.
Ed Staub
Saya bisa membayangkan itu benar. Saya hanya ingat bahwa di simulator Motorola 68k yang kami gunakan, sebagian besar operasi dapat bekerja dengan nilai 16 bit sementara tidak dengan 32 bit atau 64 bit. Saya berpikir bahwa ini berarti bahwa sistem memiliki ukuran nilai yang disukai sehingga dapat diambil secara optimal. Meskipun saya dapat membayangkan bahwa prosesor 64bit modern dapat mengambil 8bit, 16 bit, 32bit, dan 64bit dengan kemudahan yang sama, dalam hal ini ini bukan masalah. Terima kasih telah menunjukkannya.
Dmitry
"... umumnya dianggap ..." - Sebenarnya, secara jelas, dengan jelas >> ditentukan << sebagai ukuran tersebut. Di Jawa. Dan konteks dari pertanyaan ini adalah Java.
Stephen C
Sejumlah besar prosesor bahkan menggunakan jumlah siklus yang sama untuk memanipulasi dan mengakses data yang tidak berukuran kata, jadi tidak perlu dikhawatirkan kecuali Anda mengukur pada JVM dan platform tertentu.
drrob
Saya mencoba untuk mengatakannya secara umum. Yang mengatakan saya tidak benar-benar yakin tentang standar Java sehubungan dengan ukuran byte, tetapi pada titik ini saya cukup yakin bahwa jika ada bidat yang memutuskan bukan byte 8 bit, Java tidak akan ingin menyentuhnya dengan tiang sepuluh kaki. Namun, beberapa prosesor memerlukan penyelarasan multibyte, dan jika platform Java mendukungnya, itu perlu melakukan hal-hal yang lebih lambat untuk mengakomodasi menangani jenis yang lebih kecil ini, atau secara ajaib merepresentasikannya dengan representasi yang lebih besar dari yang Anda minta. Itu selalu lebih suka int daripada tipe lain karena selalu menggunakan ukuran favorit sistem.
Dmitry
2

Salah satu alasan short / byte / char menjadi kurang berkinerja adalah kurangnya dukungan langsung untuk tipe data ini. Dengan dukungan langsung, artinya, spesifikasi JVM tidak menyebutkan set instruksi apa pun untuk tipe data ini. Instruksi seperti menyimpan, memuat, menambahkan, dll. Memiliki versi untuk tipe data int. Tetapi mereka tidak memiliki versi untuk pendek / byte / char. Misalnya pertimbangkan kode java di bawah ini:

void spin() {
 int i;
 for (i = 0; i < 100; i++) {
 ; // Loop body is empty
 }
}

Hal yang sama diubah menjadi kode mesin seperti di bawah ini.

0 iconst_0 // Push int constant 0
1 istore_1 // Store into local variable 1 (i=0)
2 goto 8 // First time through don't increment
5 iinc 1 1 // Increment local variable 1 by 1 (i++)
8 iload_1 // Push local variable 1 (i)
9 bipush 100 // Push int constant 100
11 if_icmplt 5 // Compare and loop if less than (i < 100)
14 return // Return void when done

Sekarang, pertimbangkan untuk mengubah int menjadi pendek seperti di bawah ini.

void sspin() {
 short i;
 for (i = 0; i < 100; i++) {
 ; // Loop body is empty
 }
}

Kode mesin yang sesuai akan berubah sebagai berikut:

0 iconst_0
1 istore_1
2 goto 10
5 iload_1 // The short is treated as though an int
6 iconst_1
7 iadd
8 i2s // Truncate int to short
9 istore_1
10 iload_1
11 bipush 100
13 if_icmplt 5
16 return

Seperti yang bisa Anda amati, untuk memanipulasi tipe data pendek, ia masih menggunakan versi instruksi tipe data int dan secara eksplisit mengubah int menjadi short bila diperlukan. Sekarang, karena ini, kinerjanya berkurang.

Nah, alasan yang dikutip untuk tidak memberikan dukungan langsung sebagai berikut:

Mesin Virtual Java menyediakan dukungan paling langsung untuk data bertipe int. Ini sebagian untuk mengantisipasi implementasi yang efisien dari tumpukan operan Java Virtual Machine dan array variabel lokal. Hal ini juga dimotivasi oleh frekuensi data int dalam program yang khas. Jenis integral lainnya memiliki dukungan yang kurang langsung. Tidak ada versi byte, char, atau pendek dari penyimpanan, memuat, atau menambahkan instruksi, misalnya.

Dikutip dari spesifikasi JVM yang hadir di sini (Halaman 58).

Manish Bansal
sumber
Ini dibongkar bytecode; yaitu instruksi virtual JVM . Mereka tidak dioptimalkan oleh javackompiler, dan Anda tidak dapat menarik kesimpulan yang dapat diandalkan darinya tentang bagaimana program akan bekerja dalam kehidupan nyata. Kompilator JIT mengkompilasi bytecode ini ke instruksi mesin asli yang sebenarnya , dan melakukan beberapa pengoptimalan yang cukup serius dalam prosesnya. Jika Anda ingin menganalisis kinerja kode, Anda perlu memeriksa instruksi kode asli. (Dan ini rumit karena Anda perlu mempertimbangkan perilaku pengaturan waktu dari pipeline x86_64 multi-tahap.)
Stephen C
Saya yakin spesifikasi java adalah untuk implementor javac untuk diterapkan. Jadi saya rasa tidak ada lagi optimisasi yang dilakukan di level itu. Bagaimanapun, saya juga bisa sepenuhnya salah. Silakan bagikan beberapa tautan referensi untuk mendukung pernyataan Anda.
Manish Bansal
Nah, inilah satu fakta untuk mendukung pernyataan saya. Anda tidak akan menemukan angka waktu (kredibel) yang memberi tahu Anda berapa banyak siklus clock yang dibutuhkan setiap instruksi bytecode JVM. Tentu tidak dipublikasikan oleh Oracle atau pemasok JVM lainnya. Juga, baca stackoverflow.com/questions/1397009
Stephen C
Saya menemukan makalah lama (2008) di mana seseorang mencoba mengembangkan model platform independen untuk memprediksi kinerja urutan bytecode. Mereka mengklaim bahwa prediksi mereka meleset sebesar 25% dibandingkan dengan pengukuran RDTSC .... pada Pentium. Dan mereka menjalankan JVM dengan kompilasi JIT dinonaktifkan! Referensi: sciencedirect.com/science/article/pii/S1571066108004581
Stephen C
Saya hanya bingung disini. Bukankah jawaban saya mendukung fakta yang Anda nyatakan di bagian revisitasi?
Manish Bansal
0

Perbedaannya hampir tidak terlihat! Ini lebih merupakan masalah desain, kesesuaian, keseragaman, kebiasaan, dll ... Terkadang itu hanya masalah selera. Ketika semua yang Anda pedulikan adalah bahwa program Anda bangun dan berjalan dan mengganti dengan floatyang inttidak akan merugikan kebenaran, saya melihat tidak ada keuntungan dalam pergi untuk satu atau yang lain kecuali Anda dapat menunjukkan bahwa menggunakan salah satu jenis mengubah kinerja. Performa tuning berdasarkan jenis yang berbeda dalam 2 atau 3 byte benar-benar hal terakhir yang harus Anda perhatikan; Donald Knuth pernah berkata: "Optimasi prematur adalah akar dari segala kejahatan" (tidak yakin itu dia, edit jika Anda punya jawabannya).

mrk
sumber
5
Nit: A float tidak bisa mewakili semua bilangan bulat intkaleng; juga tidak bisa intmewakili nilai non-integer yang floatbisa. Artinya, meskipun semua nilai int adalah himpunan bagian dari nilai panjang, int bukan bagian dari float dan float bukan bagian dari int.
Saya berharap penjawab bermaksud menulis substituting a float for a double, jika demikian penjawab harus mengedit jawabannya. Jika tidak penjawab harus menundukkan kepala karena malu dan kembali ke dasar untuk alasan yang diuraikan oleh @pst dan untuk banyak alasan lainnya.
Kinerja Tinggi Mark
@HighPerformanceMark Tidak, saya memasukkan int dan float karena itulah yang saya pikirkan. Jawaban saya tidak spesifik untuk Java meskipun saya berpikir C ... Ini dimaksudkan untuk umum. Komentar jahat yang Anda dapatkan di sana.
Tuan