Memindahkan If ke Atas, For ke Bawah
(matklad.github.io)- Memindahkan pernyataan if di dalam fungsi ke sisi pemanggil membantu mengurangi kompleksitas kode
- Dengan memusatkan pemeriksaan kondisi dan penanganan percabangan di satu tempat, duplikasi dan pemeriksaan cabang yang tidak perlu jadi lebih mudah ditemukan
- Menggunakan refaktorisasi pembubaran enum dapat mencegah kondisi yang sama tersebar di berbagai bagian kode
- Perulangan
forberbasis operasi batch efektif untuk meningkatkan performa dan mengoptimalkan pekerjaan berulang - Dengan menggabungkan pola if ke atas, for ke bawah, keterbacaan dan efisiensi kode bisa ditingkatkan sekaligus
Catatan singkat tentang dua aturan yang saling berkaitan
- Jika ada kondisi if di dalam fungsi, pendekatan yang disarankan adalah mempertimbangkan apakah itu bisa dipindahkan ke sisi pemanggil fungsi
- Seperti pada contoh, alih-alih memeriksa precondition di dalam fungsi, lebih baik menyerahkan pemeriksaan kondisi tersebut ke sisi pemanggil, atau menjamin precondition melalui tipe (atau
assert) - Pendekatan menaikkan pemeriksaan precondition (push up) memberi dampak ke seluruh kode, sehingga secara keseluruhan mengurangi jumlah pemeriksaan kondisi yang tidak perlu
Memusatkan alur kontrol dan kondisi
- Alur kontrol dan pernyataan if adalah penyebab utama kompleksitas kode dan bug
- Memusatkan kondisi di level atas seperti sisi pemanggil, lalu mengumpulkan penanganan percabangan hanya di satu fungsi, dan menyerahkan pekerjaan nyata ke subrutin yang lurus (straight-line), adalah pola yang bermanfaat
- Jika percabangan dan alur kontrol terkumpul di satu tempat, percabangan yang duplikat dan kondisi yang tidak perlu jadi lebih mudah dikenali
Contoh:
- Saat ada
ifbertingkat di dalam fungsif, cabang mati (Dead Branch) lebih mudah dikenali - Jika percabangan tersebar melalui beberapa fungsi (
g,h), hal seperti itu menjadi lebih sulit dipahami
Refaktorisasi pembubaran enum (Dissolving enum Refactor)
- Jika kode memuat percabangan kondisi yang sama lewat
enumatau bentuk lain, kondisi itu bisa ditarik ke level atas agar percabangan dan pekerjaan lebih jelas terpisah - Dengan menerapkan cara ini, masalah kondisi yang sama muncul berulang kali di dalam kode dapat dicegah
Contoh:
- Situasi ketika kondisi percabangan yang sama dinyatakan masing-masing di fungsi
f,g, danenum E - Dapat disederhanakan menjadi satu percabangan kondisi di level atas untuk seluruh kode
Pemikiran berorientasi data (Data Oriented Thinking) dan operasi batch
- Sebagian besar program berjalan dengan banyak objek (entitas). Performa pada jalur kritis (Hot Path) ditentukan oleh pemrosesan banyak objek
- Dengan memperkenalkan konsep batch dan menjadikan operasi atas kumpulan objek sebagai dasar, sementara operasi objek tunggal ditangani sebagai kasus khusus, hasilnya lebih baik
Contoh:
-
Menjadikan fungsi pemrosesan batch seperti
frobnicate_batch(walruses)sebagai dasar -
Lalu objek individual bisa diubah menjadi kasus khusus yang diproses lewat loop
for -
Pendekatan ini berperan penting dalam optimasi performa; pada pekerjaan berskala besar, biaya awal dapat dikurangi dan fleksibilitas urutan dapat ditingkatkan
-
Pemanfaatan operasi SIMD (
struct-of-arraydan sejenisnya) juga dimungkinkan, sehingga hanya field tertentu yang diproses sekaligus sebelum pekerjaan keseluruhan dilanjutkan
Contoh praktis dan pola yang direkomendasikan
- Seperti pada perkalian polinomial berbasis FFT, performa dapat dimaksimalkan dengan memungkinkan perhitungan serentak di banyak titik
- Aturan menaikkan kondisi dan menurunkan perulangan dapat diterapkan bersamaan
Contoh:
- Daripada terus memeriksa ekspresi kondisi yang sama di dalam loop, memindahkan kondisi ke luar loop mengurangi percabangan di dalam iterasi dan mempermudah optimasi serta vektorisasi
- Pendekatan ini menjamin efisiensi tinggi pada data plane sistem berskala besar, seperti dalam desain TigerBeetle
Kesimpulan
- Dengan menggabungkan pola memindahkan
if(kondisi) ke level atas (pemanggil, pengendali) danfor(perulangan) ke level bawah (operasi, pemrosesan data), keterbacaan, efisiensi, dan performa kode semuanya dapat ditingkatkan - Berpikir dari sudut pandang ruang vektor abstrak, yaitu operasi pada tingkat himpunan, adalah alat pemecahan masalah yang lebih baik daripada pemrosesan percabangan berulang
- Singkatnya: if ke atas, for ke bawah!
1 komentar
Komentar Hacker News
getaddrinfoatauEnterCriticalSectionmemindahkan if seperti itu? Menurut saya, transformasi seperti ini hanya layak dipertimbangkan bila fungsi itu dipanggil dari sekitar dua tempat saja, dan keputusan tersebut berada di luar concern fungsi itu. Salah satu caranya adalah menulis fungsi yang hanya menjalankan kondisional, lalu mendelegasikan ke helper function. Dan ketika perlu memindahkan kondisi ke luar loop, pemanggil bisa langsung memakai helper kondisi tingkat lebih rendah. Namun inti dari pertimbangan ini adalah "optimisasi". Optimisasi sering kali berbenturan dengan desain program yang lebih baik. Bisa jadi desain yang lebih baik justru tidak mengharuskan pemanggil mengetahui kondisi itu. Dilema seperti ini juga sering muncul dalam OOP. Keputusan yang direpresentasikan dengan "if" pada praktiknya kadang diwujudkan sebagai method dispatch. Memindahkan dispatch seperti ini ke luar loop juga bisa berbenturan dengan prinsip desain. Contohnya, saat menggambar gambar pada canvas, lebih baik memanfaatkan method sepertiblitdaripada berulang kali memanggilputpixelEnterCriticalSectionmemang seharusnya melakukan validasi kuat di titik masuknya, termasuk kondisional. Tetapi di kode aplikasi, memindahkan if ke sisi pemanggil bisa saja wajar. Di library atau kode inti, memindahkan alur kontrol ke tepi itu tepat. Di dalam domain yang kita kuasai sendiri, menaruh alur kontrol di tepi memang bagus. Tapi seperti biasa aturan seperti ini hanya idiomatis, jadi harus diterapkan sesuai konteks oleh orang yang bisa menilai situasinya secara masuk akalmatchbisa diganti dengan pemanggilan method polimorfik. Tujuan pendekatan ini adalah memisahkan saat keputusan percabangan awal ditentukan dari saat perilaku nyata dijalankan. Pembedaan kasus dibawa oleh objek (di sini nilai enum) atau closure, jadi tidak perlu diulang setiap kali dipanggil. Jika pembedaan kasus berubah, cukup ubah titik percabangannya, dan tempat terjadinya perilaku nyata tidak perlu diubah. Kekurangannya adalah trade-off antara kemudahan melihat langsung cabang perilaku untuk tiap kasus, dan munculnya dependensi pada daftar kasus di level kodeif (weShouldDoThis()) { doThis(); }seperti itu, dan jika tiap pengecekan dipisah menjadi fungsi tersendiri, pengujian dan pengelolaan kompleksitas jadi lebih mudahsonarqubedan sejenisnya sering melaporkan "code smell" secara membabi buta, bukan bug nyata. Kalau kita sampai memperbaiki "kode yang sebenarnya tidak bermasalah" dengan gaya seperti ini, risikonya justru menimbulkan bug baru, dan hanya membuang waktu yang seharusnya dipakai untuk menangani masalah yang benar-benar pentingforlebih mudah dikelola dan lebih efisien untuk akses memori daripada if di luarfor. Contoh konkretnya, jika ada operasi +1 untuk bilangan ganjil dan -2 untuk bilangan genap, biasanya setiap iterasi loop harus bercabang, tetapi dengan SIMD kita bisa memproses 16intsekaligus secara vektor tanpa branch. Jika compiler melakukan vectorization dengan benar, kode asli bisa diubah menjadi versi optimal tanpa branchforadalah percabangan yang bergantung pada data sehingga tidak mudah diangkat ke atas. Kalau algoritmenya berbentukif (length % 2 == 1) { ... } else { ... }dan hanya memakai kondisi di luar loop, maka kondisi seperti itu memang jelas harus dipindahkan ke atasfor. Pada versi SIMD, if bahkan hilang sama sekali, dan pola seperti ini adalah bentuk kode ideal yang kemungkinan juga akan disukai penulis artikel itufor. Adakah yang tahu seberapa sulit compiler melakukan auto-vectorization untuk kode seperti ini? Saya penasaran di mana batasnyaformembentuk abstraksi bahwa "for adalah aturan, percabangan adalah perilaku". Tetapi ketika requirement baru muncul, abstraksi seperti ini runtuh, lalu orang mulai memaksa menambah parameter atau memperbanyak penanganan kasus khusus sehingga kode makin sulit dipahami. Seandainya sejak awal kode ditulis tanpa abstraksi itu, hasilnya mungkin akan lebih jelas dan lebih mudah dirawat. https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction