- Saat mengecilkan struct pencatatan ICMP Echo Request pada sistem pemantauan konektivitas, penggunaan memori ring buffer turun dari 12KiB menjadi 4KiB
- Dengan memakai union agar tidak menyimpan
sent_ns dan received_ns sekaligus, lalu hanya menyisakan latensi setelah paket diterima, ukuran array turun menjadi 8KiB
- Presisi diubah dari nanodetik menjadi satuan 100 mikrodetik dan
received dijadikan bitfield, tetapi padding struct membuat penghematan tambahan tidak terjadi
- Dengan mengganti alamat sumber menggunakan sebagian makna
identifier ICMP sebagai penghitung 4-bit, ukuran struct turun menjadi 8 byte dan array 512 elemen menjadi 4KiB
- Aplikasi sebenarnya tidak memiliki keterbatasan memori, jadi tidak ada kebutuhan praktis, tetapi ini menjadi eksperimen optimisasi yang sampai memperhitungkan penempatan field dan biaya akses bit
Menetapkan masalah: cara menyimpan catatan ping
- Sistem pemantauan konektivitas mengirim ICMP Echo Request ke beberapa server dan mengamati rata-rata latensi serta packet loss pada interval 1 menit, 5 menit, dan 15 menit
- Cara penyimpanan yang pertama terpikir adalah ring buffer berisi 512 entri, dan setiap entri menyimpan waktu kirim, waktu terima, alamat sumber, nomor urut, serta status diterima atau tidak
- Ukuran awal array struct
pings_rb[512] terukur sebesar 12KiB
struct ping_timestamp {
uint64_t sent_ns;
uint64_t received_ns;
in_addr_t source_addr;
uint16_t seq_no;
bool received;
};
Penghematan pertama: menggabungkan waktu kirim dan waktu berlalu dengan union
- Nilai yang sebenarnya ingin disimpan setelah paket diterima adalah latensi
received - sent, sehingga tidak perlu menyimpan waktu kirim dan waktu berlalu secara bersamaan
- Struct yang menggabungkan
sent_ts dan elapsed_ts dalam union memakai slot yang sama sebagai waktu kirim sebelum diterima, lalu sebagai waktu berlalu setelah diterima
- Setelah perubahan ini, ukuran array 512 elemen turun dari 12KiB menjadi 8KiB
struct ping_timestamp_2 {
union {
uint64_t sent_ts;
uint64_t elapsed_ts;
};
in_addr_t source_addr;
uint16_t seq_no;
bool received;
};
Percobaan kedua: mengurangi presisi dan memakai bitfield
- Waktu ping diukur dalam puluhan, ratusan, atau ribuan milidetik, jadi tidak perlu menyimpan seluruh presisi nanodetik
- Jika satuan waktu diubah menjadi 100 mikrodetik, yaitu 0,1ms, maka 43 bit cukup untuk melacak ping hingga 20 tahun
- Memakai 8 bit untuk nilai benar/salah pada
received terasa berlebihan, jadi bitfield diterapkan
- Namun ukuran array
ping_timestamp_3 tetap 8KiB, sehingga tidak ada penghematan tambahan
struct ping_timestamp_3 {
uint64_t sent_or_elapsed_ts: 43;
uint64_t received: 1;
uint64_t seq_no: 16;
in_addr_t source_addr;
};
Ukuran tidak mengecil karena padding struct
ping_timestamp_2 memiliki byte padding di bagian akhir untuk memenuhi kebutuhan alignment
ping_timestamp_3 menaruh waktu, status diterima, dan nomor urut pada 8 byte pertama, tetapi masih menyisakan alamat sumber dan padding di belakangnya
- Walau bitfield sudah diterapkan, masih ada 36 bit padding sehingga ukuran keseluruhan struct tidak berkurang
- Sekadar mengecilkan
bool menjadi satu bit tidak otomatis menyelesaikan masalah tata letak memori dan alignment
Menghapus alamat sumber dan memakai penghitung 4-bit
- Karena produk berjalan di jaringan data seluler dan alamat sumber sering berubah, struct lama menyimpan alamat sumber
- Saat alamat berubah, nomor urut juga di-reset, dan di masa lalu pernah ada paket dengan alamat sumber berbeda tetapi nomor urut sama yang diproses secara bersamaan
- ICMP Echo Request memiliki field
identifier 16-bit yang dapat dipakai aplikasi untuk mengenali paket yang dikirimnya sendiri
- Karena tidak perlu memakai seluruh 16 bit, 4 bit yang tersisa dipakai sebagai rolling counter yang bertambah saat alamat sumber berubah
- Penghitung ini dinaikkan mengikuti perubahan alamat sumber yang dipantau di bagian lain aplikasi
struct ping_timestamp {
uint64_t elapsed_or_sent_ts : 43;
uint64_t received : 1;
uint64_t counter: 4;
uint64_t seq_no: 16;
};
Hasil akhir dan tata letak field
- Struct final menghapus field alamat sumber dan memuat waktu, status diterima, penghitung, serta nomor urut di dalam 64 bit
- Ukuran array ring buffer 512 elemen menjadi 4KiB sehingga menyusut menjadi satu halaman data
- Total penghematan mencapai 8KiB dibanding ukuran awal 12KiB
- Urutan field disesuaikan agar
seq_no sejajar pada batas 16-bit, sehingga saat dimuat dapat dibaca dengan satu instruksi ldrh tanpa shift
- Saat membaca
elapsed_or_sent_ts, yang dibutuhkan hanya masking
Optimisasi tambahan: mengurangi biaya akses bit penerimaan
- Pembaruan 2025-06-21 menyebutkan bahwa menukar urutan
received dan counter membuat akses bit received hanya membutuhkan shift tanpa mask
- Perubahan ini membuat akses
received lebih murah, tetapi menambah biaya saat membaca counter karena bit received harus dihilangkan dengan mask
- Pembaruan 2025-06-22 memanfaatkan kondisi bahwa
counter hanya dibaca saat received bernilai true
- Dengan membalik makna
received menjadi not_received, di dalam kondisi yang memeriksa apakah not_received bernilai 0, mask pada counter sepenuhnya dihilangkan oleh compiler
struct ping_timestamp {
uint64_t elapsed_or_sent_ts : 43;
uint64_t counter: 4;
uint64_t not_received : 1;
uint64_t seq_no: 16;
};
Kesimpulan
- Hasil optimisasi memang menurunkan penggunaan memori dari 12KiB menjadi 4KiB, tetapi aplikasi itu sendiri tidak dibatasi oleh memori
- Terlepas dari kebutuhan praktisnya, ini menjadi eksperimen untuk menelaah layout struct, padding, bitfield, dan biaya akses pada level instruksi
- Pada komentar terakhir juga dijelaskan bahwa istilah “masalah” dipakai secara longgar, dan bahkan benchmark pun tidak dilakukan
1 komentar
Pendapat Lobste.rs
Kalau suatu hari memikirkan masalah seperti ini sudah tidak terasa menyenangkan lagi, menurut saya itulah hari untuk berhenti ngoding
Optimisasi prematur selalu menyenangkan
Yang biasanya tidak menyenangkan adalah menangani akibatnya setelah sadar kenapa optimisasi itu prematur
Bagian yang memakai 43 bit untuk timestamp agak membingungkan. Sepertinya 24 bit sudah cukup
Dari penyebutan ring buffer berukuran 512, kelihatannya ping baru dikirim setiap 2 detik dan yang dilacak adalah ping selama 17 menit 4 detik terakhir
Sebagai langkah pertama, bisa pakai delta encoding terhadap timer/urutan ideal. Waktu pengiriman terakhir tinggal dinaikkan 2 detik dan lihat indeks ring buffer, jadi mudah mengetahui kapan paket seharusnya dikirim; lalu cukup catat apakah paket terkirim tepat waktu, terlambat 0,1 ms, terlambat 2,3 ms, dan seterusnya
Waktu berlalu juga rasanya tidak perlu melebihi 17 menit 4 detik karena setelah itu ping akan kedaluwarsa. 512 × 2s = 10,240,000 × 100μs, jadi pada presisi itu sekitar 23,3 bit sudah cukup, dan kalau mau bisa dibulatkan ke 24 bit. Sisa sekitar 6.536.216 pola bit tidak valid mungkin juga bisa dipakai untuk hal lain
Bonusnya, dengan 24 bit kita bisa menaikkan presisi “terkirim” jauh lebih tinggi untuk mengurangi galat kuantisasi. Bahkan pada presisi mikrodetik pun ping masih bisa terlambat sampai 16 detik, jadi tampaknya masih sangat longgar
Saya tidak tahu apakah mengecilkan sampel dari 64 bit ke 48 bit akan membantu atau justru mengganggu performa. Saya juga tidak akan heran kalau hasilnya berbeda di lingkungan x86 dan ARM, baik 32 bit maupun 64 bit
Hanya saja, ukuran aslinya pun sudah cukup kecil untuk sangat mudah masuk ke cache data prosesor yang cukup tua, jadi penghematan memori tampaknya tidak akan banyak berpengaruh
Saya yakin itulah alasan kita melakukan optimisasi prematur. Ini olahraga untuk bersenang-senang
Saat merancang sistem atau bekerja dengan bahasa sistem tingkat rendah, optimisasi prematur sejujurnya adalah salah satu hal favorit saya
Setidaknya ada harapan bahwa nanti ini akan menghemat waktu dan memori. Hasil di level menengah biasanya cuma sedikit menambah pusing karena harus mencari tahu “kenapa ini dibuat begini?”. Kasus terburuk—dan kadang malah lebih baik—adalah ketika pekerjaan optimisasi saat perancangan jadi terlalu besar sampai proyeknya sendiri tidak jadi dikerjakan. Kita pun menutup program sambil berpikir, “Ah, ini terlalu ruwet, ngapain saya melakukan ini?”