Perbedaan antara mendeklarasikan variabel sebelum atau dalam lingkaran?


312

Saya selalu bertanya-tanya apakah, secara umum, mendeklarasikan variabel throw-away sebelum loop, sebagai lawan berulang kali di dalam loop, membuat perbedaan (kinerja)? Contoh (sangat tidak berguna) di Jawa:

a) deklarasi sebelum loop:

double intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

b) deklarasi (berulang kali) di dalam loop:

for(int i=0; i < 1000; i++){
    double intermediateResult = i;
    System.out.println(intermediateResult);
}

Mana yang lebih baik, a atau b ?

Saya menduga bahwa deklarasi variabel berulang (contoh b ) menciptakan lebih banyak overhead dalam teori , tetapi kompiler cukup pintar sehingga tidak masalah. Contoh b memiliki keuntungan menjadi lebih kompak dan membatasi ruang lingkup variabel ke tempat variabel itu digunakan. Namun, saya cenderung memberi kode sesuai contoh a .

Sunting: Saya sangat tertarik dengan kasus Java.


Ini penting ketika menulis kode Java untuk platform Android. Google menyarankan agar kode kritis-waktu untuk mendeklarasikan variabel tambahan di luar for-loop, seolah-olah di dalam for-loop, ia menyatakannya kembali setiap kali di lingkungan itu. Perbedaan kinerja sangat nyata untuk algoritma yang mahal.
AaronCarson

1
@AaronCarson dapatkah Anda memberikan tautan ke saran ini oleh Google
Vitaly Zinchenko

Jawaban:


256

Mana yang lebih baik, a atau b ?

Dari perspektif kinerja, Anda harus mengukurnya. (Dan menurut saya, jika Anda dapat mengukur perbedaan, kompilernya tidak terlalu bagus).

Dari perspektif pemeliharaan, b lebih baik. Deklarasikan dan inisialisasi variabel di tempat yang sama, dalam ruang lingkup sesempit mungkin. Jangan meninggalkan celah menganga antara deklarasi dan inisialisasi, dan jangan mencemari ruang nama yang tidak perlu.


5
Alih-alih Double, jika berurusan dengan String, masih huruf "b" lebih baik?
Antoops

3
@Antoops - ya, b lebih baik karena alasan yang tidak ada hubungannya dengan tipe data dari variabel yang dideklarasikan. Mengapa berbeda untuk Strings?
Daniel Earwicker

215

Yah saya menjalankan contoh A dan B Anda masing-masing 20 kali, mengulang 100 juta kali. (JVM - 1.5.0)

A: waktu eksekusi rata-rata: 0,074 detik

B: waktu eksekusi rata-rata: 0,067 dtk

Yang mengejutkan saya, B sedikit lebih cepat. Secepat komputer sekarang sulit untuk mengatakan apakah Anda dapat mengukur ini dengan akurat. Saya akan mengkodekannya dengan cara A juga tetapi saya akan mengatakan itu tidak masalah.


12
Anda mengalahkan saya, saya baru saja memposting hasil saya untuk profil, saya mendapat kurang lebih sama dan ya mengejutkan B lebih cepat benar-benar akan berpikir A jika saya harus bertaruh.
Mark Davidson

14
Tidak banyak kejutan - ketika variabel lokal ke loop, itu tidak perlu dipertahankan setelah setiap iterasi, sehingga bisa tetap dalam register.

142
+1 untuk benar - benar mengujinya , bukan hanya pendapat / teori yang bisa dibuat OP sendiri.
MGOwen

3
@ GoodPerson jujur, saya ingin itu dilakukan. Saya menjalankan tes ini sekitar 10 kali pada mesin saya untuk 50.000.000-100.000.000 pengulangan dengan kode yang hampir identik (yang ingin saya bagikan kepada siapa pun yang ingin menjalankan statistik). Jawabannya terbagi hampir sama, biasanya dengan selisih 900ms (lebih dari 50 juta iterasi) yang tidak terlalu banyak. Meskipun pemikiran pertama saya adalah bahwa itu akan menjadi "kebisingan", itu mungkin bersandar sedikit. Upaya ini tampaknya murni bersifat akademis bagi saya (untuk sebagian besar aplikasi kehidupan nyata) .. Saya tetap ingin melihat hasilnya;) Adakah yang setuju?
jrefz

