Abaikan Daftar vs. Pohon Pencarian Biner

Jawaban:

257

Abaikan daftar lebih dapat menerima akses / modifikasi bersamaan. Herb Sutter menulis artikel tentang struktur data di lingkungan bersamaan. Ini memiliki informasi yang lebih mendalam.

Implementasi pohon pencarian biner yang paling sering digunakan adalah pohon merah-hitam . Masalah-masalah bersamaan muncul ketika pohon dimodifikasi, seringkali perlu menyeimbangkan kembali. Operasi penyeimbangan kembali dapat memengaruhi sebagian besar pohon, yang akan membutuhkan kunci mutex pada banyak simpul pohon. Memasukkan sebuah simpul ke daftar lewati jauh lebih terlokalisasi, hanya simpul yang terhubung langsung ke simpul yang terpengaruh perlu dikunci.


Pembaruan dari komentar Jon Harrops

Saya membaca tulisan Fraser dan Harris, pemrograman bersamaan tanpa kunci . Hal yang sangat bagus jika Anda tertarik pada struktur data bebas kunci. Makalah ini berfokus pada Memori Transaksional dan operasi teoretis multi-kata-bandingkan-dan-swap MCAS. Keduanya disimulasikan dalam perangkat lunak karena belum ada perangkat keras yang mendukungnya. Saya cukup terkesan bahwa mereka mampu membangun MCAS dalam perangkat lunak sama sekali.

Saya tidak menemukan hal-hal memori transaksional sangat menarik karena memerlukan pengumpul sampah. Juga memori transaksional perangkat lunak terganggu dengan masalah kinerja. Namun, saya akan sangat senang jika memori transaksional perangkat keras menjadi hal biasa. Pada akhirnya ini masih penelitian dan tidak akan digunakan untuk kode produksi selama satu dekade atau lebih.

Pada bagian 8.2 mereka membandingkan kinerja beberapa implementasi pohon konkuren. Saya akan meringkas temuan mereka. Layak untuk mengunduh pdf karena memiliki beberapa grafik yang sangat informatif di halaman 50, 53, dan 54.

  • Mengunci daftar lompatan sangat cepat. Mereka berskala sangat baik dengan jumlah akses bersamaan. Inilah yang membuat daftar lompatan istimewa, struktur data berbasis kunci lainnya cenderung serak di bawah tekanan.
  • Daftar lompatan bebas kunci secara konsisten lebih cepat daripada mengunci daftar lompatan tetapi hanya nyaris.
  • daftar lompatan transaksional secara konsisten 2-3 kali lebih lambat daripada versi penguncian dan non-penguncian.
  • mengunci pohon merah-hitam yang serak di bawah akses bersamaan. Kinerja mereka menurun secara linear dengan setiap pengguna bersamaan yang baru. Dari dua implementasi penguncian pohon merah-hitam yang diketahui, satu pada dasarnya memiliki kunci global selama penyeimbangan kembali pohon. Yang lain menggunakan eskalasi kunci mewah (dan rumit) tetapi masih tidak secara signifikan melakukan versi kunci global.
  • pohon merah-hitam bebas kunci tidak ada (tidak lagi benar, lihat Pembaruan).
  • pohon merah-hitam transaksional dapat dibandingkan dengan daftar-lompatan transaksional. Itu sangat mengejutkan dan sangat menjanjikan. Memori transaksional, meskipun lebih lambat jika jauh lebih mudah untuk ditulis. Ini bisa semudah mencari dan mengganti dengan cepat pada versi yang tidak bersamaan.

Pembaruan
Berikut adalah kertas tentang pohon bebas-kunci: Pohon Merah-Hitam Bebas-Kunci Menggunakan CAS .
Saya belum melihat ke dalamnya, tetapi di permukaan tampak padat.

