Program Anda Lambat? Metaprogramming lah Solusinya

Lintang Erlangga - April 12th, 2021

Eksekusi program saat kompilasi
Eksekusi program saat kompilasi.

Teruntuk kalian yang membutuhkan kecepatan pada eksekusi program kalian, sangat tepat sekali kalian kesini, di sini saya akan sedikit berbincang mengenai metaprogramming.

Nah tapi sebelumnya mungkin di antara kalian ada yang bertanya, memangnya penting bikin program yang waktu eksekusinya kenceng? Untuk menjawab pertanyaan tersebut, coba kita pikirkan bersama-sama, apa yang menyebabkan program menjadi lambat? Tentunya beratnya komputasi pada program kita, lalu apa yang menyebabkan beratnya komputasi tersebut? Selain dari kompleksitas kodenya, seperti banyak kondisi if-else, ataupun perhitungan-perhitungan aritmetika, pada matriks.

Sekedar informasi aja, sebelumnya juga saya pernah membuat artikel mengenai pentingnya bikin program yang optimal, yang poinnya buatlah program seefektif mungkin. Silahkan kalau mau baca-baca

Selain itu ada juga yang lain, yaitu seperti beberapa perhitungan yang diulang terus-menerus dan kita tahu batasannya, sebagai contoh kita mau menghitung magnitude gradien pada satu piksel pada sebuah citra menggunakan filter Sobel, di mana rumus magnitude-nya sendiri yaitu.

\sqrt{g_x^2+g_y^2}

, dengan g_x dan g_y merupakan hasil konvolusi dari filter Sobel.

Pada suatu citra yang berukuran 8-bit (anggap aja 1 channel), maksimal warna yang dapat dimiliki suatu piksel yaitu sebesar 255. Dengan adanya batasan ini kita bisa mencari informasi, berapa besar maksimal gradien tersebut, dan dalam hal ini yaitu sebesar 1020 (ini contoh aja ya).

Memang perhitungannya sederhana, tapi perhitungan tersebut dilakukan pada tiap piksel, untuk citra berukuran 640x480 saja sudah terdapat 307.200. Untuk itu kita bisa membuat tabel hasil perhitungan di awal atau yang disebut sebagai lookup table, dengan mengisi tabel tersebut untuk setiap kemungkinan gradien yang muncul (tidak disarankan jika RAM nya kecil).

Nah, sekarang bagaimana jika batasannya tidak ditentukan alias bisa berapa aja? Tenang..., metaprogramming akan membantu kalian semua.

Daftar Isi

Generic programming

Sejauh ini yang sama pahami, motivasi dari teknik ini yaitu dapat membuat suatu program yang sifatnya general purpose, maksudnya program kita dapat menghandle berbagai macam tipe data tanpa perlu melakukan kompilasi ulang. Seperti misal, suatu fungsi yang mempunyai argumen berupa class A

int contoh(A a){
  ...
}

Apabila kita ingin mengeksekusi fungsi “contoh” untuk tipe data yang berbeda, kita dapat membuat fungsi lainnya dengan nama yang sama, namun argumen yang berbeda, atau yang disebut sebagai overload. Tapi apakah mungkin kita menuliskan fungsi overload tersebut satu-satu?

Pada C++, masalah ini dapat diselesaikan dengan menggunakan fitur template. Dan fungsi sebelumnya apabila dituliskan pada fitur ini menjadi seperti berikut

template <typename T>
int contoh(T t){
  ...
}

Jadi, berbagai macam tipe data yang menjadi argumen fungsi tersebut diwakili oleh T yaitu argumen dari template.

Dan proses tersebut dievaluasi sebelum kompiler melakukan penerjemahan ke dalam bahasa mesin. Proses inilah yang akan dimanfaatkan untuk melakukan komputasi sebelum runtime atau pada compile-time.

Memang komputasi apa saja yang bisa dilakukan dengan fitur template ini? Banyak sekali, mulai dari pengkondisian if-else, perulangan, hingga perhitungan matematika pun bisa!

