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.
java
performance
loops
variables
initialization
Rabarberski
sumber
sumber
Jawaban:
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.
sumber
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.
sumber
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:
Keluaran:
Perbedaannya adalah bahwa semua tindakan menangkap
outer
variabel yang sama , tetapi masing-masing memilikiinner
variabel sendiri-sendiri .sumber
Outer
9?Berikut ini adalah apa yang saya tulis dan susun dalam .NET.
Inilah yang saya dapatkan dari .NET Reflector ketika CIL dirender kembali ke dalam kode.
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
WriteLine
fungsi. 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 juststring 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.sumber
Ini adalah gotcha di VB.NET. Hasil Visual Basic tidak akan menginisialisasi ulang variabel dalam contoh ini:
Ini akan mencetak 0 pertama kali (variabel Visual Basic memiliki nilai default ketika dideklarasikan!) Tetapi
i
setiap kali setelah itu.Namun, jika Anda menambahkan
= 0
, Anda mendapatkan apa yang Anda harapkan:sumber
Saya membuat tes sederhana:
vs.
Saya mengkompilasi kode-kode ini dengan gcc - 5.2.0. Dan kemudian saya membongkar main () dari dua kode ini dan itulah hasilnya:
1º:
vs.
2º
Yang mana persis sama dengan hasil asm. bukankah bukti bahwa kedua kode menghasilkan hal yang sama?
sumber
Ini tergantung pada bahasa - IIRC C # mengoptimalkan ini, jadi tidak ada perbedaan, tetapi JavaScript (misalnya) akan melakukan alokasi memori keseluruhan shebang setiap kali.
sumber
Saya akan selalu menggunakan A (daripada mengandalkan kompiler) dan mungkin juga menulis ulang ke:
Ini masih membatasi
intermediateResult
ruang lingkup loop, tetapi tidak mendeklarasikan ulang selama setiap iterasi.sumber
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,
sumber
sticks around after your loop is finished
- Meskipun ini tidak masalah dalam bahasa seperti Python, di mana nama terikat bertahan sampai fungsi berakhir.my
kata kunci), C #, dan Java untuk nama 5 yang saya gunakan.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.
sumber
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.
sumber
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:
Lagi pula, itu termasuk dalam kategori optimasi prematur yang mengandalkan kualitas kompiler dan / atau JVM.
sumber
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 ...
sumber
Nah, Anda selalu bisa membuat ruang lingkup untuk itu:
Dengan cara ini Anda hanya mendeklarasikan variabel satu kali, dan itu akan mati ketika Anda meninggalkan loop.
sumber
Saya selalu berpikir bahwa jika Anda mendeklarasikan variabel Anda di dalam loop Anda maka Anda membuang-buang memori. Jika Anda memiliki sesuatu seperti ini:
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:
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.
sumber
Saya pikir itu tergantung pada kompiler dan sulit untuk memberikan jawaban umum.
sumber
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
class
ataustruct
) Saya lebih suka varian a (luar).Alasan: mengurangi jumlah panggilan ctor-dtor.
sumber
Dari perspektif kinerja, di luar (jauh) lebih baik.
Saya menjalankan masing-masing fungsi 1 miliar kali. luar () mengambil 65 milidetik. inside () butuh 1,5 detik.
sumber
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:
sumber
A) adalah taruhan yang aman daripada B) ......... Bayangkan jika Anda menginisialisasi struktur dalam loop daripada 'int' atau 'float' lalu apa?
Suka
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'.
sumber
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.
sumber
ini adalah bentuk yang lebih baik
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.
sumber
Mencoba hal yang sama di Go, dan membandingkan keluaran kompilator menggunakan
go tool compile -S
dengan go 1.9.4Perbedaan nol, sesuai output assembler.
sumber
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
Di dalam kasing
Saya memeriksa file yang dikompilasi pada dekompiler IntelliJ dan untuk kedua kasus, saya mendapatkan yang sama
Test.class
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
Di dalam kasing
Jika Anda memperhatikan, hanya
Slot
ditugaskan untuki
danintermediateResult
diLocalVariableTable
swap sebagai produk pesanan mereka dari penampilan. Perbedaan slot yang sama tercermin pada baris kode lainnya.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
Nol pekerjaan didekompilasi
sumber
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.
sumber