HeadlinesBriefing favicon HeadlinesBriefing.com

Quicksort Tervektorisasi: 10x Lebih Cepat

Hacker News •
×

Hari ini kami membagikan kode open source yang dapat mengurutkan array angka sekitar sepuluh kali lebih cepat daripada C++ std::sort, dan mengungguli algoritma spesifik arsitektur tercanggih, sambil portabel di semua arsitektur CPU modern. Di bawah ini kami membahas bagaimana kami mencapai ini.

Pertama, sedikit latar belakang. Ada tren terkini menuju database kolumnar yang menyimpan semua nilai dari kolom tertentu secara berurutan, bukan menyimpan semua bidang dari sebuah record atau "baris" sebelum bidang record berikutnya. Ini bisa lebih cepat untuk memfilter atau mengurutkan, yang merupakan blok bangunan kunci untuk kueri SQL; oleh karena itu kami fokus pada tata letak data ini.

Mengingat pengurutan telah banyak dipelajari, bagaimana mungkin kita menemukan percepatan 10x? Jawabannya terletak pada instruksi SIMD/vektor. Ini melakukan operasi pada beberapa elemen independen dalam satu instruksi—misalnya, mengoperasikan 16 float32 sekaligus saat menggunakan set instruksi AVX-512, atau empat pada Arm NEON.

Jika Anda sudah familiar dengan SIMD, Anda mungkin pernah mendengar penggunaannya di superkomputer, aljabar linier untuk aplikasi pembelajaran mesin, pemrosesan video, atau codec gambar seperti JPEG XL. Tetapi jika operasi SIMD hanya melibatkan elemen independen, bagaimana kita bisa mengurutkannya, yang melibatkan penataan ulang elemen array yang berdekatan? Bayangkan kita memiliki cara khusus untuk mengurutkan, misalnya array 256 elemen. Kemudian, algoritma Quicksort untuk mengurutkan array yang lebih besar terdiri dari mempartisinya menjadi dua sub-array: yang kurang dari nilai "pivot" (idealnya median), dan semua lainnya; lalu rekursi hingga sub-array paling banyak 256 elemen, dan menggunakan metode khusus kami untuk mengurutkannya. Partisi menyumbang sebagian besar waktu CPU, jadi jika kita dapat mempercepatnya menggunakan SIMD, kita memiliki pengurutan yang cepat.

Untungnya, set instruksi modern (Arm SVE, RISC-V V, x86 AVX-512) menyertakan instruksi khusus yang cocok untuk partisi. Diberikan input terpisah nilai ya/tidak (apakah suatu elemen kurang dari pivot), instruksi "compress-store" ini menyimpan ke memori berurutan hanya elemen yang inputnya sesuai adalah "ya". Kita kemudian dapat menegasikan nilai ya/tidak secara logis dan menerapkan instruksi lagi untuk menulis elemen ke partisi lainnya. Strategi ini telah digunakan dalam Quicksort khusus AVX-512. Tetapi bagaimana dengan set instruksi lain seperti AVX2 yang tidak memiliki compress-store? Pekerjaan sebelumnya telah menunjukkan cara meniru instruksi ini menggunakan instruksi permute. Kami membangun teknik ini untuk mencapai Quicksort tervektorisasi pertama yang portabel ke enam set instruksi di tiga arsitektur, dan faktanya mengungguli pengurutan spesifik arsitektur sebelumnya.

Implementasi kami menggunakan fungsi SIMD portabel Highway, jadi kami tidak perlu mengimplementasikan ulang sekitar 3.000 baris C++ untuk setiap platform. Highway menggunakan compress-store bila tersedia dan jika tidak, instruksi permute yang setara. Berbeda dengan state of the art sebelumnya—yang juga spesifik untuk integer 32-bit—kami mendukung rentang penuh input 16-128 bit. Meskipun implementasi portabel tunggal kami, kami mencapai kecepatan rekor pada AVX2, AVX-512 (Intel Skylake) dan Arm NEON (Apple M1). Untuk satu juta angka 32/64/128-bit, kode kami yang berjalan di Apple M1 dapat menghasilkan output terurut pada laju 499/471/466 MB/s. Pada Skylake 3 GHz dengan AVX-512, kecepatannya adalah 1123/1119/1120 MB/s. Menariknya, AVX-512 1,4-1,6 kali lebih cepat dari AVX2—percepatan yang layak untuk usaha tambahan nol (Highway memeriksa instruksi apa yang tersedia pada CPU dan menggunakan yang terbaik yang tersedia). Saat berjalan pada AVX2, kami mengukur 798 MB/s, sedangkan state of the art sebelumnya yang dioptimalkan untuk AVX2 hanya mencapai 699 MB/s. Sebagai perbanding