Kondisional if-else

Sebelum C++17, untuk melakukan pengkondisian cukup merepotkan karena kita melakukannya tidak seperti halnya menggunakan if pada biasanya seperti

if (.......){
  ...
}

Kita perlu menaruhnya di level argumen template, atau bisa juga pada trailing return type, seperti berikut

template <typename T, std::enable_if_t<...kondisinya disini...>>
auto contoh_lagi(T t){
  ...
}

Ketika kondisi yang telah dibuat tidak terpenuhi, kompiler akan memberi tahu bahwa fungsi tersebut tidak siap untuk dipakai, yang biasanya muncul berupa error. Nah kalau kalian mau ngehandle juga ketika nilainya bernilai salah, bisa kita buat overloadnya

template <typename T, std::enable_if_t<!(...kondisinya disini...)>>
auto contoh_lagi(T t){
  ...
}

Dengan adanya fitur if constexpr, tugas tersebut dapat dengan mudah dilakukan hanya dengan menambahkan kode constexpr setelah if yang ingin kita eksekusi saat kompilasi, sebagai contoh

if constexpr(...kondisi...){
  ...
}

Tentunya variabel yang digunakan harus bersifat constexpr juga.

Perulangan

Untuk perulangan, sejauh ini yang saya tahu belum ada bentuk seperti if constexpr. Oleh karena itu kita akan gunakan fitur lainnya, yaitu index_sequence.

Sebagai contoh, misal kita ingin menghitung jumlah 10 bilangan bulat pertama, dengan fitur tersebut bisa kita modelkan menjadi seperti

#include <iostream>
#include <utility>

template <size_t... Is>
constexpr auto sum(std::index_sequence<Is...> seq){
  auto res(0);
  ((res += Is), ... );
  return res;
}

int main(){
  constexpr size_t N = 10;
  constexpr size_t total = sum(std::make_index_sequence<N+1>{});
  static_assert(total == 55);
  std::cout << "Jumlah " << N << " bilangan bulat pertama yaitu: " << total << std::endl;

  return 0;
}

Fungsi static_assert sendiri sengaja saya gunakan, selain untuk memastikan nilainya benar juga untuk memastikkan bahwa semuanyan berjalan saat compile-time. Kode tersebut sudah saya tes melalui compiler g++, kalau kalian malas buka editor sama kompilasinya, bisa juga dicoba pada compiler-compiler yang tersedia di internet.

Sedangkan untuk perhitungan matematika, nampaknya pada contoh perulangan ini kita secara gak langsung melihat bagaimana operasi matematika dapat dilakukan juga saat kompilasi. Dan ini tidak hanya terbatas pada operasi pertambahan saja, perhitungan-perhitungan lainnya seperti transpos matriks pun bisa dilakukan.

--

Ada satu hal yang perlu diketahui, semua pasti ada imbas baliknya (trade-off), dan ini kita bayar saat kompilasi. Waktu serta resource yang diperlukan tentunya bertambah saat compile-time.

Jadi saya ingin memberikan sedikit saran terkait ini, terkadang kalau fitur template yang kita gunakan sudah kompleks banget, biasanya akan menyebabkan hang pada OS kita, hal ini terjadi karena proses kompilasinya bener-bener memakan RAM.

Untuk membatasi penggunaan RAM berlebih, kalian bisa membatasi jumlah core CPU yang digunakan supaya tidak tiba-tiba gak respon komputernya. Kalau misal komputer kalian punya 4 core coba gunakan 2 core saja (ini rule of thumb aja bukan aturan pastinya).

Oke cukup sekian tulisan singkat tentang pemrograman (C++) ini, semoga bermanfaat buat teman-teman sekalian, terima kasih.

Label
Lintang Erlangga
Lintang Erlangga

Lintang Erlangga merupakan alumni Teknik Elektro UGM 2016, dan juga mantan anggota Gadjah Mada Robotics Team sebagai perwakilan kampus pada kompetisi robot tingkat regional hingga nasional.

Komentar