3
Menampilkan hasil pengujian tanpa mendokumentasikan pengaturan, tidak ada gunanya. Itu terutama benar dalam kasus ini, di mana kedua fragmen kode menghasilkan bytecode yang identik, sehingga setiap perbedaan yang diukur hanyalah tanda dari kondisi pengujian yang tidak memadai.
Holger

66

Itu tergantung pada bahasa dan penggunaan yang tepat. Misalnya, dalam C # 1 tidak ada bedanya. Dalam C # 2, jika variabel lokal ditangkap oleh metode anonim (atau ekspresi lambda dalam C # 3) itu dapat membuat perbedaan yang sangat signifikan.

Contoh:

using System;
using System.Collections.Generic;

class Test
{
    static void Main()
    {
        List<Action> actions = new List<Action>();

        int outer;
        for (int i=0; i < 10; i++)
        {
            outer = i;
            int inner = i;
            actions.Add(() => Console.WriteLine("Inner={0}, Outer={1}", inner, outer));
        }

        foreach (Action action in actions)
        {
            action();
        }
    }
}

Keluaran:

Inner=0, Outer=9
Inner=1, Outer=9
Inner=2, Outer=9
Inner=3, Outer=9
Inner=4, Outer=9
Inner=5, Outer=9
Inner=6, Outer=9
Inner=7, Outer=9
Inner=8, Outer=9
Inner=9, Outer=9

Perbedaannya adalah bahwa semua tindakan menangkap outervariabel yang sama , tetapi masing-masing memiliki innervariabel sendiri-sendiri .


3
dalam contoh B (pertanyaan awal), apakah itu benar-benar membuat variabel baru setiap kali? apa yang terjadi di mata tumpukan?
Royi Namir

@ Jon, apakah itu bug di C # 1.0? Bukankah idealnya angka Outer9?
nawfal

@nawfal: Saya tidak tahu apa maksud Anda. Ekspresi Lambda tidak dalam 1.0 ... dan Outer adalah 9. Bug apa yang Anda maksud?
Jon Skeet

@nawfal: Maksud saya adalah bahwa tidak ada fitur bahasa di C # 1.0 di mana Anda bisa membedakan antara mendeklarasikan variabel di dalam loop dan mendeklarasikannya di luar (dengan asumsi keduanya dikompilasi). Itu berubah dalam C # 2.0. Tidak ada bug
Jon Skeet

@ JonSkeet Oh ya, saya mengerti sekarang, saya sepenuhnya mengabaikan fakta bahwa Anda tidak dapat menutup variabel seperti itu di 1.0, salah saya! :)
nawfal

35

Berikut ini adalah apa yang saya tulis dan susun dalam .NET.

double r0;
for (int i = 0; i < 1000; i++) {
    r0 = i*i;
    Console.WriteLine(r0);
}

for (int j = 0; j < 1000; j++) {
    double r1 = j*j;
    Console.WriteLine(r1);
}

Inilah yang saya dapatkan dari .NET Reflector ketika CIL dirender kembali ke dalam kode.

for (int i = 0; i < 0x3e8; i++)
{
    double r0 = i * i;
    Console.WriteLine(r0);
}
for (int j = 0; j < 0x3e8; j++)
{
    double r1 = j * j;
    Console.WriteLine(r1);
}

