Tampaknya loop tak berujung berakhir, kecuali System.out.println digunakan

91

Saya memiliki sedikit kode sederhana yang seharusnya menjadi loop tanpa akhir karena xakan selalu tumbuh dan akan selalu tetap lebih besar dari j.

int x = 5;
int y = 9;
for (int j = 0; j < x; j++) {
   x = x + y;
}
System.out.println(y);

tetapi sebagaimana adanya, ia mencetak ydan tidak berputar tanpa henti. Saya tidak tahu mengapa. Namun, ketika saya menyesuaikan kode dengan cara berikut:

int x = 5;
int y = 9;
for (int j = 0; j < x; j++) {
    x = x + y;
    System.out.println(y);
}
System.out.println(y);

Ini menjadi lingkaran tanpa akhir dan saya tidak tahu mengapa. Apakah java mengenali loop tak berujung dan melewatkannya pada situasi pertama tetapi harus mengeksekusi pemanggilan metode pada situasi kedua sehingga berperilaku seperti yang diharapkan? Bingung :)

Omar
sumber
4
Loop kedua tidak terbatas karena batas atas xtumbuh lebih cepat daripada variabel loop j. Dengan kata lain, jtidak akan pernah mencapai batas atas, karenanya loop akan berjalan "selamanya." Yah, tidak selamanya, kemungkinan besar Anda akan mendapatkan luapan di beberapa titik.
Tim Biegeleisen
75
Ini bukan loop tanpa akhir, hanya saja dibutuhkan 238609294 kali untuk keluar dari loop for dalam kasus pertama dan kedua kalinya mencetak nilai y238609294 kali
N00b Pr0grammer
13
jawaban satu kata: melimpah
qwr
20
Anehnya, System.out.println(x)alih-alih ydi bagian akhir akan langsung menunjukkan apa masalahnya
JollyJoker
9
@TeroLahtinen tidak, tidak akan. Baca spesifikasi bahasa Java jika Anda ragu apa itu tipe int. Ini adalah perangkat keras independen.
9ilsdx 9rvj 0lo

Jawaban:

161

Kedua contoh tersebut tidak terbatas.

Masalahnya adalah batasan inttipe di Java (atau hampir semua bahasa umum lainnya). Ketika nilai xmencapai 0x7fffffff, menambahkan nilai positif apa pun akan menghasilkan luapan dan xmenjadi negatif, oleh karena itu lebih rendah dari j.

Perbedaan antara loop pertama dan kedua adalah kode bagian dalam membutuhkan lebih banyak waktu dan mungkin perlu beberapa menit sampai xoverflow. Untuk contoh pertama mungkin memerlukan waktu kurang dari detik atau kemungkinan besar kode akan dihapus oleh pengoptimal karena tidak berpengaruh apa pun.

Seperti yang disebutkan dalam pembahasan, waktu akan sangat bergantung pada bagaimana OS men-buffer keluaran, apakah itu dikeluarkan ke emulator terminal, dll., Sehingga bisa jadi lebih lama dari beberapa menit.

Zbynek Vyskovsky - kvr000
sumber
48
Saya baru saja mencoba program (di laptop saya) yang mencetak garis dalam satu lingkaran. Saya mengatur waktunya, dan itu dapat mencetak sekitar 1000 baris / detik. Berdasarkan komentar N00b bahwa loop akan dieksekusi 238609294 kali, dibutuhkan sekitar 23861 detik untuk loop berhenti - lebih dari 6,6 jam. Sedikit lebih dari "beberapa menit".
ajb
11
@ajb: Tergantung implementasinya. IIRC println()pada Windows adalah operasi pemblokiran, sedangkan pada (beberapa?) Unix itu buffer sehingga berjalan lebih cepat. Coba juga gunakan print(), buffer mana sampai mencapai \n(atau buffer terisi, atau flush()disebut)
BlueRaja - Danny Pflughoeft
6
Juga tergantung pada terminal yang menunjukkan keluaran. Lihat stackoverflow.com/a/21947627/53897 untuk contoh ekstrem (di mana perlambatan disebabkan oleh pembungkusan kata)
Thorbjørn Ravn Andersen
1
Ya, itu buffered di UNIX, tapi masih memblokir. Setelah buffer 8K atau lebih terisi, buffer akan terhalang sampai ada ruang. Kecepatannya akan sangat bergantung pada seberapa cepat dikonsumsi. Mengalihkan output ke / dev / null akan menjadi yang tercepat, tetapi mengirimkannya ke terminal, defaultnya, akan membutuhkan pembaruan grafis ke layar dan lebih banyak daya komputasi karena membuat font memperlambatnya.
penguin359
2
@ Zbynek oh, mungkin begitu, tapi itu mengingatkan saya, terminal I / O biasanya akan menjadi baris buffer, bukan memblokir jadi kemungkinan besar setiap println akan mengakibatkan panggilan sistem yang semakin memperlambat kasus terminal.
penguin359
33

