Komputasi dengan performa tinggi (High Performance Computing-HPC) biasa dilakukan ketika permasalahan (model) yang dihadapi kompleks atau ketika data (domain/permasalahan) besar. Untuk menyelesaikan permasalahan besar/komplex tersebut, sebuah komputer dengan performa tinggi biasanya digunakan. Arsitektur komputer modern dengan performa tinggi memiliki prosesor CPU (& GPU) yang cukup banyak untuk membantu menyelesaikan masalah tersebut. Sebelum era Big Data, HPC hanya fokus pada bagaimana permasalahan dipecah menjadi sub masalah dan diselesaikan secara bersamaan (parallel/concurrent) se-efisien mungkin. Karena data yang relatif kecil, total komunikasi antara Prosesor dan memori tidak signifikan dibanding lamanya komputasi yang dilakukan. Namun hal ini tidak lagi tepat ketika datanya besar. Perubahan teknik pemrograman paralel apa saja yang dibutuhkan ketika datanya besar? Post ini adalah catatan kecil dari beberapa pengalaman saya ketika melakukan pemrograman paralel ketika datanya besar (Bagian 1 lebih ke Teori/Konsep, nanti di Bagian 2 merupakan teknis/penerapan dari apa yang dijelaskan di Bagian 1).
Konsep Dasar Pemrograman paralel
Teknik yang digunakan dalam artikel ini applicable di hampir semua bahasa pemrograman yang mendukung pemrograman paralel. Mari kita mulai dengan dasar konsep pemrograman parallel secara umum. Secara umum, pemrograman parallel dimulai dengan pemecahan masalah menjadi sub masalah (partition), kemudian mencari hubungan antara sub masalah (communicate). Jika tidak ada ketergantungan antar sub-masalah (sub-domain), maka permasalahannya disebut embarassingly parallel (EP). Contohnya beberapa proses di image processing. Pemrograman parallel pada permasalahan seperti ini sangat mudah untuk dilakukan. Namun jika tidak EP, maka sub-masalahnya dikelompokkan (Agglomerate) dan dipetakan (map) untuk kemudian dijalankan secara parallel (concurrent) (Lihat Gambar 1).
Tipe (Super) Komputer
Proses komunikasi dan sinkronisasi proses dapat dilakukan di library parallel MPI(Message Parsing Interface) maupun OpenMP. Saya akan tuliskan di artikel yang lain bagaimana melakukan instruksi-instruksi parallel di kedua library tersebut. Post ini hanya akan fokus keteori dan permasalahan unik pemrograman parallel di data yang besar. Sebelum saya membahas lebih lanjut, mari kita bahas beberapa macam super komputer (HPC). Ada yang memory-nya terpusat (shared): Bayangkan komputer yang memiliki beberapa core, namun mengakses sebuah memory RAM yang sama (seperti kebanyakan Laptop/PC saat ini). Tipe lain adalah ketika prosesor dan memorynya terdistribusi (bayangkan komputer Google, silahkan search kalau belum pernah lihat :) ). Hati-hati perbedaan tipe super komputer ini akan mengakibatkan perbedaan cara coding yang optimal pula.
Berdasarkan bagaimana data masuk dan diolah, HPC terbagi menjadi 4 macam (Gambar 2): sebuah proses dan sumber data (SISD), Sebuah proses dan beberapa data (SIMD), beberapa proses dan sebuah sumber data (MISD), dan beberapa proses komputasi dan data (MIMD).
Beberapa Catatan Kecil Pemrograman Parallel di Data yang Besar
1. Bawa Komputasi ke Data dan Bukan Sebaliknya.
Sebelum era Big Data, pemrograman parallel dilakukan dengan mengkomunikasikan data ke pusat komputasi (gambar 3 kiri). Namun ketika datanya besar, hal ini tidak feasible lagi untuk dilakukan. Instead, datanya sekarang dibuat terdistribusi dan komputasi dilakukan di pusat-pusat data tersebut (gambar 3). Arsitektur ini jauh lebih robust untuk Big Data processing, namun model Statistik yang digunakan BUKANmodel statistik konvensional.
2. Distributed Computing Statistic/Data Mining Model Bukan Statistik Konvensional
Memparalelkan komputasi model statistik/data mining di platform DDDC (Distributed Data-Distributed Computing) bukanlah sekedar memparallel-kan komputasi model statistik biasa (e.g. parallel KMean/parallel Hierarchical model/decision tree/ANN). Model Statistik dan data mining konvensional punya asumsi dasar yang sama, yaitu centralized data, artinya data diasumsikan berada di satu tempat. Itulah sebabnya hampir semua optimasi di model statistik ini hanyalah optimasi loss function (mencari nilai minimal) ketika mencari optimal parameter model. Di era big data dimana data tersebar (distributed), maka optimasinya menjadi optimasi dengan tujuan ganda (saya akan menulis tentang ini di artikel yang lain). Optimasi dilakukan tidak hanya untuk mendapatkan model optimal, tapi juga komunikasi yang minimal dan se-optimal mungkin mendekati optimal global.
Apakah maksud dari optimal global ? …. karena data terdistribusi (berada di beberapa tempat), berbagai model statistik/data mining pada data terdistribusi berusaha untuk mengoptimalkan model di masing-masing pusat data (data centre) dan meminimalkan komunikasi yang dibutuhkan. walau lebih cepat, namun pendekatan ini rentan untuk mendapatkan model yang optimal secara lokal saja. Bergantung permasalahannya, terkadang tidak menjadi masalah (Misal perusahaan besar yang memiliki anak-anak perusahaan yang variatif core bisnisnya). Namun ada juga saat dimana perusahaan besar memiliki cabang di berbagai tempat dengan core bisnis yang sama. Sehingga bussiness decision-nya haruslah optimal secara menyeluruh (global). Contoh model data mining, saat data tersebar (distributed) akan dijelaskan di artikel yang lain.
3. Global & Persistent Variable di Suatu Fungsi
Seorang programer handal biasanya selalu menyarankan menggunakan variabel lokal di suatu fungsi. Yups ini adalah saran/nasehat yang tepat. Mengapa? Karena alasan performa, management memory yang dinamis, robustness,dll. Namun sayangnya saat melakukan pemrograman parallel untuk data yang besar hal ini tidak lagi selalu benar.
Pemrosesan parallel biasanya atas suatu fungsi. Namun apabila kita melakukan apa yang biasanya kita lakukan pada pemrograman biasa (serial) maka biaya transfer (copy) data di memory (Gambar 4) akan terlalu besar dan biasanya malah membuat program berjalan lebih lambat. Contoh variabel “A” pada gambar 4 dapat di misalkan daftar “Stopwords” pada pemrosesan parallel pre-processing data teks.
Untuk mengatasi bottleneck copy variabel ini, minimal ada 2 cara yang dapat dilakukan:
[1]. Menggunakan variabel Global untuk variabel A.
[2]. Jika nodes/prosesor dedicated untuk suatu proses tertentu dan proses tersebut dilakukan berulang-ulang. Penggunaan variabel Persistent bisa juga dilakukan. Apa itu variabel persistent? Variabel lokal yang tidak hilang ketika fungsi selesai dijalankan. Variabel persistent tetap bisa dihilangkan di memory, namun hal ini harus dilakukan secara manual.
[1]. Menggunakan variabel Global untuk variabel A.
[2]. Jika nodes/prosesor dedicated untuk suatu proses tertentu dan proses tersebut dilakukan berulang-ulang. Penggunaan variabel Persistent bisa juga dilakukan. Apa itu variabel persistent? Variabel lokal yang tidak hilang ketika fungsi selesai dijalankan. Variabel persistent tetap bisa dihilangkan di memory, namun hal ini harus dilakukan secara manual.
4. Streaming Data for Data Science Models
Pada data yang besar, tidak mungkin seluruh data di load di memory seperti pada kasus pemrograman biasa. Kalaupun datanya bisa di load di memory karena komputer yang kita gunakan memiliki spesifikasi yang sangat baik, namun ketika melakukan pemrograman parallel, maka ukuran memory yang digunakan akan membengkak. Belum lagi saat pemodelan statistik-nya dilakukan biasanya memerlukan memory yang besar juga. Oleh karena itu model data science (mining) yang biasa digunakan di big data adalah model incremental (online/streaming).
Di aplikasinya ada dua macam cara big data dilakukan. Sebagian perusahaan ada yang masih mengolah datanya dalam sistem batch. Misal sebuah perusahaan telekomunikasi mengolah data SMS harian. Ada juga yang mengolah datanya secara real-time, streaming data dari data warehouse/NoSQL. Pada kasus kedua (data streaming) dan menggunakan model incremental, biasanya tidak ada masalah. Selama modelnya tepat, analisis yang baik dan bernilai tinggi bisa di dapatkan.
Akan tetapi pada kasus pertama, jadi sedikit tricky. Mengapa? Karena biasanya datanya berupa teks/Json/XML. Apa salahnya data besar dalam format tersebut? Concurrency…. Jika data di streaming langsung dari NoSQL/datawarehouse, data bisa di streaming secara bersamaan oleh beberapa nodes/prosesor bersamaan. Namun File teks hanya bisa di baca secara serial.
Solusi:
[1]. Pecah menjadi beberapa file. Seberapa banyak ? ada beberapa pilihan: sejumlah nodes/proses, atau buat sedemikian sehingga setiap file seukuran batch process/updates yang optimal (sesuaikan dengan memory/heap memory).
[2]. Compress file-file di tahap sebelumnya (optional, but most of the time it is wise to do this)
[3]. Streaming (baca) data dari file-file ini secara simultan (seperti Gambar 5).
[1]. Pecah menjadi beberapa file. Seberapa banyak ? ada beberapa pilihan: sejumlah nodes/proses, atau buat sedemikian sehingga setiap file seukuran batch process/updates yang optimal (sesuaikan dengan memory/heap memory).
[2]. Compress file-file di tahap sebelumnya (optional, but most of the time it is wise to do this)
[3]. Streaming (baca) data dari file-file ini secara simultan (seperti Gambar 5).
5. Multiple Port Connections
Bagi para programer profesional multiple ports mungkin sudah tidak asing. Namun mungkin ada baiknya saya share untuk para pemula. Ada kalanya ketika kita menjalankan suatu data service (database/search engine), kita bisa mengingkatkan concurency dengan menginstall beberapa service pada suatu nodes/server. Walau alamat IP-nya sama, asalkan portnya berbeda, peningkatan performa masih memungkinkan hingga suatu tahap/batasan tertentu. Tentu saja throughput jaringan dan prosesor nodes (server) harus baik performanya.
Bergantung pada spesifikasi machine (komputer) dan network, selain meningkatkan performa, aplikasi data science yang terhubung ke service multi-ports seperti ini juga biasanya akan meningkat performanya. Kelebihan lain, bisa menghemat biaya, ketimbang harus membeli nodes baru, padahal nodes yang ada belum tentu optimal utilisasinya.
6. Nitty-Gritty (detail) is the Devil
- Saat data di olah dari database relasional (data terstruktur), karena domain constraint database dan fixed schema database relasional, maka program pengolah data bisa dengan ‘nyaman’ memproses datanya tanpa hawatir suatu ketika datanya berubah struktur atau tipe-nya. Saat mengolah data dari NoSQL yang schemaless, kenyamanan ini hilang. Karena data ‘variety’ yang besar di big data, maka extra proses dibutuhkan untuk meyakinkan konsistensi data untuk di proses.
- High performance programming untuk data yang tidak terlalu besar masih cukup robust terhadap beberapa inefisiensi. Misal saja ada satu-dua baris formula atau proses yang tidak dinyatakan dengan cara yang paling efisien, mungkin tidak terlalu masalah. Namun pada saat datanya besar, inefisiensi kecil bisa mengakibatkan perubahan performa yang besar. Misal inefisiensinya hanya 0.001 detik per observasi, namun karena big data memiliki jutaan bahkan mungkin milyaran observasi, maka delay akibat sebuah inefisiensi tersebut bisa berakibat delay berhari-hari.
7. GPU Programming
Menggunakan processor Graphical Cards untuk keperluan pemrosesan data di data science (mining) beberapa tahun belakangan ini mulai menjadi trend. Alasannya? karena lebih murah dan cepat. Dua contoh library yang cukup terkenal yang memanfaatkan GPU adalah Theano dan Google Tensor Flow. Selain itu Matlab dan Numba (python) juga mendukung proses komputasi di GPU. Walau GPU programming nampak sangat menjanjikan, namun beberapa hal ini perlu di perhatikan:
- Transfer data dari memory ke GPU memory: Untuk dapat mengolah data di prosesor graphis, data terlebih dahulu di transfer dari main memory ke GPU memory (Gambar 8). Hati-hati biasanya memory GPU lebih lambat dan lebih kecil dibanding memory utama komputer. Proses transfer data ini seringnya jadi bottleneck GPU programming. Sehingga perlu diyakinkan bahwa yang di proses di GPU hanyalah sesuatu yang memiliki kompleksitas perhitungan (matematis) yang tinggi saja.
- Tidak semua operasi/komputasi bisa dilakukan di GPU. Jangan bayangkan prosesor di GPU sama dengan CPU. Prosesor di GPU biasanya hanya dapat melakukan operasi-operasi dan tipe-tipe data tertentu saja. Operasi di GPU biasanya terkait komputasi yang dibutuhkan saat pemrosesan gambar, misal rotasi matrix (berarti perkalian matrix), translasi (berarti penjumlahan matrix), dsb. GPU tidak didesain untuk menggantikan peran CPU.
- Gunakan GPU pada saat proses yang EP ingin diselesaikan secara massively parallel.
- Untuk praktisnya, gunakan library yang memanfaatkan GPU. Lakukan pure GPU programming seminimal mungkin, karena GPU programming lebih kompleks ketimbang pemrograman biasa.