Jadi keduanya terlihat sama persis setelah kompilasi. Dalam kode bahasa yang dikelola, dikonversi menjadi kode CL / byte dan pada saat eksekusi kode dikonversi menjadi bahasa mesin. Jadi, dalam bahasa mesin, double mungkin tidak dapat dibuat di stack. Ini mungkin hanya register karena kode mencerminkan bahwa itu adalah variabel sementara untuk WriteLinefungsi. Ada satu set aturan optimisasi lengkap hanya untuk loop. Jadi rata-rata pria tidak perlu khawatir tentang hal itu, terutama dalam bahasa yang dikelola. Ada beberapa kasus ketika Anda dapat mengoptimalkan kode kelola, misalnya, jika Anda harus menggabungkan banyak string menggunakan just string a; a+=anotherstring[i]vs usingStringBuilder. Ada perbedaan kinerja yang sangat besar di antara keduanya. Ada banyak kasus di mana kompiler tidak dapat mengoptimalkan kode Anda, karena ia tidak dapat mengetahui apa yang dimaksudkan dalam ruang lingkup yang lebih besar. Tapi itu bisa mengoptimalkan hal-hal dasar untuk Anda.


int j = 0 untuk (; j <0x3e8; j ++) dengan cara ini dinyatakan sekali waktu kedua variabel, dan tidak masing-masing untuk siklus. 2) tugas itu lebih baik daripada semua opsi lainnya. 3) Jadi aturan praktik terbaik adalah setiap deklarasi di luar iterasi untuk.
luka

24

Ini adalah gotcha di VB.NET. Hasil Visual Basic tidak akan menginisialisasi ulang variabel dalam contoh ini:

For i as Integer = 1 to 100
    Dim j as Integer
    Console.WriteLine(j)
    j = i
Next

' Output: 0 1 2 3 4...

Ini akan mencetak 0 pertama kali (variabel Visual Basic memiliki nilai default ketika dideklarasikan!) Tetapi isetiap kali setelah itu.

Namun, jika Anda menambahkan = 0, Anda mendapatkan apa yang Anda harapkan:

For i as Integer = 1 to 100
    Dim j as Integer = 0
    Console.WriteLine(j)
    j = i
Next

'Output: 0 0 0 0 0...

1
Saya telah menggunakan VB.NET selama bertahun-tahun dan belum pernah menemukan ini !!
ChrisA

12
Ya, tidak menyenangkan untuk mengetahui hal ini dalam praktik.
Michael Haren

Berikut ini adalah referensi tentang ini dari Paul Vick: panopticoncentral.net/archive/2006/03/28/11552.aspx
ferventcoder

1
@eschneider @ferventcoder Sayangnya @PaulV telah memutuskan untuk meninggalkan posting blog lamanya , jadi ini sekarang merupakan tautan mati.
Mark Hurd

ya, baru-baru ini berlari melintasi ini; sedang mencari beberapa dokumen resmi tentang ini ...
Eric Schneider

15

Saya membuat tes sederhana:

int b;
for (int i = 0; i < 10; i++) {
    b = i;
}

vs.

for (int i = 0; i < 10; i++) {
    int b = i;
}

Saya mengkompilasi kode-kode ini dengan gcc - 5.2.0. Dan kemudian saya membongkar main () dari dua kode ini dan itulah hasilnya:

1º:

   0x00000000004004b6 <+0>:     push   rbp
   0x00000000004004b7 <+1>:     mov    rbp,rsp
   0x00000000004004ba <+4>:     mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret

vs.

   0x00000000004004b6 <+0>: push   rbp
   0x00000000004004b7 <+1>: mov    rbp,rsp
   0x00000000004004ba <+4>: mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret 

Yang mana persis sama dengan hasil asm. bukankah bukti bahwa kedua kode menghasilkan hal yang sama?


3
ya, dan itu keren bahwa Anda melakukan ini, tetapi ini kembali ke apa yang orang katakan tentang ketergantungan bahasa / kompiler. Saya bertanya-tanya bagaimana JIT atau kinerja bahasa yang ditafsirkan akan terpengaruh.
user137717

12

Ini tergantung pada bahasa - IIRC C # mengoptimalkan ini, jadi tidak ada perbedaan, tetapi JavaScript (misalnya) akan melakukan alokasi memori keseluruhan shebang setiap kali.


