Menghitung panjang urutan Collatz - iterator khusus menghasilkan perlambatan?

Saya telah memecahkan masalah UVA #100 - "The 3n + 1 masalah". Ini adalah "contoh" masalah mereka, dengan batas waktu yang sangat mudah (batas 3 detik, mereka solusi sampel tanpa caching sama sekali berjalan dalam 0,738 detik, solusi terbaik saya sejauh ini berjalan dalam 0,016 detik), jadi saya pikir bagaimanapun saya bereksperimen dengan kode, saya harus selalu memenuhi batasnya. Ya, saya salah.

Spesifikasi masalahnya sederhana: setiap baris masukan memiliki dua angka i dan j, dan keluarannya harus mencetak angka-angka ini diikuti dengan panjang maksimum rangkaian Collatz yang awalnya berada di antara i dan j inklusif.

Saya sudah menyiapkan empat solusi. Semuanya sangat mirip dan semuanya menghasilkan jawaban yang bagus.

Solusi pertama menyimpan cache hingga 0x100000 panjang urutan dalam vector. Panjang 0 berarti panjang barisan yang dimulai pada bilangan tertentu ini belum dihitung. Ini berjalan cukup cepat - 0,03 detik.

Solusi kedua cukup mirip, hanya saja solusi ini menyimpan setiap panjang tunggal dalam array renggang yang diimplementasikan dengan unordered_map. Ini berjalan cukup lambat dibandingkan solusi sebelumnya, tetapi masih sesuai dengan batasnya: 0,28 detik.

Sebagai latihan, saya juga menulis solusi ketiga, yang didasarkan pada solusi kedua. Tujuannya adalah menggunakan fungsi max_element, yang hanya menerima iterator. Saya tidak bisa menggunakan unordered_map::iterator, untuk menambah iterator seperti itu, AFAIK, linier dalam ukuran peta; oleh karena itu, saya menulis sebuah iterator khusus, yang beroperasi pada "wadah" abstrak yang "menampung" panjang urutan setiap nomor yang mungkin (tetapi sebenarnya menghitungnya dan menyimpannya dalam cache hanya jika diperlukan). Pada intinya, ini sebenarnya solusi unordered_map yang sama - hanya saja ada lapisan iterator tambahan yang ditambahkan di atas. Solusinya tidak sesuai dalam 3 detik. membatasi.

Dan sekarang tibalah hal yang tidak dapat saya mengerti. Meskipun solusi ketiga sengaja dibuat terlalu rumit, saya hampir tidak percaya bahwa lapisan iterator tambahan dapat menyebabkan perlambatan seperti itu. Untuk memeriksanya, saya menambahkan lapisan iterator yang sama ke solusi vector. Ini adalah solusi keempat saya. Menilai dari apa yang dilakukan ide iterator ini terhadap solusi unordered_map saya, saya memperkirakan akan terjadi perlambatan yang cukup besar di sini juga; tapi anehnya, hal ini tidak terjadi sama sekali. Solusi ini berjalan hampir secepat solusi vector biasa, dalam 0,035 detik.

Bagaimana ini mungkin? Apa sebenarnya penyebab lambatnya solusi ketiga? Bagaimana mungkin memperumit dua solusi serupa dengan cara yang persis sama akan memperlambat salah satu solusi, dan hampir tidak merugikan solusi lainnya sama sekali? Mengapa menambahkan lapisan iterator ke solusi unordered_map membuatnya tidak tepat waktu, dan melakukan hal yang sama pada solusi vector hampir tidak memperlambatnya sama sekali?

Sunting:

Saya menemukan bahwa masalahnya tampaknya paling terlihat jika masukan berisi banyak baris berulang. Saya menguji keempat solusi di mesin saya terhadap input 1 1000000, diulangi 200 kali. Solusi dengan vektor biasa memproses semuanya dalam 1,531 detik. Solusi dengan vektor dan lapisan iterator tambahan membutuhkan waktu 3,087 detik. Solusi dengan peta polos tidak berurutan membutuhkan waktu 33,821 detik. Dan solusi dengan peta tidak berurutan dan lapisan iterator tambahan yang dikelola memerlukan waktu lebih dari setengah jam - Saya menghentikannya setelah 31 menit 0,482 detik! Saya mengujinya di Linux mint 17.2 64 bit, g++ versi Ubuntu 4.8.4-2ubuntu1~14.04 dengan flag -std=c++11 -O2, prosesor Celeron 2955U @1.4 GHz x 2


