Static Search Trees: 40x lebih cepat daripada pencarian biner
(curiouscoding.nl)- Mengimplementasikan pohon pencarian statis bernama "S+ tree" untuk melakukan pencarian berkecepatan tinggi pada data terurut
- Berangkat dari kode yang diusulkan dalam post Algorithmica, lalu mengoptimalkannya serta mengimplementasikan ide tambahan dan perbaikan yang diusulkan
- Setelah menganalisis kode assembly, semua instruksi yang memungkinkan dioptimalkan untuk memaksimalkan performa
- Memperkenalkan batching untuk meningkatkan throughput dengan memproses banyak kueri secara paralel
- Tujuannya adalah menjalankan kueri secara efisien pada data terurut sambil mempertahankan throughput tinggi melalui S+ tree
1. Pengenalan dan motivasi
-
Definisi masalah:
- Input: daftar bilangan bulat tak bertanda 32-bit terurut
vals: Vec<u32> - Output: struktur data dengan ukuran seminimal mungkin yang mengembalikan nilai yang lebih besar dari atau sama dengan kueri tertentu (q)
- Metrik: jumlah kueri independen yang dapat diproses per detik
- Input: daftar bilangan bulat tak bertanda 32-bit terurut
-
Tujuan:
- Membangun struktur data yang efisien dalam bioinformatika, khususnya meningkatkan kecepatan pencarian suffix array untuk pengindeksan sekuens DNA
- Menargetkan performa maksimum dengan memanfaatkan kinerja CPU dan instruksi SIMD
-
Bacaan yang direkomendasikan:
- Penelitian tentang pencarian biner dan layout array (“Array Layouts for Comparison-Based Searching”)
- Materi pengantar tentang S+ tree dan B-tree statis
2. S+ tree dan optimisasi
2.1 Pencarian biner dan layout Eytzinger
- Pencarian biner di pustaka standar Rust kurang efisien terhadap cache, dan performanya menurun saat ukuran data bertambah
- Layout Eytzinger:
- Menyimpan pohon pencarian biner dalam bentuk array untuk memaksimalkan pemanfaatan cache
- Performa: hingga 4x lebih cepat daripada pencarian biner untuk data yang melampaui cache L3
2.2 Konsep S+ tree
- S-tree:
- Bentuk pohon heksadesimal dengan 15 nilai terurut per node
- Lebih efisien daripada B-tree dan lebih mudah dipadatkan daripada layout Eytzinger
- S+ tree:
- Menyimpan semua data di node leaf, dengan salinan duplikat disimpan di node atas
- Menyediakan pencarian yang sederhana dan struktur yang efisien
2.3 Optimisasi fungsi find
- Pencarian linear dasar:
- Menelusuri data dan mengembalikan nilai yang memenuhi kondisi (tidak efisien)
- Vektorisasi otomatis:
- Diubah menjadi kode tanpa branch, meningkatkan performa 2x dengan memanfaatkan instruksi SIMD
- Implementasi SIMD manual:
- Dioptimalkan secara manual menggunakan instruksi SIMD, memaksimalkan performa hanya dengan 5 instruksi
3. Batching dan prefetching
3.1 Batching
- Memproses banyak kueri secara paralel untuk menutupi latensi memori
- Throughput meningkat seiring pembesaran ukuran batch, lalu jenuh pada ukuran batch maksimum 16
3.2 Prefetching
- Membawa node berikutnya ke memori lebih awal untuk meningkatkan performa pada data yang melampaui cache L3
- Dikombinasikan dengan batching, waktu pemrosesan dipangkas dari 45ns/query menjadi 30ns/query
4. Optimisasi layout dan struktur data
4.1 Mengubah ukuran node
- Mengurangi jumlah nilai per node menjadi 15 untuk menyederhanakan operasi perkalian dan meningkatkan efisiensi cache
- Memberikan sedikit peningkatan performa untuk data yang berada di dalam cache L3
4.2 Mengubah layout memori
- Mencoba layout yang disimpan dalam urutan terbalik atau konfigurasi dengan padding minimal
- Baik layout terbalik maupun pengurangan padding tidak banyak memengaruhi performa
5. Partisi data
5.1 Partisi berbasis prefiks
- Memisahkan part berdasarkan bit-bit tinggi data
- Meningkatkan performa untuk data kecil, tetapi menimbulkan overhead memori
5.2 Subtree terkompresi
- Mengemas setiap subtree untuk meningkatkan efisiensi ruang
- Perlu melacak ukuran part, dan kecepatan kueri sedikit menurun
6. Perbandingan multithread
- Dengan 6 thread, waktu kueri turun dari 27ns menjadi 7ns
- Batas bandwidth RAM menjadi bottleneck
7. Kesimpulan dan riset lanjutan
- Peningkatan performa lebih dari 40x dibanding pencarian biner (1150ns/query → 27ns/query)
- Pekerjaan selanjutnya:
- Optimisasi keseimbangan data dan pengurangan akses RAM
- Menangani kueri rentang dan kueri pengurutan
- Integrasi pencarian suffix array
2 komentar
Wah... jika ini diterapkan pada pustaka bawaan di berbagai bahasa, dampaknya tampaknya akan sangat besar.
Opini Hacker News
Mengamati bahwa Rust semakin sering digunakan dalam konten algoritma tingkat rendah. Dulu, konten semacam ini umumnya ditulis dalam C(++) atau pseudocode ilmiah. Melihat meningkatnya penggunaan Rust sebagai hal yang positif
Metode membagi kueri menjadi batch belum dieksplorasi. Biaya utama ada pada lookup di tabel yang berada di luar cache
Jumlah nilai
int32tidak terlalu banyak, dan seluruh bitset berukuran 512MB. Untuk struktur data umum, merekomendasikan Roaring BitmapsMerasa kagum dengan cara meningkatkan efisiensi tingkat rendah di Rust. Ingin membandingkannya dengan implementasi C++
Keunggulan pohon Eytzinger adalah adanya rumus untuk mengonversi indeks node ke posisi traversal inorder
Mengejutkan bahwa ada overhead sekitar ~27ns untuk mencari
u32dalam memori 4GBAda banyak diskusi tentang Rust dan C++, tetapi berpikir tentang cara mengimplementasikannya sambil mempertahankan SIMD di Common Lisp
Setiap kali membaca tulisan tentang optimisasi tingkat rendah, merasa berterima kasih karena penulis telah meluangkan waktu untuk menghemat nanodetik
Mengira ada kesalahan pada gambar 3 di 1.7. Menyarankan agar label sumbu y pada gambar 1 di 1.3 seharusnya "throughput terbalik"
Jika sesekali perlu mendukung penulisan, bisa menggunakan pohon pencarian statis besar dan pohon kecil yang dapat ditulisi