Ya, tapi itu tidak seberapa. Saya menjalankan tes sederhana dengan loop untuk mengeksekusi 100 juta kali dan saya menemukan bahwa perbedaan terbesar dalam menyatakan di luar loop adalah 8 ms. Biasanya lebih seperti 3-4 dan kadang-kadang menyatakan di luar loop dilakukan lebih buruk (hingga 4 ms), tapi itu tidak khas.
user137717

11

Saya akan selalu menggunakan A (daripada mengandalkan kompiler) dan mungkin juga menulis ulang ke:

for(int i=0, double intermediateResult=0; i<1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

Ini masih membatasi intermediateResultruang lingkup loop, tetapi tidak mendeklarasikan ulang selama setiap iterasi.


12
Apakah Anda secara konseptual ingin variabel untuk hidup selama durasi loop, bukan secara terpisah per iterasi? Saya jarang melakukannya. Tulis kode yang mengungkapkan niat Anda sejelas mungkin, kecuali Anda punya alasan yang sangat, sangat baik untuk melakukan sebaliknya.
Jon Skeet

4
Ah, kompromi yang bagus, saya tidak pernah memikirkan hal ini! IMO, kode menjadi sedikit kurang jelas secara visual ')
Rabarberski

2
@ Jon - Saya tidak tahu apa yang sebenarnya dilakukan OP dengan nilai perantara. Hanya berpikir itu adalah opsi yang patut dipertimbangkan.
Triptych

6

Menurut saya, b adalah struktur yang lebih baik. Dalam a, nilai terakhir dari intermediateResult bertahan setelah loop Anda selesai.

Sunting: Ini tidak membuat banyak perbedaan dengan tipe nilai, tetapi tipe referensi bisa agak berbobot. Secara pribadi, saya suka variabel yang akan ditereferensi sesegera mungkin untuk pembersihan, dan b melakukan itu untuk Anda,


sticks around after your loop is finished- Meskipun ini tidak masalah dalam bahasa seperti Python, di mana nama terikat bertahan sampai fungsi berakhir.
new123456

@ new123456: OP meminta untuk spesifik Jawa, bahkan jika pertanyaan itu ditanyakan agak umum. Banyak bahasa yang diturunkan dari C memiliki cakupan tingkat blok: C, C ++, Perl (dengan mykata kunci), C #, dan Java untuk nama 5 yang saya gunakan.
Powerlord

Saya tahu - itu adalah pengamatan, bukan kritik.
new123456

5

Saya menduga beberapa kompiler dapat mengoptimalkan keduanya menjadi kode yang sama, tetapi tentu saja tidak semua. Jadi saya katakan Anda lebih baik dengan yang pertama. Satu-satunya alasan untuk yang terakhir adalah jika Anda ingin memastikan bahwa variabel yang dideklarasikan hanya digunakan dalam loop Anda.


5

Sebagai aturan umum, saya mendeklarasikan variabel saya dalam lingkup yang paling memungkinkan. Jadi, jika Anda tidak menggunakan intermediateResult di luar loop, maka saya akan pergi dengan B.


5

Seorang rekan kerja lebih suka formulir pertama, mengatakan itu adalah optimasi, lebih suka menggunakan kembali deklarasi.

Saya lebih suka yang kedua (dan mencoba membujuk rekan kerja saya! ;-)), setelah membaca bahwa:

  • Ini mengurangi ruang lingkup variabel ke tempat mereka dibutuhkan, yang merupakan hal yang baik.
  • Java cukup optimal untuk tidak membuat perbedaan signifikan dalam kinerja. IIRC, mungkin bentuk kedua bahkan lebih cepat.

Lagi pula, itu termasuk dalam kategori optimasi prematur yang mengandalkan kualitas kompiler dan / atau JVM.


5

Ada perbedaan dalam C # jika Anda menggunakan variabel dalam lambda, dll. Tetapi secara umum kompiler pada dasarnya akan melakukan hal yang sama, dengan asumsi variabel hanya digunakan dalam loop.