deft_code
sumber
3
Belum lagi bahwa dalam skiplist non-degenerasi, sekitar 50% dari node hanya boleh memiliki satu tautan yang membuat penyisipan dan penghapusan sangat efisien.
Adisak
2
Penyeimbangan ulang tidak membutuhkan kunci mutex. Lihat cl.cam.ac.uk/research/srg/netos/lock-free
Jon Harrop
3
@ Jon, ya dan tidak. Tidak ada implementasi pohon merah-hitam bebas kunci yang dikenal. Fraser dan Harris menunjukkan bagaimana pohon transaksional berbasis memori merah-hitam diimplementasikan dan kinerjanya. Memori transaksional masih sangat banyak di arena penelitian, jadi dalam kode produksi, pohon merah-hitam masih perlu mengunci sebagian besar pohon.
deft_code
4
@deft_code: Intel baru-baru ini mengumumkan implementasi Memori Transaksional melalui TSX di Haswell. Ini mungkin membuktikan menariknya struktur-struktur data kunci gratis yang Anda sebutkan.
Mike Bailey
2
Saya pikir jawaban Fizz lebih baru (dari 2015) daripada jawaban ini (2012) dan karena itu mungkin harus menjadi jawaban yang disukai sekarang.
fnl
81

Pertama, Anda tidak dapat secara adil membandingkan struktur data acak dengan yang memberikan Anda jaminan terburuk.

Daftar lompatan setara dengan pohon pencarian biner acak seimbang (RBST) dengan cara yang dijelaskan secara lebih rinci di Dean dan Jones ' "Menjelajahi Dualitas Antara Daftar Lewati dan Pohon Pencarian Biner" .

Sebaliknya, Anda juga dapat memiliki daftar lompatan deterministik yang menjamin kinerja kasus terburuk, lih. Munro et al.

Berbeda dengan beberapa klaim di atas, Anda dapat memiliki implementasi pohon pencarian biner (BST) yang bekerja dengan baik dalam pemrograman bersamaan. Masalah potensial dengan BST yang berfokus pada konkurensi adalah Anda tidak dapat dengan mudah mendapatkan jaminan yang sama tentang keseimbangan seperti yang Anda lakukan dari pohon merah-hitam (RB). (Tapi "standar", yaitu randomzided, lewati daftar juga tidak memberi Anda jaminan ini.) Ada pertukaran antara mempertahankan keseimbangan setiap saat dan akses yang baik (dan mudah diprogram), sehingga pohon RB yang santai biasanya digunakan ketika konkurensi yang baik diinginkan. Relaksasi terdiri dari tidak menyeimbangkan pohon segera. Untuk survei yang agak bertanggal (1998) lihat Hanke's '' The Performance of Algoritma Pohon Merah-Hitam bersamaan '' [ps.gz] .

Salah satu peningkatan terbaru pada hal ini adalah apa yang disebut pohon kromatik (pada dasarnya Anda memiliki beberapa bobot sehingga hitam akan menjadi 1 dan merah akan menjadi nol, tetapi Anda juga membiarkan nilai di antaranya). Dan bagaimana pohon berwarna berubah terhadap daftar lompatan? Mari kita lihat apa yang Brown dkk. "Teknik Umum untuk Pohon Tanpa Pemblokiran" (2014) harus mengatakan:

dengan 128 utas, algoritme kami mengungguli skiplist non-pemblokiran Java sebesar 13% hingga 156%, pohon AVL berbasis kunci dari Bronson et al. sebesar 63% hingga 224%, dan sebuah RBT yang menggunakan memori transaksional perangkat lunak (STM) sebanyak 13 hingga 134 kali

EDIT menambahkan: daftar lompatan berbasis kunci Pugh, yang diperbandingkan dalam Fraser dan Harris (2007) "Pemrograman Bersamaan Tanpa Kunci" mendekati versi bebas kunci mereka sendiri (satu poin yang ditekankan dalam jawaban teratas di sini), juga di-tweak untuk operasi bersamaan yang baik, lih. "Pemeliharaan Serentak dari Daftar Lewati" Pugh , meskipun dengan cara yang agak ringan. Namun demikian, satu makalah yang lebih baru / 2009 "Algoritma daftar sederhana Optimisme"oleh Herlihy et al., yang mengusulkan implementasi berbasis kunci yang seharusnya lebih sederhana (daripada Pugh) dari daftar lompatan bersamaan, mengkritik Pugh karena tidak memberikan bukti kebenaran yang cukup meyakinkan bagi mereka. Mengesampingkan keraguan ini (mungkin terlalu berlebihan), Herlihy et al. menunjukkan bahwa penerapan daftar loncatan berbasis penguncian yang lebih sederhana sebenarnya gagal untuk menskalakan serta penerapan JDK yang bebas penguncian daripadanya, tetapi hanya untuk pertengkaran tinggi (50% sisipan, 50% penghapusan dan pencarian 0%) ... yang Fraser dan Harris tidak menguji sama sekali; Fraser dan Harris hanya menguji pencarian 75%, sisipan 12,5% dan penghapusan 12,5% (pada daftar lompatan dengan ~ 500K elemen). Implementasi yang lebih sederhana dari Herlihy et al. juga mendekati solusi bebas-kunci dari JDK dalam kasus pertikaian rendah yang mereka uji (70% pencarian, 20% sisipan, 10% dihapus); mereka benar-benar mengalahkan solusi bebas kunci untuk skenario ini ketika mereka membuat daftar lompatan mereka cukup besar, yaitu pergi dari elemen 200K ke 2M, sehingga kemungkinan pertikaian pada kunci apa pun menjadi diabaikan. Akan lebih baik jika Herlihy et al. telah melupakan hangus mereka atas bukti Pugh dan menguji implementasinya juga, tetapi sayangnya mereka tidak melakukan itu.