Karena mereka dideklarasikan sebagai int, setelah mencapai nilai max, loop akan putus karena nilai x akan menjadi negatif.

Tetapi ketika System.out.println ditambahkan ke loop, kecepatan eksekusi menjadi terlihat (karena keluaran ke konsol akan memperlambat kecepatan eksekusi). Namun, jika Anda membiarkan program ke-2 (program dengan syso di dalam perulangan) berjalan cukup lama, program tersebut akan memiliki perilaku yang sama seperti yang pertama (program tanpa syso di dalam perulangan).

Ace Zachary
sumber
21
Orang tidak menyadari seberapa banyak spamming ke konsol dapat memperlambat kode mereka.
pengguna9993
13

Ada dua alasan untuk ini:

  1. Java mengoptimalkan forperulangan dan karena tidak ada penggunaan xsetelah perulangan, cukup hapus perulangan. Anda dapat memeriksa ini dengan meletakkan System.out.println(x);pernyataan setelah pengulangan.

  2. Ada kemungkinan bahwa Java tidak benar-benar mengoptimalkan loop dan menjalankan program dengan benar dan pada akhirnya xakan menjadi terlalu besar intdan meluap. Integer overflow kemungkinan besar akan membuat integer xmenjadi negatif yang akan lebih kecil dari j sehingga akan keluar dari loop dan mencetak nilai y. Ini juga dapat diperiksa dengan menambahkan System.out.println(x);setelah perulangan.

Juga, bahkan dalam kasus pertama akhirnya overflow akan terjadi sehingga rendering ke kasus kedua sehingga tidak akan pernah menjadi loop tanpa akhir yang sebenarnya.

Ashok Vishnoi
sumber
14
Saya memilih pintu nomor 2.
Robby Cornelissen
Benar. Itu pergi pada skala negatif dan keluar dari lingkaran. Tapi a sysoutsangat lambat untuk menambahkan ilusi putaran tak terbatas.
Pavan Kumar
4
1. Akan menjadi bug. Pengoptimalan kompiler tidak diperbolehkan untuk mengubah perilaku program. Jika ini adalah pengulangan tanpa batas, maka kompilator dapat mengoptimalkan semua yang diinginkannya, namun, hasilnya tetap harus berupa pengulangan tanpa batas. Solusi sebenarnya adalah OP salah: tidak satu pun dari keduanya merupakan loop tak terbatas, yang satu melakukan lebih banyak pekerjaan daripada yang lain, jadi butuh waktu lebih lama.
Jörg W Mittag
1
@ JörgWMittag Dalam kasus ini x adalah variabel lokal yang tidak ada hubungannya dengan yang lain. Karena itu mungkin saja itu dioptimalkan. Tetapi kita harus melihat bytecode untuk menentukan apakah itu masalahnya, jangan pernah berasumsi bahwa kompilator melakukan sesuatu seperti itu.
Semoga
1

Keduanya bukanlah loop tanpa akhir, awalnya j = 0, selama j <x, j meningkat (j ++), dan j adalah integer sehingga loop akan berjalan hingga mencapai nilai maksimum lalu meluap (Overflow Integer adalah kondisinya yang terjadi jika hasil operasi aritmatika, seperti perkalian atau penjumlahan, melebihi ukuran maksimum jenis bilangan bulat yang digunakan untuk menyimpannya.). untuk contoh kedua sistem hanya mencetak nilai y sampai pengulangan putus.

jika Anda mencari contoh loop tanpa akhir, akan terlihat seperti ini

int x = 6;

for (int i = 0; x < 10; i++) {
System.out.println("Still Looping");
}

karena (x) tidak akan pernah mencapai nilai 10;

Anda juga bisa membuat loop tak terbatas dengan loop for ganda:

int i ;

  for (i = 0; i <= 10; i++) {
      for (i = 0; i <= 5; i++){
         System.out.println("Repeat");   
      }
 }