Mengingat bahwa mereka pada dasarnya sama: Perhatikan bahwa versi b membuatnya lebih jelas bagi pembaca bahwa variabel tidak, dan tidak bisa, digunakan setelah loop. Selain itu, versi b jauh lebih mudah di-refactored. Lebih sulit untuk mengekstrak loop body ke dalam metodenya sendiri dalam versi a. Selain itu, versi b meyakinkan Anda bahwa tidak ada efek samping untuk refactoring tersebut.

Oleh karena itu, versi mengganggu saya tanpa akhir, karena tidak ada manfaatnya dan itu membuatnya jauh lebih sulit untuk alasan tentang kode ...


5

Nah, Anda selalu bisa membuat ruang lingkup untuk itu:

{ //Or if(true) if the language doesn't support making scopes like this
    double intermediateResult;
    for (int i=0; i<1000; i++) {
        intermediateResult = i;
        System.out.println(intermediateResult);
    }
}

Dengan cara ini Anda hanya mendeklarasikan variabel satu kali, dan itu akan mati ketika Anda meninggalkan loop.


4

Saya selalu berpikir bahwa jika Anda mendeklarasikan variabel Anda di dalam loop Anda maka Anda membuang-buang memori. Jika Anda memiliki sesuatu seperti ini:

for(;;) {
  Object o = new Object();
}

Maka tidak hanya objek perlu dibuat untuk setiap iterasi, tetapi perlu ada referensi baru yang dialokasikan untuk setiap objek. Tampaknya jika pengumpul sampah lambat maka Anda akan memiliki banyak referensi menggantung yang perlu dibersihkan.

Namun, jika Anda memiliki ini:

Object o;
for(;;) {
  o = new Object();
}

Maka Anda hanya membuat satu referensi dan menetapkan objek baru untuk itu setiap kali. Tentu, mungkin butuh sedikit lebih lama untuk keluar dari ruang lingkup, tetapi kemudian hanya ada satu referensi menggantung untuk berurusan dengan.


3
Referensi baru tidak dialokasikan untuk setiap objek, bahkan jika referensi tersebut dinyatakan dalam loop 'for'. Dalam KEDUA kasus: 1) 'o' adalah variabel lokal dan ruang stack dialokasikan satu kali untuk itu di awal fungsi. 2) Ada Objek baru yang dibuat di setiap iterasi. Jadi tidak ada perbedaan kinerja. Untuk organisasi kode, keterbacaan dan pemeliharaan, menyatakan referensi dalam loop lebih baik.
Ajoy Bhatia

1
Meskipun saya tidak dapat berbicara untuk Java, dalam. NET referensi tidak 'dialokasikan' untuk setiap objek dalam contoh pertama. Ada satu entri pada stack untuk variabel lokal (ke metode). Untuk contoh Anda, IL yang dibuat identik.
Jesse C. Slicer

3

Saya pikir itu tergantung pada kompiler dan sulit untuk memberikan jawaban umum.


3

Latihan saya adalah sebagai berikut:

  • jika jenis variabelnya sederhana (int, dobel, ...) Saya lebih suka varian b (di dalam).
    Alasan: mengurangi ruang lingkup variabel.

  • jika jenis variabel tidak sederhana (semacam classatau struct) Saya lebih suka varian a (luar).
    Alasan: mengurangi jumlah panggilan ctor-dtor.


1

Dari perspektif kinerja, di luar (jauh) lebih baik.

public static void outside() {
    double intermediateResult;
    for(int i=0; i < Integer.MAX_VALUE; i++){
        intermediateResult = i;
    }
}

public static void inside() {
    for(int i=0; i < Integer.MAX_VALUE; i++){
        double intermediateResult = i;
    }
}

Saya menjalankan masing-masing fungsi 1 miliar kali. luar () mengambil 65 milidetik. inside () butuh 1,5 detik.


2
Pasti kompilasi Debug tidak dioptimalkan, ya?
Tomasz Przychodzki

