Adakah yang tahu bagaimana tipe kamus built-in untuk python diimplementasikan? Pemahaman saya adalah bahwa ini semacam tabel hash, tapi saya belum dapat menemukan jawaban definitif.
python
data-structures
dictionary
ricree
sumber
sumber
Jawaban:
Berikut ini semua tentang dikte Python yang dapat saya kumpulkan (mungkin lebih dari yang ingin diketahui siapa pun; tetapi jawabannya komprehensif).
dict
menggunakan pengalamatan terbuka untuk menyelesaikan tabrakan hash (dijelaskan di bawah) (lihat dictobject.c: 296-297 ).O(1)
pencarian berdasarkan indeks).Gambar di bawah ini adalah representasi logis dari tabel hash Python. Pada gambar di bawah ini,
0, 1, ..., i, ...
di sebelah kiri adalah indeks slot di tabel hash (mereka hanya untuk tujuan ilustrasi dan tidak disimpan bersama dengan tabel!).Ketika dikt diinisialisasi baru itu dimulai dengan 8 slot . (lihat dictobject.h: 49 )
i
,, yang didasarkan pada hash tombol. CPython awalnya menggunakani = hash(key) & mask
(di manamask = PyDictMINSIZE - 1
, tapi itu tidak terlalu penting). Perhatikan saja bahwa slot awali
,, yang diperiksa tergantung pada hash kunci.<hash|key|value>
). Tapi bagaimana jika slot itu ditempati !? Kemungkinan besar karena entri lain memiliki hash yang sama (tabrakan hash!)==
bukanis
perbandingan) dari entri dalam slot terhadap hash dan kunci dari entri saat ini untuk dimasukkan ( dictobject.c : 337.344-345 ) masing-masing. Jika keduanya cocok, maka dianggap entri sudah ada, menyerah dan pindah ke entri berikutnya yang akan dimasukkan. Jika hash atau kunci tidak cocok, itu mulai memeriksa .i+1, i+2, ...
dan menggunakan yang tersedia pertama (itu linear probing). Tetapi karena alasan yang dijelaskan dengan indah di komentar (lihat dictobject.c: 33-126 ), CPython menggunakan probing acak . Dalam penyelidikan acak, slot berikutnya diambil dalam urutan acak semu. Entri ditambahkan ke slot kosong pertama. Untuk diskusi ini, algoritma aktual yang digunakan untuk memilih slot berikutnya tidak terlalu penting (lihat dictobject.c: 33-126 untuk algoritme untuk menyelidik). Yang penting adalah bahwa slot diselidiki sampai slot kosong pertama ditemukan.dict
ukurannya akan diubah jika dua pertiga penuh. Ini menghindari memperlambat pencarian. (lihat dictobject.h: 64-65 )CATATAN: Saya melakukan penelitian tentang implementasi Python Dict sebagai jawaban atas pertanyaan saya sendiri tentang bagaimana beberapa entri dalam suatu dict dapat memiliki nilai hash yang sama. Saya memposting versi tanggapan yang sedikit diedit di sini karena semua penelitian juga sangat relevan untuk pertanyaan ini.
sumber
Inilah kursus singkatnya:
Aspek yang dipesan tidak resmi pada Python 3.6 (untuk memberikan implementasi lain kesempatan untuk mengikuti), tetapi resmi dalam Python 3.7 .
Kamus Python adalah Tabel Hash
Untuk waktu yang lama, ini bekerja persis seperti ini. Python akan mengalokasikan 8 baris kosong dan menggunakan hash untuk menentukan di mana harus menempel pasangan kunci-nilai. Misalnya, jika hash untuk kunci berakhir pada 001, itu akan menempel di indeks 1 (yaitu 2) (seperti contoh di bawah ini.)
Setiap baris membutuhkan 24 byte pada arsitektur 64 bit, 12 pada 32 bit. (Perhatikan bahwa tajuk kolom hanyalah label untuk keperluan kami di sini - tidak ada di memori.)
Jika hash berakhir sama dengan hash kunci yang sudah ada sebelumnya, ini adalah tabrakan, dan kemudian akan menempel pasangan nilai kunci di lokasi yang berbeda.
Setelah 5 nilai kunci disimpan, saat menambahkan pasangan nilai kunci lainnya, kemungkinan tabrakan hash terlalu besar, sehingga kamus menjadi dua kali lipat. Dalam proses 64 bit, sebelum mengubah ukuran, kami memiliki 72 byte kosong, dan setelah itu, kami membuang 240 byte karena 10 baris kosong.
Ini membutuhkan banyak ruang, tetapi waktu pencariannya cukup konstan. Algoritma perbandingan kunci adalah untuk menghitung hash, pergi ke lokasi yang diharapkan, membandingkan id kunci - jika mereka objek yang sama, mereka sama. Jika tidak maka bandingkan nilai hash, jika tidak sama, mereka tidak sama. Lain, maka kita akhirnya membandingkan kunci untuk kesetaraan, dan jika mereka sama, kembalikan nilainya. Perbandingan akhir untuk kesetaraan bisa sangat lambat, tetapi pemeriksaan sebelumnya biasanya memotong perbandingan akhir, membuat pencarian sangat cepat.
Tabrakan memperlambat segalanya, dan penyerang secara teoritis dapat menggunakan tabrakan hash untuk melakukan serangan penolakan layanan, jadi kami mengacak inisialisasi fungsi hash sehingga menghitung hash yang berbeda untuk setiap proses Python baru.
Ruang terbuang yang dijelaskan di atas telah mengarahkan kami untuk memodifikasi implementasi kamus, dengan fitur baru yang menarik bahwa kamus sekarang dipesan melalui penyisipan.
Tabel Compact Hash Baru
Sebagai gantinya, kita mulai dengan mengalokasikan array untuk indeks penyisipan.
Karena pasangan nilai kunci pertama kami berada di slot kedua, kami mengindeks seperti ini:
Dan meja kami baru saja diisi dengan urutan penyisipan:
Jadi ketika kita melakukan pencarian untuk kunci, kita menggunakan hash untuk memeriksa posisi yang kita harapkan (dalam hal ini, kita langsung ke indeks 1 dari array), kemudian pergi ke indeks itu di tabel-hash (misalnya indeks 0 ), periksa apakah kunci sama (menggunakan algoritma yang sama dijelaskan sebelumnya), dan jika demikian, kembalikan nilainya.
Kami mempertahankan waktu pencarian konstan, dengan kehilangan kecepatan kecil dalam beberapa kasus dan keuntungan dalam kasus lain, dengan sisi positifnya kami menghemat cukup banyak ruang selama implementasi yang sudah ada sebelumnya dan kami mempertahankan urutan penyisipan. Satu-satunya ruang yang terbuang adalah null byte dalam array indeks.
Raymond Hettinger memperkenalkan ini di python-dev pada bulan Desember 2012. Akhirnya masuk ke CPython dengan Python 3.6 . Pemesanan melalui penyisipan dianggap sebagai detail implementasi untuk 3.6 untuk memungkinkan implementasi lain dari Python kesempatan untuk mengejar ketinggalan.
Tombol Bersama
Optimalisasi lain untuk menghemat ruang adalah implementasi yang berbagi kunci. Jadi, alih-alih memiliki kamus berlebihan yang mengambil semua ruang itu, kami memiliki kamus yang menggunakan kembali kunci bersama dan hash kunci. Anda bisa memikirkannya seperti ini:
Untuk mesin 64 bit, ini bisa menghemat hingga 16 byte per kunci per kamus tambahan.
Tombol Bersama untuk Objek & Alternatif Kustom
Dikte bersama ini dimaksudkan untuk digunakan untuk objek khusus
__dict__
. Untuk mendapatkan perilaku ini, saya percaya Anda harus menyelesaikan mengisi__dict__
sebelum Anda instantiate objek berikutnya ( lihat PEP 412 ). Ini berarti Anda harus menetapkan semua atribut Anda di__init__
atau__new__
, jika tidak, Anda mungkin tidak mendapatkan penghematan ruang.Namun, jika Anda tahu semua atribut Anda pada saat Anda
__init__
dieksekusi, Anda juga bisa menyediakan__slots__
untuk objek Anda, dan menjamin bahwa__dict__
itu tidak dibuat sama sekali (jika tidak tersedia pada orang tua), atau bahkan memperbolehkan__dict__
tetapi menjamin bahwa atribut Anda yang diramalkan adalah disimpan dalam slot. Untuk lebih lanjut__slots__
, lihat jawaban saya di sini .Lihat juga:
**kwargs
fungsi.sumber
find_empty_slot
: github.com/python/cpython/blob/master/Objects/dictobject.c # L969 - dan mulai jalur 134 ada beberapa prosa yang menjelaskannya.Kamus Python menggunakan pengalamatan terbuka ( referensi di dalam kode Cantik )
NB! Pengalamatan terbuka , alias hashing tertutup harus, seperti dicatat dalam Wikipedia, tidak menjadi bingung dengan hashing terbuka yang berlawanan !
Pengalamatan terbuka berarti bahwa dict menggunakan slot array, dan ketika posisi utama objek diambil dalam dict, tempat objek tersebut dicari pada indeks yang berbeda dalam array yang sama, menggunakan skema "perturbation", di mana nilai hash objek memainkan bagian .
sumber