1 poin oleh GN⁺ 2024-01-06 | 1 komentar | Bagikan ke WhatsApp

Meningkatkan kecepatan kode: jangan oper struktur yang lebih besar dari 16 byte di AMD64

  • Untuk meningkatkan performa bahasa Neat, cara pengoperan array diubah dari satu parameter struktur menjadi tiga parameter pointer.
  • Alasan array Neat lebih lambat daripada array di bahasa D adalah karena ukuran array 24 byte melebihi 16 byte sehingga parameternya diteruskan dengan cara yang berbeda.
  • Menurut spesifikasi SystemV AMD64 ABI, semua struktur yang melebihi 16 byte diteruskan melalui pointer.

Verifikasi masalah melalui benchmark

  • Melalui benchmark, dikonfirmasi adanya perbedaan performa antara cara mengoper struktur dan cara mengoper field secara terpisah.
  • Saat mengoper struktur, diperlukan proses alokasi di stack dan penyalinan, sedangkan saat mengoper field satu per satu, nilainya bisa langsung diteruskan melalui register SSE.
  • Cara mengoper field secara terpisah menunjukkan performa sekitar 2 kali lebih cepat dibandingkan cara mengoper struktur.

Pilihan perancang bahasa

  • Saat memanggil C API, C ABI harus diikuti, tetapi tipe tingkat tinggi yang digunakan secara internal tidak harus direpresentasikan sebagai struktur.
  • Perancang bahasa dapat menentukan bagaimana array, tuple, union type, dan lainnya akan diteruskan.
  • Meneruskan tipe yang melebihi 16 byte sebagai field-field terpisah dapat membantu meningkatkan performa.

Opini GN⁺

  • Artikel ini sangat bermanfaat bagi pengembang yang tertarik pada optimisasi perangkat lunak.
  • Secara khusus, artikel ini menunjukkan bahwa pada aplikasi yang sensitif terhadap performa, ukuran struktur dan cara pengoperannya dapat memberikan dampak yang penting.
  • Perancang bahasa atau pengembang API dapat memanfaatkan informasi ini untuk menemukan peluang peningkatan performa.

1 komentar

 
GN⁺ 2024-01-06
Komentar Hacker News
  • Terkait masalah ABI SysV amd64, ABI internal bahasa dapat diatur ke sesuatu selain SysV. Selama tidak diekspos ke pemanggil C SysV, Anda dapat memakai konvensi pemanggilan apa pun yang diinginkan. Perbedaan NeatLang tampak jauh lebih kompleks daripada sekadar mengubah konvensi pemanggilan LLVM, dan penulis mungkin juga ingin mengekspos tipe ke program C dengan konvensi pemanggilan yang konsisten.
  • Sering kali ada kurangnya pemahaman tentang biaya pengiriman argumen, dan tulisan tentang hal ini cukup informatif. Misalnya, di Google ada praktik meneruskan objek 24-byte berdasarkan nilai; ini tidak terlihat di profiler, tetapi menimbulkan biaya di setiap fungsi.
  • Saat beralih ke x64, dilakukan benchmark pada engine grafis karena khawatir objek vec3 (3xfloat) melebar dari 12 byte menjadi 16 byte. Ditemukan bahwa penggunaan 16 byte lebih cepat karena selaras dengan pembacaan 8-byte. Akibatnya, vec3 digunakan seperti vec4. Disarankan untuk selalu melakukan benchmark secara menyeluruh.
  • Argumen yang sudah dimuat sebelumnya di register berkinerja lebih baik daripada penulisan ke stack, dan manipulasi stack lebih cepat daripada yang dialokasikan di heap. Inilah sebabnya kode kompleks dengan banyak variabel global dapat berjalan cepat, sementara fungsi rekursif yang elegan atau argumen tuple/struct/list cenderung lambat. Yang pertama lebih mudah dioptimalkan menjadi loop assembly yang rapat.
  • Di MSVC, struct yang melebihi 8 byte dikirim melalui stack. Ini adalah detail ABI yang tidak boleh dijadikan ketergantungan dalam kode portabel. Namun, untuk fungsi yang jarang dipanggil, tidak perlu terlalu stres, dan untuk fungsi kecil yang sering dipanggil, biarkan kompiler meng-inline kode agar mengaktifkan optimasi berguna yang lebih dari sekadar mengirim argumen lewat register.
  • Di Windows, saat menggunakan konvensi pemanggilan cdecl bawaan, struct yang lebih besar dari 8 byte tidak dikirim melalui register.
  • Di amd64, meneruskan dan mengembalikan struct yang melebihi 16 byte berdasarkan nilai dengan ABI sysv amd64 memang lambat, tetapi sering kali tetap sepadan demi membuat kode lebih jelas. Tentu ini tidak berlaku dalam kasus ini, tetapi misalnya setiap kompiler C++, Golang, OCaml, dan SBCL dapat menggunakan ABI kustom di dalam bahasanya sendiri.
  • Di C++, ada aturan praktis bahwa tipe non-primitif sebaiknya diteruskan sebagai referensi (atau pointer jika memang diperlukan), kecuali ada alasan kuat untuk tidak melakukannya. Ini juga karena ABI dan untuk menghindari copy constructor atau move constructor. Jika ingin optimasi performa, ini adalah detail low-level membosankan yang memang perlu diperhatikan di C++.
  • Artikel tersebut memberi tautan ke benchmark yang sangat khusus, di mana Java (JIT) lebih cepat daripada C++ dan bahkan lebih cepat daripada Scala. Ini menimbulkan pertanyaan tentang apa itu Julia HO dan mengapa begitu cepat, besarnya perbedaan kecepatan antara Python dan Pypy, apakah ada alasan untuk tidak memakai Pypy, dan apakah itu seharusnya menjadi standar.
  • Dalam contoh yang diberikan, ini bisa diperbaiki tanpa memengaruhi pemanggil dengan mengubah tipe parameter “struct Vector” agar diteruskan sebagai referensi “const struct Vector &”. Banyak kode C++ yang memiliki bug pointer menggunakan pointer secara tidak perlu, padahal meneruskan sebagai referensi akan lebih mudah dan lebih aman digunakan.