int j = 0 untuk (; j <0x3e8; j ++) dengan cara ini dinyatakan sekali waktu kedua variabel, dan tidak masing-masing untuk siklus. 2) tugas itu lebih baik daripada semua opsi lainnya. 3) Jadi aturan praktik terbaik adalah setiap deklarasi di luar iterasi untuk.
luka

1

Saya menguji JS dengan Node 4.0.0 jika ada yang tertarik. Mendeklarasikan di luar loop menghasilkan ~ .5 ms peningkatan kinerja rata-rata lebih dari 1000 percobaan dengan 100 juta iterasi loop per percobaan. Jadi saya akan mengatakan maju dan menulisnya dengan cara yang paling mudah dibaca / dipelihara yaitu B, imo. Saya akan meletakkan kode saya di biola, tapi saya menggunakan modul Node sekarang-kinerja. Ini kodenya:

var now = require("../node_modules/performance-now")

// declare vars inside loop
function varInside(){
    for(var i = 0; i < 100000000; i++){
        var temp = i;
        var temp2 = i + 1;
        var temp3 = i + 2;
    }
}

// declare vars outside loop
function varOutside(){
    var temp;
    var temp2;
    var temp3;
    for(var i = 0; i < 100000000; i++){
        temp = i
        temp2 = i + 1
        temp3 = i + 2
    }
}

// for computing average execution times
var insideAvg = 0;
var outsideAvg = 0;

// run varInside a million times and average execution times
for(var i = 0; i < 1000; i++){
    var start = now()
    varInside()
    var end = now()
    insideAvg = (insideAvg + (end-start)) / 2
}

// run varOutside a million times and average execution times
for(var i = 0; i < 1000; i++){
    var start = now()
    varOutside()
    var end = now()
    outsideAvg = (outsideAvg + (end-start)) / 2
}

console.log('declared inside loop', insideAvg)
console.log('declared outside loop', outsideAvg)

0

A) adalah taruhan yang aman daripada B) ......... Bayangkan jika Anda menginisialisasi struktur dalam loop daripada 'int' atau 'float' lalu apa?

Suka

typedef struct loop_example{

JXTZ hi; // where JXTZ could be another type...say closed source lib 
         // you include in Makefile

}loop_example_struct;

//then....

int j = 0; // declare here or face c99 error if in loop - depends on compiler setting

for ( ;j++; )
{
   loop_example loop_object; // guess the result in memory heap?
}

Anda tentu akan menghadapi masalah dengan kebocoran memori !. Oleh karena itu saya percaya 'A' adalah taruhan yang lebih aman sementara 'B' rentan terhadap akumulasi memori dan bekerja di perpustakaan sumber dekat. Anda dapat memeriksa menggunakan Alat 'Valgrind' di Linux khususnya sub alat 'Helgrind'.


0

Itu pertanyaan yang menarik. Dari pengalaman saya, ada pertanyaan pamungkas untuk dipertimbangkan ketika Anda memperdebatkan masalah ini untuk kode:

Apakah ada alasan mengapa variabel tersebut harus bersifat global?

Masuk akal untuk hanya mendeklarasikan variabel sekali, secara global, sebagai lawan berkali-kali secara lokal, karena lebih baik untuk mengatur kode dan membutuhkan lebih sedikit garis kode. Namun, jika hanya perlu dideklarasikan secara lokal dalam satu metode, saya akan menginisialisasi dalam metode itu sehingga jelas bahwa variabel tersebut secara eksklusif relevan dengan metode itu. Berhati-hatilah untuk tidak memanggil variabel ini di luar metode yang diinisialisasi jika Anda memilih opsi yang terakhir - kode Anda tidak akan tahu apa yang Anda bicarakan dan akan melaporkan kesalahan.

Juga, sebagai catatan tambahan, jangan menduplikasi nama variabel lokal antara metode yang berbeda bahkan jika tujuannya hampir identik; itu hanya membingungkan.