loop ini tidak terbatas karena loop for pertama mengatakan i <10, yang benar sehingga masuk ke loop for kedua dan loop for kedua meningkatkan nilai (i) hingga == 5. Kemudian melanjutkan ke loop pertama for loop lagi karena i <10, proses terus berulang sendiri karena me-reset setelah for loop kedua

Kennedy
sumber
1

Ini adalah pengulangan terbatas karena setelah nilai xmelebihi 2,147,483,647(yang merupakan nilai maksimum dari sebuah int), xakan menjadi negatif dan tidak lebih besar dari yang jlainnya, baik Anda mencetak y atau tidak.

Anda cukup mengubah nilai ymenjadi 100000dan mencetak ydalam pengulangan dan pengulangan akan segera berhenti.

Alasan mengapa Anda merasa itu menjadi tak terbatas adalah karena System.out.println(y);membuat kode dieksekusi jauh lebih lambat daripada tanpa tindakan apa pun.

Joe Cheng
sumber
0

Masalah yang menarik Sebenarnya dalam kedua kasus loop tidak ada habisnya

Tetapi perbedaan utama di antara mereka adalah kapan itu akan berakhir dan berapa banyak waktu yang xdibutuhkan untuk melebihi intnilai maks yang 2,147,483,647setelah itu akan mencapai keadaan luapan dan loop akan berakhir.

Cara terbaik untuk memahami masalah ini adalah dengan menguji contoh sederhana dan mempertahankan hasilnya.

Contoh :

for(int i = 10; i > 0; i++) {}
System.out.println("finished!");

Keluaran:

finished!
BUILD SUCCESSFUL (total time: 0 seconds)

Setelah menguji loop tak terbatas ini, dibutuhkan waktu kurang dari 1 detik untuk berhenti.

for(int i = 10; i > 0; i++) {
    System.out.println("infinite: " + i);
}
System.out.println("finished!");

Keluaran:

infinite: 314572809
infinite: 314572810
infinite: 314572811
.
.
.
infinite: 2147483644
infinite: 2147483645
infinite: 2147483646
infinite: 2147483647
finished!
BUILD SUCCESSFUL (total time: 486 minutes 25 seconds)

Pada kasus pengujian ini, Anda akan melihat perbedaan besar dalam waktu yang dibutuhkan untuk menghentikan dan menyelesaikan program.

Jika Anda tidak sabar, Anda akan berpikir loop ini tidak ada habisnya dan tidak akan berakhir tetapi pada kenyataannya akan memakan waktu berjam-jam untuk mengakhiri dan mencapai keadaan overflow pada inilainya.

Akhirnya kami menyimpulkan setelah kami meletakkan pernyataan print di dalam for loop bahwa itu akan membutuhkan lebih banyak waktu daripada loop dalam kasus pertama tanpa pernyataan print.

Waktu yang dibutuhkan untuk menjalankan program tergantung pada spesifikasi komputer Anda khususnya kekuatan pemrosesan (kapasitas prosesor), sistem operasi dan IDE Anda yang sedang menyusun program.

Saya menguji kasus ini pada:

Lenovo 2,7 GHz Intel Core i5

OS: Windows 8.1 64x

IDE: NetBeans 8.2

Dibutuhkan sekitar 8 jam (486 menit) untuk menyelesaikan program ini.

Anda juga dapat melihat bahwa kenaikan langkah di loop for i = i + 1adalah faktor yang sangat lambat untuk mencapai nilai int maksimal.

Kita dapat mengubah faktor ini dan membuat peningkatan langkah lebih cepat untuk menguji loop dalam waktu yang lebih singkat.

jika kita menempatkan i = i * 10dan mengujinya:

for(int i = 10; i > 0; i*=10) {
           System.out.println("infinite: " + i);
}
     System.out.println("finished!");

Keluaran:

infinite: 100000
infinite: 1000000
infinite: 10000000
infinite: 100000000
infinite: 1000000000
infinite: 1410065408
infinite: 1215752192
finished!
BUILD SUCCESSFUL (total time: 0 seconds)

Seperti yang Anda lihat, ini sangat cepat dibandingkan dengan loop sebelumnya

dibutuhkan kurang dari 1 detik untuk menghentikan dan menyelesaikan program yang sedang berjalan.

Setelah contoh tes ini saya pikir itu harus menjelaskan masalah dan membuktikan validitas Zbynek Vyskovsky - jawaban kvr000 , juga akan menjadi jawaban untuk pertanyaan ini .

Oghli
sumber