Pembicaraan baru-baru ini unordered_map
di C ++ membuat saya sadar bahwa saya harus menggunakan unordered_map
untuk sebagian besar kasus di mana saya gunakan map
sebelumnya, karena efisiensi pencarian ( diamortisasi O (1) vs O (log n) ). Sering kali saya menggunakan peta, saya menggunakan salah satu int
atau std::string
sebagai jenis kunci; karenanya, saya tidak punya masalah dengan definisi fungsi hash. Semakin saya memikirkannya, semakin saya sadari bahwa saya tidak dapat menemukan alasan untuk menggunakan std::map
lebih std::unordered_map
dalam kasus kunci dengan tipe sederhana - Saya melihat antarmuka, dan tidak menemukan apa pun. perbedaan signifikan yang akan memengaruhi kode saya.
Oleh karena itu pertanyaan: apakah ada alasan untuk menggunakan std::map
lebih std::unordered_map
dalam kasus jenis sederhana seperti int
dan std::string
?
Saya meminta dari sudut pandang pemrograman ketat - Saya tahu itu tidak sepenuhnya dianggap standar, dan itu dapat menimbulkan masalah dengan porting.
Juga, saya berharap bahwa salah satu jawaban yang benar mungkin "itu lebih efisien untuk set data yang lebih kecil" karena overhead yang lebih kecil (apakah itu benar?) - maka saya ingin membatasi pertanyaan pada kasus di mana jumlah kunci tidak mudah (> 1 024).
Sunting: ya, saya lupa yang sudah jelas (terima kasih GMan!) - ya, peta dipesan tentu saja - Saya tahu itu, dan saya mencari alasan lain.
sumber
Jawaban:
Jangan lupa bahwa
map
elemen-elemennya tetap teratur. Jika Anda tidak bisa melepaskannya, jelas Anda tidak bisa menggunakannyaunordered_map
.Hal lain yang perlu diingat adalah yang
unordered_map
umumnya menggunakan lebih banyak memori.map
hanya memiliki beberapa petunjuk rumah tangga, dan memori untuk setiap objek. Sebaliknya,unordered_map
memiliki array besar (ini bisa menjadi sangat besar dalam beberapa implementasi), dan kemudian memori tambahan untuk setiap objek. Jika Anda perlu sadar memori,map
harus membuktikan lebih baik, karena tidak memiliki array yang besar.Jadi, jika Anda perlu pencarian-pencarian murni, saya akan mengatakan
unordered_map
adalah cara untuk pergi. Tetapi selalu ada trade-off, dan jika Anda tidak mampu membelinya, maka Anda tidak dapat menggunakannya.Hanya dari pengalaman pribadi, saya menemukan peningkatan besar dalam kinerja (diukur, tentu saja) ketika menggunakan
unordered_map
alih-alihmap
dalam tabel pencarian entitas utama.Di sisi lain, saya merasa jauh lebih lambat dalam memasukkan dan menghapus elemen berulang kali. Ini bagus untuk koleksi elemen yang relatif statis, tetapi jika Anda melakukan banyak penyisipan dan penghapusan, hashing + bucket sepertinya bertambah. (Catatan, ini lebih dari beberapa iterasi.)
sumber
unordered_map
dan memesannya di awal - apakah Anda masih membayar penalti dengan banyak penyisipan? Katakanlah, Anda hanya menyisipkan satu kali ketika Anda membangun tabel pencarian - dan kemudian hanya membaca dari itu.Jika Anda ingin membandingkan kecepatan
std::map
danstd::unordered_map
implementasi Anda, Anda dapat menggunakan proyek sparsehash Google yang memiliki program time_hash_map untuk menentukan waktu mereka. Misalnya, dengan gcc 4.4.2 pada sistem Linux x86_64sumber
Saya kira-kira sama dengan poin yang dibuat GM: tergantung pada jenis penggunaannya,
std::map
bisa (dan sering) lebih cepat daripadastd::tr1::unordered_map
(menggunakan implementasi yang termasuk dalam VS 2008 SP1).Ada beberapa faktor rumit yang perlu diingat. Misalnya, dalam
std::map
, Anda membandingkan kunci, yang berarti Anda hanya pernah melihat cukup awal kunci untuk membedakan antara cabang-cabang pohon kanan dan kiri. Dalam pengalaman saya, hampir satu-satunya waktu Anda melihat seluruh kunci adalah jika Anda menggunakan sesuatu seperti int yang dapat Anda bandingkan dalam satu instruksi. Dengan jenis kunci yang lebih khas seperti std :: string, Anda sering membandingkan hanya beberapa karakter atau lebih.Fungsi hash yang layak, sebaliknya, selalu melihat seluruh kunci. TKI, bahkan jika tabel lookup adalah kompleksitas konstan, hash itu sendiri memiliki kompleksitas linear (meskipun pada panjang kunci, bukan jumlah item). Dengan string panjang sebagai kunci, sebuah
std::map
mungkin menyelesaikan pencarian sebelumunordered_map
bahkan akan memulai pencariannya.Kedua, sementara ada beberapa metode mengubah ukuran tabel hash, kebanyakan dari mereka adalah cukup lambat - ke titik bahwa kecuali pencarian yang jauh lebih sering daripada sisipan dan penghapusan, std :: peta sering akan lebih cepat dari
std::unordered_map
.Tentu saja, seperti yang saya sebutkan di komentar pada pertanyaan Anda sebelumnya, Anda juga dapat menggunakan tabel pohon. Ini memiliki kelebihan dan kekurangan. Di satu sisi, itu membatasi kasus terburuk ke pohon. Ini juga memungkinkan penyisipan dan penghapusan yang cepat, karena (setidaknya ketika saya sudah melakukannya) saya telah menggunakan tabel ukuran tetap. Menghapus semua ukuran tabel memungkinkan Anda untuk menjaga tabel hash Anda lebih sederhana dan biasanya lebih cepat.
Satu hal lain: persyaratan untuk hashing dan peta berbasis pohon berbeda. Hashing jelas membutuhkan fungsi hash, dan perbandingan kesetaraan, di mana peta yang dipesan membutuhkan perbandingan yang kurang. Tentu saja hibrida yang saya sebutkan membutuhkan keduanya. Tentu saja, untuk kasus umum menggunakan string sebagai kunci, ini sebenarnya bukan masalah, tetapi beberapa jenis kunci lebih cocok dipesan daripada hashing (atau sebaliknya).
sumber
dynamic hashing
teknik, yang terdiri dari memiliki periode transisi di mana setiap kali Anda memasukkan item, Anda juga mengulangik
item lainnya. Tentu saja, ini berarti bahwa selama masa transisi Anda harus mencari 2 tabel yang berbeda ...unordered_map
perlu mengkonfirmasi kecocokan hash dengan perbandingan penuh, jadi itu semua tergantung pada bagian proses pencarian yang Anda kontras.Saya tertarik dengan jawaban dari @Jerry Coffin, yang menyarankan bahwa peta yang dipesan akan menunjukkan peningkatan kinerja pada string yang panjang, setelah beberapa percobaan (yang dapat diunduh dari pastebin ), saya telah menemukan bahwa ini hanya berlaku untuk koleksi string acak, ketika peta diinisialisasi dengan kamus diurutkan (yang berisi kata-kata dengan banyak awalan-tumpang tindih), aturan ini rusak, mungkin karena peningkatan kedalaman pohon yang diperlukan untuk mengambil nilai. Hasilnya ditunjukkan di bawah ini, kolom nomor 1 adalah waktu memasukkan, 2 adalah waktu pengambilan.
sumber
std::map
biasanya mengunggulistd::unordered_map
, terutama untuk kunci integer tetapi ~ 100 kunci tampaknya kehilangan tepi danstd::unordered_map
mulai menang. Memasukkan urutan yang sudah dipesan ke dalamstd::map
sangat buruk, Anda akan mendapatkan skenario terburuknya (O (N)).Saya hanya akan menunjukkan bahwa ... ada banyak jenis
unordered_map
s.Lihat Artikel Wikipedia di peta hash. Bergantung pada implementasi yang digunakan, karakteristik dalam hal pencarian, penyisipan, dan penghapusan mungkin sangat bervariasi.
Dan itulah yang paling membuat saya khawatir dengan penambahan
unordered_map
STL: mereka harus memilih implementasi tertentu karena saya ragu mereka akan turunPolicy
, dan jadi kita akan terjebak dengan implementasi untuk penggunaan rata-rata dan tidak ada untuk kasus lainnya ...Misalnya beberapa peta hash memiliki pengulangan linear, di mana alih-alih mengulangi seluruh peta hash sekaligus, sebagian diulangi di setiap penyisipan, yang membantu mengamortisasi biaya.
Contoh lain: beberapa peta hash menggunakan daftar node untuk sebuah ember, yang lain menggunakan peta, yang lain tidak menggunakan node tetapi menemukan slot terdekat dan terakhir beberapa akan menggunakan daftar node tetapi menyusun ulang sehingga elemen yang terakhir diakses ada di depan (seperti caching).
Jadi pada saat ini saya cenderung memilih
std::map
atau mungkin aloki::AssocVector
(untuk set data beku).Jangan salah paham, saya ingin menggunakan
std::unordered_map
dan saya mungkin di masa depan, tetapi sulit untuk "mempercayai" portabilitas wadah seperti itu ketika Anda memikirkan semua cara menerapkannya dan berbagai pertunjukan yang dihasilkan ini.sumber
Perbedaan signifikan yang belum benar-benar disebutkan di sini:
map
menjaga iterator agar semua elemen stabil, di C ++ 17 Anda bahkan dapat memindahkan elemen dari satumap
ke yang lain tanpa membatalkan iterator ke elemen (dan jika diimplementasikan dengan benar tanpa potensi alokasi).map
pengaturan waktu untuk operasi tunggal biasanya lebih konsisten karena mereka tidak pernah membutuhkan alokasi besar.unordered_map
menggunakanstd::hash
seperti yang diterapkan di libstdc ++ rentan terhadap DoS jika diumpankan dengan input yang tidak terpercaya (menggunakan MurmurHash2 dengan seed konstan - bukan bahwa seeding akan sangat membantu, lihat https://emboss.github.io/blog/2012/12/14/ breaking-murmur-hash-flooding-dos-reloaded / ).sumber
Tabel hash memiliki konstanta yang lebih tinggi daripada implementasi peta umum, yang menjadi signifikan untuk wadah kecil. Ukuran maks adalah 10, 100, atau mungkin bahkan 1.000 atau lebih? Konstanta sama seperti sebelumnya, tetapi O (log n) dekat dengan O (k). (Ingat kompleksitas logaritmik masih sangat bagus.)
Apa yang membuat fungsi hash yang baik tergantung pada karakteristik data Anda; jadi jika saya tidak berencana melihat fungsi hash kustom (tapi tentu saja dapat berubah pikiran nanti, dan dengan mudah karena saya mengetik hampir semua) dan meskipun default dipilih untuk tampil baik untuk banyak sumber data, saya menemukan yang dipesan sifat peta cukup membantu awalnya bahwa saya masih default untuk memetakan daripada tabel hash dalam kasus itu.
Ditambah lagi, Anda tidak perlu berpikir untuk menulis fungsi hash untuk tipe lain (biasanya UDT), dan cukup menulis op <(yang Anda inginkan juga).
sumber
map
dan salah satunyaunordered_map
, dengan platform tertentu dan ukuran cache tertentu, dan melakukan analisis yang kompleks. : PAlasan telah diberikan dalam jawaban lain; ini yang lain.
std :: map (pohon biner seimbang) operasi diamortisasi O (log n) dan kasus terburuk O (log n). operasi std :: unordered_map (tabel hash) diamortisasi O (1) dan kasus terburuk O (n).
Bagaimana ini dimainkan dalam praktek adalah bahwa tabel hash "cegukan" sesekali dengan operasi O (n), yang mungkin atau mungkin bukan sesuatu yang bisa ditoleransi aplikasi Anda. Jika tidak bisa menerimanya, Anda lebih suka std :: map over std :: unordered_map.
sumber
Ringkasan
Dengan asumsi pemesanan tidak penting:
std::unordered_map
std::map
. Ini karena bacaan tentang ituO(log n)
.std::map
adalah pilihan yang baik.std::unordered_map
.Konteks Sejarah
Dalam sebagian besar bahasa, peta tidak berurutan (alias kamus berbasis hash) adalah peta default namun di C ++ Anda mendapatkan peta yang dipesan sebagai peta default. Bagaimana itu bisa terjadi? Beberapa orang keliru berasumsi bahwa komite C ++ membuat keputusan ini dalam kearifan unik mereka tetapi sayangnya sayangnya lebih buruk dari itu.
Dipercaya secara luas bahwa C ++ berakhir dengan peta yang dipesan sebagai default karena tidak ada terlalu banyak parameter tentang bagaimana mereka dapat diimplementasikan. Di sisi lain, implementasi berbasis hash memiliki banyak hal untuk dibicarakan. Jadi untuk menghindari kemacetan dalam standardisasi, mereka hanya cocok dengan peta yang dipesan. Sekitar tahun 2005, banyak bahasa sudah memiliki implementasi implementasi berbasis hash yang baik sehingga lebih mudah bagi komite untuk menerima yang baru
std::unordered_map
. Di dunia yang sempurna,std::map
akan tidak teratur dan kita akan memilikistd::ordered_map
tipe yang terpisah.Performa
Di bawah dua grafik harus berbicara sendiri ( sumber ):
sumber
Saya telah membuat tes baru-baru ini yang membuat 50000 bergabung & urutkan. Itu berarti jika kunci string sama, gabungkan string byte. Dan hasil akhirnya harus disortir. Jadi ini termasuk mencari setiap penyisipan.
Untuk
map
implementasi, dibutuhkan 200 ms untuk menyelesaikan pekerjaan. Untukunordered_map
+map
, dibutuhkan 70 ms untukunordered_map
penyisipan dan 80 ms untukmap
penyisipan. Jadi implementasi hybrid 50 ms lebih cepat.Kita harus berpikir dua kali sebelum menggunakan
map
. Jika Anda hanya perlu data yang akan diurutkan dalam hasil akhir program Anda, solusi hybrid mungkin lebih baik.sumber
Tambahan kecil untuk semua hal di atas:
Penggunaan yang lebih baik
map
, ketika Anda perlu mendapatkan elemen berdasarkan rentang, karena mereka diurutkan dan Anda hanya bisa beralih dari satu batas ke batas lainnya.sumber
Dari: http://www.cplusplus.com/reference/map/map/
"Secara internal, elemen-elemen dalam peta selalu diurutkan berdasarkan kuncinya mengikuti kriteria urutan lemah spesifik yang ditunjukkan oleh objek perbandingan internal (dari tipe Bandingkan).
kontainer peta pada umumnya lebih lambat daripada wadah unordered_map untuk mengakses elemen individu dengan kunci mereka, tetapi mereka memungkinkan iterasi langsung pada subset berdasarkan pesanan mereka. "
sumber