1
lol Saya tidak setuju untuk begitu banyak alasan ... Namun, tidak ada suara bawah ... Aku menghormati hak Anda untuk memilih
Grantly

0

ini adalah bentuk yang lebih baik

double intermediateResult;
int i = byte.MinValue;

for(; i < 1000; i++)
{
intermediateResult = i;
System.out.println(intermediateResult);
}

1) dengan cara ini dinyatakan sekali waktu kedua variabel, dan tidak masing-masing untuk siklus. 2) tugas itu lebih baik daripada semua opsi lainnya. 3) Jadi aturan praktik terbaik adalah setiap deklarasi di luar iterasi untuk.


0

Mencoba hal yang sama di Go, dan membandingkan keluaran kompilator menggunakan go tool compile -Sdengan go 1.9.4

Perbedaan nol, sesuai output assembler.


0

Saya memiliki pertanyaan yang sama untuk waktu yang lama. Jadi saya menguji sepotong kode yang lebih sederhana.

Kesimpulan: Untuk kasus seperti itu TIDAK ada perbedaan kinerja.

Kasing luar

int intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i+2;
    System.out.println(intermediateResult);
}

Di dalam kasing

for(int i=0; i < 1000; i++){
    int intermediateResult = i+2;
    System.out.println(intermediateResult);
}

Saya memeriksa file yang dikompilasi pada dekompiler IntelliJ dan untuk kedua kasus, saya mendapatkan yang sama Test.class

for(int i = 0; i < 1000; ++i) {
    int intermediateResult = i + 2;
    System.out.println(intermediateResult);
}

Saya juga membongkar kode untuk kedua kasus menggunakan metode yang diberikan dalam jawaban ini . Saya hanya akan menunjukkan bagian yang relevan dengan jawabannya

Kasing luar

Code:
  stack=2, locals=3, args_size=1
     0: iconst_0
     1: istore_2
     2: iload_2
     3: sipush        1000
     6: if_icmpge     26
     9: iload_2
    10: iconst_2
    11: iadd
    12: istore_1
    13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    16: iload_1
    17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
    20: iinc          2, 1
    23: goto          2
    26: return
LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13      13     1 intermediateResult   I
            2      24     2     i   I
            0      27     0  args   [Ljava/lang/String;

Di dalam kasing

Code:
      stack=2, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: sipush        1000
         6: if_icmpge     26
         9: iload_1
        10: iconst_2
        11: iadd
        12: istore_2
        13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        16: iload_2
        17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        20: iinc          1, 1
        23: goto          2
        26: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13       7     2 intermediateResult   I
            2      24     1     i   I
            0      27     0  args   [Ljava/lang/String;

Jika Anda memperhatikan, hanya Slotditugaskan untuk idan intermediateResultdi LocalVariableTableswap sebagai produk pesanan mereka dari penampilan. Perbedaan slot yang sama tercermin pada baris kode lainnya.

  • Tidak ada operasi tambahan yang dilakukan
  • intermediateResult masih merupakan variabel lokal dalam kedua kasus, sehingga tidak ada perbedaan waktu akses.

BONUS

Compiler melakukan banyak optimasi, lihat apa yang terjadi dalam kasus ini.

Kasus nol kerja

for(int i=0; i < 1000; i++){
    int intermediateResult = i;
    System.out.println(intermediateResult);
}

Nol pekerjaan didekompilasi

for(int i = 0; i < 1000; ++i) {
    System.out.println(i);
}

-1

Bahkan jika saya tahu kompiler saya cukup pintar, saya tidak akan suka untuk bergantung padanya, dan akan menggunakan a) varian.

B) varian masuk akal bagi saya hanya jika Anda sangat perlu membuat intermediateResult tidak tersedia setelah loop body. Tapi aku tidak bisa membayangkan situasi yang menyedihkan seperti itu ....

EDIT: Jon Skeet membuat poin yang sangat bagus, menunjukkan bahwa deklarasi variabel di dalam sebuah loop dapat membuat perbedaan semantik yang sebenarnya.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.