EDIT2: Saya menemukan (2015 diterbitkan) motherlode dari semua tolok ukur: Gramoli's "Lebih Dari Yang Pernah Ingin Anda Ketahui tentang Sinkronisasi. Synchrobench, Mengukur Dampak Sinkronisasi pada Algoritma Bersamaan" : Berikut adalah gambar kutipan yang relevan dengan pertanyaan ini.

masukkan deskripsi gambar di sini

"Algo.4" adalah prekursor (versi lama, 2011) dari Brown et al. Yang disebutkan di atas. (Saya tidak tahu seberapa baik atau buruk versi 2014). "Algo.26" adalah Herlihy yang disebutkan di atas; seperti yang Anda lihat akan dibuang pada pembaruan, dan jauh lebih buruk pada CPU Intel yang digunakan di sini daripada pada CPU Sun dari kertas asli. "Algo.28" adalah ConcurrentSkipListMap dari JDK; itu tidak berhasil sebaik yang diharapkan dibandingkan dengan implementasi daftar lewati berbasis CAS lainnya. Pemenang di bawah pertentangan tinggi adalah "Algo.2" algoritma berbasis kunci (!!) yang dijelaskan oleh Crain et al. di "Pohon Pencarian Biner Ramah-Contention" dan "Algo.30" adalah "daftar putar berputar" dari "struktur data Logaritmik untuk multicores" . ". Maklum bahwa Gramoli adalah penulis bersama untuk ketiga makalah algoritma pemenang ini. "Algo.27" adalah implementasi C ++ dari daftar lompatan Fraser.

Kesimpulan Gramoli adalah bahwa jauh lebih mudah untuk mengacaukan implementasi pohon konkuren berbasis-CAS daripada mengacaukan daftar lompatan yang sama. Dan berdasarkan angka-angka, sulit untuk tidak setuju. Penjelasannya untuk fakta ini adalah:

Kesulitan dalam merancang pohon yang bebas kunci berasal dari kesulitan memodifikasi banyak referensi secara atom. Lewati daftar terdiri dari menara yang terhubung satu sama lain melalui penerus pointer dan di mana setiap node menunjuk ke simpul tepat di bawahnya. Mereka sering dianggap mirip dengan pohon karena setiap node memiliki penerus di menara penerus dan di bawahnya, bagaimanapun, perbedaan utama adalah bahwa penunjuk ke bawah umumnya tidak dapat diubah sehingga menyederhanakan modifikasi atom dari sebuah node. Perbedaan ini mungkin merupakan alasan mengapa melewatkan daftar mengungguli pohon di bawah pertikaian berat seperti yang diamati pada Gambar [di atas].

Mengatasi kesulitan ini adalah masalah utama dalam karya Brown et al. Mereka memiliki seluruh kertas (2013) terpisah "Pragmatis Primitif untuk Non-blocking Data Structures" untuk membangun multi-record senyawa LL / SC "primitif", yang mereka sebut LLX / SCX, yang diimplementasikan sendiri menggunakan CAS (level mesin). Brown et al. menggunakan blok bangunan LLX / SCX ini pada 2014 mereka (tetapi tidak pada 2011 mereka) implementasi pohon bersamaan.