person Community    schedule 22.07.2015    source sumber
comment
Saya tidak dapat mereproduksi masalahnya. Solusi ketiga selesai dalam waktu yang dapat diabaikan di mesin saya untuk program dan masukan yang diberikan. (time melaporkan 1-2 ms.) Sama untuk yang kedua. Mungkin memposting file input yang lebih sulit?   -  person Potatoswatter    schedule 22.07.2015
comment
… mengubah baris terakhir menjadi 900 1000000 menambah waktu hingga rentang 1 detik: Solusi ketiga selesai dalam 1 detik dan solusi kedua selesai dalam 2 detik. Tampaknya juga tidak terlalu sensitif terhadap tingkat pengoptimalan atau GCC vs. Dentang.   -  person Potatoswatter    schedule 22.07.2015
comment
@Potatoswatter UVA bekerja seperti ini: Anda memberikan mereka file sumber, mereka mengompilasinya dan menjalankannya di mesin mereka, memberi masukan kepada program Anda, yang biasanya cukup sulit. Jika program Anda gagal diselesaikan dalam batas waktunya (3 detik di sini), mereka menilainya salah. Sayangnya, mereka merahasiakan masukannya, jadi saya tidak bisa mempostingnya di sini. Mereka mempublikasikan beberapa contoh masukan - yang telah saya posting di sini - tetapi biasanya sangat ringan sehingga tidak cocok untuk pengujian apa pun. Waktu yang saya posting adalah waktu solusi dijalankan di server mereka dan masukannya.   -  person    schedule 22.07.2015
comment
@Potatoswatter Saya menjalankan program di mesin saya dengan input 1 1000000. Hasil: Solusi dengan vector biasa: 0,046 detik; Solusi dengan vector dan iterator: 0,064 detik; Solusi dengan unordered_map: 1,26 detik; Dan solusi dengan unordered_map dan lapisan iterator tambahan: 2,656 detik. BTW, apakah Anda yakin dalam masukan Anda, solusi ketiga selesai lebih cepat daripada solusi kedua? Itu akan menarik... bagaimana menambahkan iterator tersebut dapat mempercepat waktu eksekusi?   -  person    schedule 22.07.2015
comment
Ya, saya yakin solusi #2 (hanyaunordered_map) dua kali lebih cepat dari #3 (ditambah lengthsbase::iterator) di mesin saya. Saya tidak tahu kenapa. Saya menggunakan GCC 5.1 dan Clang 3.6 di OS X, pada Macbook Haswell 2.3 GHz Core i7. Bagaimanapun, bahkan jika angka Anda 2:1 di arah yang berlawanan, itu masih merupakan hasil yang sama sekali berbeda dan tidak mengejutkan apa yang dikatakan oleh juri online.   -  person Potatoswatter    schedule 22.07.2015
comment
Maksud saya bukan tentang masukan apa yang digunakan oleh juri online, melainkan apa yang menurut Anda harus digunakan oleh kami di SO. Sebaiknya setiap penjawab tidak merancang tesnya sendiri.   -  person Potatoswatter    schedule 22.07.2015
comment
Eek, komentar yang lain itu terbalik. Bukan dua kali lebih cepat, tapi dua kali lebih lama. Saya memeriksa ulang. Nyata.   -  person Potatoswatter    schedule 22.07.2015
comment
@Potatoswatter Saya menguji keempat solusi terhadap input 1 1000000, diulang 200 kali. Vektor biasa: 1,531 detik; vektor dengan iterator: 3,087 detik; peta polos tak berurutan: 33,821 detik; peta tidak berurutan dengan iterator: Entahlah, menghentikannya setelah 31 menit 0,482 detik. Linux mint 17.2 64 bit, versi g++ Ubuntu 4.8.4-2ubuntu1~14.04 dengan flag -std=c++11 -O2, prosesor Celeron 2955U @1.4 GHz x 2   -  person    schedule 22.07.2015


Jawaban (1)


tampaknya merupakan masalah di GCC 4.8. Itu tidak terjadi di 4,9 ke atas. Untuk beberapa alasan, loop luar berikutnya (dengan cache unordered_map yang terisi) berjalan semakin lambat, bukan lebih cepat. Saya tidak yakin kenapa, karena unordered_map tidak bertambah besar.

Jika Anda mengikuti tautan itu dan mengalihkan GCC 4.8 ke 4.9, maka Anda akan melihat perilaku yang diharapkan di mana proses selanjutnya pada rentang numerik yang sama menambah sedikit waktu karena mereka memanfaatkan cache.

Secara keseluruhan, filosofi menjadi "konservatif" dengan pembaruan kompiler sudah ketinggalan jaman sejak lama. Kompiler saat ini telah diuji secara ketat dan Anda harus menggunakan rilis terbaru (atau setidaknya yang terbaru) untuk pengembangan sehari-hari.

Bagi hakim online yang menjatuhkan Anda pada bug yang sudah lama diperbaiki adalah hal yang kejam.

person Potatoswatter    schedule 23.07.2015
comment
Terima kasih banyak. Memang, hakim mengatakan sendiri ia menggunakan *4.8.2 - GNU C++ Compiler dengan opsi: -lm -lcrypt -O2 -std=c++11 -pipe -DONLINE_JUDGE untuk C++11. Bagi saya, saya hanya menggunakan versi standar dari repositori standar... Saya harus mengaktifkan backport jika saya ingin mengupgrade kompiler saya, saya khawatir... - person ; 23.07.2015
comment
Silakan lihat juga tolok ukur yang saya buat: melpon.org/wandbox/permlink/XRocZilmeRupsDk9 Ini dia situasi sebaliknya: gcc 4.8.2 menghasilkan program yang berjalan dalam ~7 detik, sedangkan gcc 4.9.0 dan even gcc 5.2.0 dan even gcc 6.0.0 menghasilkan program yang dimatikan oleh Wandbox karena berjalan terlalu lama. Tolok ukurnya agak mirip dengan masalah yang dibahas di sini. - person ; 23.07.2015
comment
BTW Clang 3.6.0 tampaknya baik-baik saja di kedua tautan Wandbox. - person ; 23.07.2015