Pemilihan lebar

Memilih lebar vektor yang portabel dan menangani sisa perulangan dengan benar.

Daftar isi

Lebar vektor menentukan berapa banyak lane yang dihitung bersamaan, dan keputusan ini menyertai setiap penulisan tipe SIMD. Topik ini membahas pertimbangan memilih lebar, cara menetapkan lebar tetap yang portabel antar CPU, dan penanganan ekor perulangan ketika panjang data bukan kelipatan lebar. Setelah topik ini dibaca, perulangan SIMD dapat ditulis dengan benar untuk data dengan panjang apa pun tanpa membaca di luar batas.

Pertimbangan memilih lebar#

Lebar vektor memengaruhi dua hal sekaligus, yaitu jumlah instruksi yang dibutuhkan dan tekanan pada register prosesor. Vektor berlebar 8 memproses delapan elemen per instruksi, sehingga jumlah perulangan yang diperlukan lebih sedikit dibandingkan vektor berlebar 4 untuk data yang sama. Di sisi lain, register vektor CPU berbeda-beda ukurannya, umumnya 128, 256, atau 512 bit, sehingga lebar yang cocok untuk satu CPU belum tentu cocok untuk CPU lain. Karena itu, lebar terbesar bukan pilihan otomatis yang selalu unggul, dan penilaian akhir membutuhkan pengukuran pada CPU sasaran.

Lebar yang sah wajib pangkat dua antara 1 dan 2^15, sehingga pilihan praktisnya adalah 1, 2, 4, 8, dan 16. Untuk elemen float64 berukuran 64 bit, lebar 4 menuntut register 256 bit, sedangkan lebar 8 menuntut register 512 bit. Kedua ukuran register ini menjadi batas alami ketika lebar dipilih menurut perangkat keras sasaran, karena CPU tanpa register 512 bit tidak dapat menampung vektor berlebar 8 secara utuh dalam satu register.

Lebar tetap yang portabel#

Lebar tetap yang sederhana adalah pilihan awal yang masuk akal untuk kode yang berjalan di banyak CPU. Lebar 4 untuk float64 setara dengan register vektor 256 bit yang tersedia luas pada CPU modern, sehingga kode dengan lebar ini berjalan wajar tanpa menuntut perangkat keras tertentu. Lebar yang menuntut register terbesar pada satu CPU justru membuat kode bergantung pada CPU itu, dan penyesuaiannya menjadi pekerjaan tambahan bila perangkat kerasnya berbeda. Perbandingan kecepatan antar lebar tidak disebutkan di sini karena kebenarannya hanya dapat dipastikan lewat pengukuran pada CPU yang bersangkutan.

Lebar yang dipilih dapat diberi nama dengan konstanta compile-time, yaitu konstanta yang nilainya ditetapkan saat proses kompilasi, agar penulisannya konsisten di seluruh berkas. Deklarasi comptime LEBAR = 4 pada tingkat modul membuat nama LEBAR dapat dipakai sebagai parameter lebar, misalnya SIMD[DType.float64, LEBAR], sekaligus menjadi satu tempat perubahan bila lebar perlu disesuaikan. Karena nilainya diketahui pada compile time, compiler memperlakukan LEBAR sama seperti lebar yang ditulis langsung.

Menangani ekor perulangan#

Panjang data pada pekerjaan nyata jarang menjadi kelipatan lebar vektor. Sepuluh elemen dengan lebar 4 terbagi menjadi dua potongan penuh dan satu ekor berisi dua elemen. Pola penanganannya terdiri atas dua fase, yaitu perulangan potongan yang memproses empat elemen sekaligus dengan SIMD dan perulangan ekor yang memproses sisa elemen satu per satu secara skalar. Kondisi perulangan potongan ditulis i + LEBAR <= len(data) agar potongan terakhir tidak melewati ujung data.

Jalankan program berikut yang menjumlahkan sepuluh nilai dengan lebar 4 dan ekor skalar.

mojo jumlahkan.mojo
comptime LEBAR = 4

def main():
    var data: List[Float64] = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
    var total: Float64 = 0.0
    var i = 0
    while i + LEBAR <= len(data):
        var potongan = SIMD[DType.float64, LEBAR](data[i], data[i + 1], data[i + 2], data[i + 3])
        total += potongan.reduce_add()
        i += LEBAR
    while i < len(data):
        total += data[i]
        i += 1
    print(total)
bash
# jalankan program penjumlahan dengan potongan SIMD dan ekor skalar
mojo run jumlahkan.mojo
text Keluaran
55.0

Hasil 55.0 berasal dari dua potongan penuh dan satu ekor. Potongan pertama menampung 1.0 sampai 4.0 dan menyumbang 10.0, potongan kedua menampung 5.0 sampai 8.0 dan menyumbang 26.0, sedangkan ekor menambahkan 9.0 dan 10.0 secara skalar. Konstanta LEBAR dipakai pada kondisi perulangan, pada tipe vektor, dan pada langkah penambahan indeks, sehingga mengganti lebar cukup dilakukan di satu tempat. Bila panjang data lebih kecil dari lebar, perulangan potongan tidak berjalan sama sekali dan seluruh data diproses oleh ekor, sehingga pola ini aman untuk data pendek. Pola dua fase ini adalah bentuk baku perulangan SIMD untuk data dengan panjang sembarang, dan sisa elemen memang diserahkan pada perulangan skalar alih-alih dipaksakan menjadi vektor.

Kesalahan umum#

  • Menulis kondisi perulangan potongan sebagai i <= len(data). Potongan terakhir kemudian mencoba membaca elemen di luar ujung data, dan program berhenti dengan pesan execution crashed tanpa penjelasan penyebabnya. Perbaikannya adalah kondisi i + LEBAR <= len(data) yang menjamin seluruh lane potongan berada di dalam data.
  • Memilih lebar yang bukan pangkat dua. Penulisan SIMD[DType.float64, 3] ditolak compiler dengan pesan SIMD vector length must be a power of two between 1 and 2^15. Larangan ini berlaku bersama kewajiban lebar sebagai konstanta compile-time, sehingga vektor selebar sisa data tidak dapat dibangun sama sekali dan sisa data diserahkan pada ekor skalar.
  • Menyimpulkan lebar terbaik tanpa pengukuran. Lebar yang lebih besar menekan jumlah instruksi, tetapi kecepatan sesungguhnya bergantung pada ukuran register vektor CPU dan bentuk program, sehingga pernyataan bahwa satu lebar lebih cepat hanya sah setelah diukur pada CPU sasaran.

Ringkasan#

  • Lebar vektor memengaruhi jumlah instruksi dan tekanan register, dan pilihannya wajib pangkat dua antara 1 dan 2^15.
  • Lebar 4 untuk float64 setara register 256 bit yang tersedia luas, sehingga menjadi lebar tetap yang portabel sebagai pilihan awal.
  • Konstanta comptime LEBAR = 4 memberi nama lebar pada tingkat modul dan dipakai pada kondisi perulangan, tipe vektor, serta langkah indeks.
  • Perulangan SIMD berjalan dua fase, yaitu potongan penuh dengan kondisi i + LEBAR <= len(data) dan ekor skalar untuk sisa elemen.

Referensi#