Saya pikir mungkin juga layak untuk merangkum di sini ide-ide mendasar dari daftar lompatan "no hot spot" / friendly-contention (CF). Ini menambah ide penting dari pohon RB yang rileks (dan struktur data friedrrrency serupa): menara tidak lagi dibangun segera setelah dimasukkan, tetapi ditunda sampai ada sedikit pertengkaran. Sebaliknya, penghapusan menara tinggi dapat menciptakan banyak pertengkaran; ini diamati sejauh kertas loncatan daftar Pugh 1990 bersamaan, itulah sebabnya Pugh memperkenalkan pembalikan pointer pada penghapusan (sebuah berita gembira bahwa halaman Wikipedia pada daftar lompatan masih tidak disebutkan sampai hari ini, sayangnya). Daftar lompatan CF mengambil langkah lebih jauh dan menunda menghapus tingkat atas menara tinggi. Kedua jenis operasi yang tertunda dalam daftar lompatan CF dilakukan oleh utas pengumpul sampah yang terpisah (berbasis CAS), yang oleh penulisnya disebut "utas pengadaptasi".

Kode Synchrobench (termasuk semua algoritma yang diuji) tersedia di: https://github.com/gramoli/synchrobench . Brown et al terbaru. implementasi (tidak termasuk dalam hal di atas) tersedia di http://www.cs.toronto.edu/~tabrown/chromatic/ConcurrentChromaticTreeMap.java Apakah ada yang punya mesin inti 32+ yang tersedia? J / K Maksud saya adalah bahwa Anda dapat menjalankan ini sendiri.

Mendesis
sumber
12

Juga, selain jawaban yang diberikan (kemudahan implementasi dikombinasikan dengan kinerja yang sebanding dengan pohon seimbang). Saya menemukan bahwa mengimplementasikan in-order traversal (maju dan mundur) jauh lebih sederhana karena daftar-lompatan secara efektif memiliki daftar tertaut di dalam implementasinya.

Evan Teran
sumber
1
bukan in-order traversal untuk pohon bin sesederhana: "def func (node): func (kiri (node)); op (node); func (kanan (node))"?
Claudiu
6
Tentu, itu benar jika Anda ingin melintasi semua dalam satu panggilan fungsi. tetapi akan lebih menyebalkan jika Anda ingin memiliki gaya iterator traversal seperti di std :: map.
Evan Teran
@Van: Tidak dalam bahasa fungsional di mana Anda bisa menulis dalam CPS.
Jon Harrop
@ Evan: def iterate(node): for child in iterate(left(node)): yield child; yield node; for child in iterate(right(node)): yield child;? =). kontrol non-lokal dan awesom .. @ Jon: menulis di CPS itu menyebalkan, tapi mungkin Anda maksud dengan kelanjutan? generator pada dasarnya adalah kasus khusus kelanjutan untuk python.
Claudiu
1
@ Evan: ya itu berfungsi selama parameter node terpotong dari pohon selama modifikasi. C ++ traversal memiliki kendala yang sama.
deft_code
10

Dalam praktiknya saya telah menemukan bahwa kinerja B-tree pada proyek saya telah bekerja lebih baik daripada melewatkan daftar. Melewati daftar memang tampak lebih mudah dipahami tetapi menerapkan B-tree tidak terlalu sulit.

Satu keuntungan yang saya tahu adalah bahwa beberapa orang pintar telah bekerja bagaimana menerapkan daftar lompatan bersamaan bebas-penguncian yang hanya menggunakan operasi atom. Misalnya, Java 6 berisi kelas ConcurrentSkipListMap, dan Anda dapat membaca kode sumbernya jika Anda gila.

Tetapi tidak terlalu sulit untuk menulis varian B-tree bersamaan - Saya pernah melihatnya dilakukan oleh orang lain - jika Anda terlebih dahulu membelah dan menggabungkan node "berjaga-jaga" saat Anda berjalan turun pohon maka Anda tidak perlu khawatir tentang kebuntuan dan hanya perlu memegang kunci pada dua tingkat pohon sekaligus. Sinkronisasi overhead akan sedikit lebih tinggi tetapi B-tree mungkin lebih cepat.

Jonathan
sumber
4
Saya pikir Anda tidak boleh menyebut Binary Tree sebagai B-Tree, ada DS yang sama sekali berbeda dengan nama itu
Shihab Shahriar Khan
8

Dari artikel Wikipedia yang Anda kutip:

Θ (n) operasi, yang memaksa kita untuk mengunjungi setiap node dalam urutan menaik (seperti mencetak seluruh daftar) memberikan kesempatan untuk melakukan derandomisasi di belakang layar dari struktur level dari daftar-skip dengan cara yang optimal, membawa daftar lompatan ke O (log n) waktu pencarian. [...] Daftar lompatan, yang baru-baru ini kami tidak melakukan operasi [apapun] Θ (n), tidak memberikan jaminan kinerja kasus terburuk absolut yang sama dengan struktur data pohon seimbang yang lebih tradisional , karena selalu mungkin (walaupun dengan probabilitas sangat rendah) bahwa koin-membalik yang digunakan untuk membangun daftar lompatan akan menghasilkan struktur yang sangat tidak seimbang

EDIT: jadi ini trade-off: Daftar Lewati menggunakan lebih sedikit memori dengan risiko bahwa mereka mungkin berubah menjadi pohon yang tidak seimbang.

Mitch Wheat
sumber
ini akan menjadi alasan untuk tidak menggunakan daftar lewati.
Claudiu
7
mengutip MSDN, "Peluang [untuk 100 elemen level 1] tepatnya 1 dalam 1.267.650.600.228.229.401.491.703.205.376".
peterchen
8
Mengapa Anda mengatakan bahwa mereka menggunakan lebih sedikit memori?
Jonathan
1
@ Peterchen: Begitu, terima kasih. Jadi ini tidak terjadi dengan daftar lewati deterministik? @Mitch: "Lewati Daftar menggunakan lebih sedikit memori". Bagaimana cara melewatkan daftar menggunakan lebih sedikit memori daripada pohon biner seimbang? Sepertinya mereka punya 4 pointer di setiap node dan duplikat node sedangkan pohon hanya memiliki 2 pointer dan tidak ada duplikat.
Jon Harrop
1
@ Jon Harrop: Node di level satu hanya perlu satu pointer per node. Setiap node pada level yang lebih tinggi hanya membutuhkan dua pointer per node (Satu ke node berikutnya dan satu ke level di bawahnya), meskipun tentu saja level 3 node berarti Anda menggunakan total 5 pointer untuk nilai satu itu. Tentu saja, ini masih akan menyedot banyak memori (lebih dari pencarian biner jika Anda ingin daftar lompatan yang tidak berguna dan memiliki dataset besar) ... tapi saya pikir saya kehilangan sesuatu ...
Brian
2

Abaikan daftar diimplementasikan menggunakan daftar.

Solusi bebas kunci ada untuk daftar yang ditautkan secara tunggal dan ganda - tetapi tidak ada solusi bebas kunci yang langsung menggunakan hanya CAS untuk setiap struktur data O (logn).

Namun Anda dapat menggunakan daftar berbasis CAS untuk membuat daftar lewati.

(Perhatikan bahwa MCAS, yang dibuat menggunakan CAS, memungkinkan struktur data sewenang-wenang dan bukti konsep pohon merah-hitam telah dibuat menggunakan MCAS).

Jadi, anehnya, mereka ternyata sangat berguna :-)


sumber
5
"tidak ada solusi bebas kunci yang langsung menggunakan hanya CAS untuk setiap struktur data O (logn)". Tidak benar. Untuk contoh balasan, lihat cl.cam.ac.uk/research/srg/netos/lock-free
Jon Harrop
-1

Abaikan Daftar memang memiliki keuntungan dari pengupasan kunci. Tapi, waktu runtuhnya tergantung pada bagaimana tingkat node baru diputuskan. Biasanya ini dilakukan dengan menggunakan Random (). Pada kamus yang berisi 56.000 kata, daftar lompatan membutuhkan waktu lebih lama daripada pohon rentang dan pohon lebih banyak waktu daripada tabel hash. Dua yang pertama tidak dapat mencocokkan runtime tabel hash. Juga, array dari tabel hash dapat dikunci secara bersamaan juga.

Abaikan Daftar dan daftar berurutan serupa digunakan ketika lokalitas referensi diperlukan. Misalnya: mencari penerbangan berikutnya dan sebelum tanggal dalam aplikasi.

Pohon splay pencarian biner inemem hebat dan lebih sering digunakan.

Lewati Daftar Vs Splay Tree Vs Hash Table Runtime di kamus find op

Harisankar Krishna Swamy
sumber
Saya melihat sekilas dan hasil Anda tampaknya menunjukkan SkipList lebih cepat daripada SplayTree.
Chinasaur
Adalah keliru untuk menganggap pengacakan sebagai bagian dari daftar-lompatan. Bagaimana elemen dilewati sangat penting. Pengacakan ditambahkan untuk struktur probabilistik.
user568109