Saya tahu output ke konsol adalah operasi yang mahal. Untuk kepentingan keterbacaan kode kadang-kadang ada baiknya memanggil fungsi untuk menampilkan teks dua kali, daripada memiliki string teks yang panjang sebagai argumen.
Misalnya, seberapa efisien untuk dimiliki
System.out.println("Good morning.");
System.out.println("Please enter your name");
vs.
System.out.println("Good morning.\nPlease enter your name");
Dalam contoh perbedaannya hanya satu panggilan ke println()
tetapi bagaimana jika itu lebih?
Pada catatan terkait, pernyataan yang melibatkan pencetakan teks dapat terlihat aneh saat melihat kode sumber jika teks yang akan dicetak panjang. Dengan asumsi teks itu sendiri tidak dapat dibuat lebih pendek, apa yang bisa dilakukan? Haruskah ini menjadi kasus di mana beberapa println()
panggilan dilakukan? Seseorang pernah memberi tahu saya sebaris kode tidak boleh lebih dari 80 karakter (IIRC) jadi apa yang akan Anda lakukan
System.out.println("Good morning everyone. I am here today to present you with a very, very lengthy sentence in order to prove a point about how it looks strange amongst other code.");
Apakah hal yang sama berlaku untuk bahasa seperti C / C ++ karena setiap kali data ditulis ke aliran output, pemanggilan sistem harus dilakukan dan prosesnya harus menuju ke mode kernel (yang sangat mahal)?
Jawaban:
Ada dua 'kekuatan' di sini, dalam ketegangan: Kinerja vs Keterbacaan.
Mari kita atasi masalah ketiga terlebih dahulu, garis panjang:
Cara terbaik untuk menerapkan ini dan menjaga kewaspadaan, adalah dengan menggunakan penggabungan string:
Rangkaian string-konstan akan terjadi pada waktu kompilasi, dan tidak akan berpengaruh pada kinerja sama sekali. Garis-garisnya dapat dibaca, dan Anda bisa melanjutkan.
Sekarang, tentang:
vs.
Opsi kedua secara signifikan lebih cepat. Saya akan menyarankan tentang 2X secepat .... mengapa?
Karena 90% (dengan margin kesalahan yang luas) dari pekerjaan tidak terkait dengan membuang karakter ke output, tetapi diperlukan overhead untuk mengamankan output untuk menulis padanya.
Sinkronisasi
System.out
adalah aPrintStream
. Semua implementasi Java yang saya ketahui, secara internal menyinkronkan PrintStream: Lihat kode di GrepCode! .Apa artinya ini untuk kode Anda?
Ini berarti bahwa setiap kali Anda menelepon
System.out.println(...)
Anda menyinkronkan model memori Anda, Anda sedang memeriksa dan menunggu kunci. Utas lain yang memanggil System.out juga akan dikunci.Dalam aplikasi single-threaded dampaknya
System.out.println()
sering dibatasi oleh kinerja IO sistem Anda, seberapa cepat Anda dapat menulis ke file. Dalam aplikasi multithreaded, penguncian bisa menjadi masalah daripada IO.Pembilasan
Setiap println memerah . Ini menyebabkan buffer dibersihkan dan memicu penulisan tingkat Konsol ke buffer. Jumlah upaya yang dilakukan di sini tergantung pada implementasi, tetapi, secara umum dipahami bahwa kinerja flush hanya sebagian kecil terkait dengan ukuran buffer yang disiram. Ada overhead signifikan terkait dengan flush, di mana buffer memori ditandai sebagai kotor, mesin Virtual melakukan IO, dan sebagainya. Mengalami overhead itu sekali, bukan dua kali, merupakan pengoptimalan yang jelas.
Beberapa angka
Saya mengumpulkan tes kecil berikut:
Kode ini relatif sederhana, berulang kali mencetak string pendek, atau panjang ke output. String panjang memiliki beberapa baris baru di dalamnya. Ini mengukur berapa lama untuk mencetak masing-masing 1.000 iterasi.
Jika saya menjalankannya di prompt perintah unix (Linux), dan mengarahkan ulang
STDOUT
ke/dev/null
, dan mencetak hasil yang sebenarnyaSTDERR
, saya dapat melakukan hal berikut:Output (dalam errlog) terlihat seperti:
Apa artinya ini? Saya ulangi 'bait' terakhir:
Ini berarti bahwa, untuk semua maksud dan tujuan, meskipun garis 'panjang' sekitar 5 kali lebih lama, dan berisi beberapa baris baru, dibutuhkan output yang sama panjangnya dengan garis pendek.
Jumlah karakter per detik untuk jangka panjang adalah 5 kali lebih banyak, dan waktu yang berlalu hampir sama .....
Dengan kata lain, kinerja Anda berskala relatif terhadap jumlah cetak yang Anda miliki, bukan apa yang mereka cetak.
Pembaruan: Apa yang terjadi jika Anda mengarahkan ulang ke file, alih-alih ke / dev / null?
Ini jauh lebih lambat, tetapi proporsinya hampir sama ....
sumber
"\n"
mungkin bukan terminator garis kanan.println
akan secara otomatis mengakhiri baris dengan karakter yang tepat, tetapi menempel\n
ke string Anda secara langsung dapat menyebabkan masalah. Jika Anda ingin melakukannya dengan benar, Anda mungkin perlu menggunakan pemformatan string atauline.separator
properti sistem .println
jauh lebih bersih.Saya tidak berpikir memiliki banyak
println
s adalah masalah desain sama sekali. Cara saya melihatnya adalah bahwa ini jelas dapat dilakukan dengan penganalisa kode statis jika itu benar-benar masalah.Tapi itu bukan masalah karena kebanyakan orang tidak melakukan IO seperti ini. Ketika mereka benar-benar perlu melakukan banyak IO, mereka menggunakan yang di-buffer (BufferedReader, BufferedWriter, dll.) Ketika input di-buffer, Anda akan melihat bahwa kinerjanya cukup mirip, sehingga Anda tidak perlu khawatir memiliki sekelompok
println
atau sedikitprintln
.Jadi untuk menjawab pertanyaan aslinya. Saya akan mengatakan, tidak buruk jika Anda menggunakannya
println
untuk mencetak beberapa hal karena kebanyakan orang akan menggunakannyaprintln
.sumber
Dalam bahasa tingkat yang lebih tinggi seperti C dan C ++, ini kurang masalah daripada di Jawa.
Pertama-tama, C dan C ++ mendefinisikan penggabungan string waktu kompilasi, sehingga Anda dapat melakukan sesuatu seperti:
Dalam kasus seperti itu, menggabungkan string bukan hanya optimasi yang bisa Anda lakukan, biasanya (dll) bergantung pada kompiler yang akan dibuat. Sebaliknya, secara langsung diperlukan oleh standar C dan C ++ (fase 6 terjemahan: "Token string yang berdekatan disatukan.").
Meskipun dengan mengorbankan sedikit kompleksitas tambahan dalam kompiler dan implementasi, C dan C ++ melakukan sedikit lebih banyak untuk menyembunyikan kompleksitas menghasilkan output secara efisien dari programmer. Java jauh lebih mirip bahasa rakitan - setiap panggilan untuk
System.out.println
menerjemahkan lebih langsung ke panggilan ke operasi yang mendasarinya untuk menulis data ke konsol. Jika Anda ingin buffering untuk meningkatkan efisiensi, itu harus disediakan secara terpisah.Ini berarti, misalnya, bahwa dalam C ++, menulis ulang contoh sebelumnya, untuk sesuatu seperti ini:
... biasanya 1 tidak akan berpengaruh pada efisiensi. Setiap penggunaan
cout
hanya akan menyetor data ke buffer. Buffer itu akan mengalir ke aliran yang mendasarinya ketika buffer terisi, atau kode mencoba membaca input dari penggunaan (seperti denganstd::cin
).iostream
s juga memilikisync_with_stdio
properti yang menentukan apakah output dari iostreams disinkronkan dengan input gaya-C (mis.,getchar
). Secara defaultsync_with_stdio
disetel ke true, jadi jika, misalnya, Anda menulisstd::cout
, lalu membacagetchar
, data yang Anda tuliscout
akan memerah ketikagetchar
dipanggil. Anda dapat mengatursync_with_stdio
ke false untuk menonaktifkan itu (biasanya dilakukan untuk meningkatkan kinerja).sync_with_stdio
juga mengontrol tingkat sinkronisasi antara utas. Jika sinkronisasi diaktifkan (default) menulis ke iostream dari beberapa utas dapat menghasilkan data dari utas yang disisipkan, tetapi mencegah segala kondisi balapan. TKI, program Anda akan berjalan dan menghasilkan output, tetapi jika lebih dari satu utas menulis ke aliran pada satu waktu, pencampuran yang acak dari data dari utas yang berbeda biasanya akan membuat keluaran yang cukup berguna.Jika Anda mematikan sinkronisasi, maka sinkronisasi akses dari banyak utas juga menjadi tanggung jawab Anda. Penulisan bersamaan dari beberapa utas dapat / akan menyebabkan perlombaan data, yang berarti kode memiliki perilaku yang tidak ditentukan.
Ringkasan
C ++ default untuk upaya menyeimbangkan kecepatan dengan keamanan. Hasilnya cukup berhasil untuk kode single-threaded, tetapi kurang begitu untuk kode multi-threaded. Kode multithreaded biasanya perlu memastikan bahwa hanya satu utas yang menulis ke aliran sekaligus untuk menghasilkan keluaran yang bermanfaat.
1. Dimungkinkan untuk mematikan buffering untuk stream, tetapi sebenarnya melakukan hal itu sangat tidak biasa, dan ketika / jika seseorang melakukannya, itu mungkin karena alasan yang cukup spesifik, seperti memastikan bahwa semua output ditangkap segera meskipun ada efek pada kinerja . Bagaimanapun, ini hanya terjadi jika kode melakukannya secara eksplisit.
sumber
"2^31 - 1 = " + Integer.MAX_VALUE
disimpan sebagai string yang diinternir tunggal (JLS Sec 3.10.5 dan 15.28 ).Sementara kinerja tidak benar-benar masalah di sini, keterbacaan yang buruk dari banyak
println
pernyataan menunjuk ke aspek desain yang hilang.Mengapa kita menulis urutan banyak
println
pernyataan? Jika itu hanya satu blok teks tetap, seperti--help
teks dalam perintah konsol, akan jauh lebih baik untuk memilikinya sebagai sumber daya yang terpisah dan membacanya dan menulisnya ke layar berdasarkan permintaan.Tetapi biasanya itu adalah campuran dari bagian dinamis dan statis. Katakanlah kita memiliki beberapa data pesanan kosong di satu sisi, dan beberapa bagian teks statis tetap di sisi lain, dan hal-hal ini harus dicampur bersama untuk membentuk lembar konfirmasi pesanan. Sekali lagi, juga dalam kasus ini, lebih baik untuk memiliki file teks ressource yang terpisah: ressource akan menjadi template, yang mengandung beberapa jenis simbol (placeholder), yang diganti saat runtime oleh data pesanan aktual.
Pemisahan bahasa pemrograman dari bahasa alami memiliki banyak keuntungan - di antaranya adalah internasionalisasi: Anda mungkin harus menerjemahkan teks jika Anda ingin menjadi multilangual dengan perangkat lunak Anda. Juga, mengapa langkah kompilasi diperlukan jika Anda hanya ingin memiliki koreksi tekstual, katakan perbaiki salah eja.
sumber