Mengenal dan Membandingkan Berbagai Pemrograman Paralel pada CPU dan GPU [GGPU – Bag. 2]

Di artikel sebelumnya Mengenal General Purpose Programming pada GPU (GPGPU) – [Bag. 1] kita sudah membahas kemampuan, manfaat, dan sedikit gambaran tentang cara melakukan pemrograman umum pada GPU. Di artikel ini kita akan melihat secara lebih detil bermacam cara pemrograman umum pada GPU dan juga pemrograman paralel pada CPU sekaligus kita bandingkan performansinya masing-masing.

Tujuan saya melakukan ini adalah mencari pertimbangan untuk memutuskan platform, bahasa pemrograman, dan API/librari apa yang nanti saya pakai untuk proyek pembelajaran mesin saya.

Tujuan

Dari segi platform, tentu saya ingin mengetahui seberapa besar manfaat yang kita dapat dengan menggunakan GPU. Di artikel sebelumnya sudah dipaparkan spesifikasi performansi di atas kertas, dan kali ini kita akan ukur perbedaannya secara riil di program kita.

Selain itu ada pilihan sistem operasi, yaitu Linux dan Windows. Kalau MacOS sepertinya kurang sesuai, paling tidak buat saya, karena GPU secara fisik bentuknya adalah kartu PCI-X sehingga harus punya komputer Apple yang berupa desktop atau server dan agak susah menjustifikasi investasinya. Kecuali kalau hanya untuk menjalankan dengan CPU, untuk pengembangan kode atau sekedar coba-coba, maka bisa pakai MacBook.

Berikut adalah tabel dukungan sistem operasi dari librari-librari pembelajaran mesin terkenal:

Linux Windows MacOS Lainnya
TensorFlow Android, iOS
PyTorch
MXNet Raspberry Pi
CNTK
Theano
Caffe
scikit-learn NetBSD

Menurut pengamatan saya, walaupun Windows didukung, tapi dukungannya tidak sebagus Linux. Misalnya saat ini, TensorFlow dan MXNet terbaru yang mendukung CUDA  9 hanya baru dirilis di Linux.

Dari segi bahasa pemrograman, karena saya fasih memakai Python dan C++, maka dua bahasa inilah yang menjadi perhatian utama saya. Python jelas adalah bahasa utama untuk pembelajaran mesin. Kalau Anda memilih Python maka rasanya Anda berada di jalan yang benar. Tapi C++ dengan standard barunya C++-11 sepertinya sangat mengasyikkan dan menarik untuk dipakai, jadi sebenarnya saya agak-agak mencari justifikasi agar bisa memakai C++.

Berikut adalah tabel dukungan bahasa dari berbagai librari pembelajaran mesin.  Sepertinya dukungan paling luas memang untuk Python dan C++.

Python C++ Java Lainnya
TensorFlow ~ ~ Go
PyTorch ~
MXNet R, Julia, Scala, Perl
CNTK ~ C#, BrainScript
Theano
Caffe
scikit-learn

(catatan: tanda ~ artinya hanya mendukung sebagian)

Dari segi librari, saya relatif belum familiar dengan librari apapun, jadi tidak ada preferensi untuk memilih salah satu karena semua harus belajar dari awal. Tapi perhatian utama saya adalah TensorFlow dan PyTorch, karena dua inilah yang mempunyai dukungan terbesar baik dari industri maupun komunitas (TensorFlow dibuat oleh Google dan PyTorch oleh Facebook). Tapi MXNet juga menarik karena mendukung C++ dan ada dukungan dari Amazon (bisa dijalankan di AWS). Sedangkan Theano kurang menarik karena penciptanya mengatakan tidak akan melanjutkan pengembangannya lagi, dan CNTK dari Microsoft kurang menarik karena rasanya tidak memberikan hal baru dari yang diberikan TensorFlow dan PyTorch (kecuali kalau Anda memakai C# atau .NET, maka CNTK adalah pilihan utama Anda). Tapi ini hanya sekedar perasaan dan tidak didasari penelitian apapun. Kalau Caffe saya belum meneliti.

Saya juga mengesampingkan beberapa librari dengan pertimbangan sebagai berikut:

  • scikit-learn: librari pembelajaran mesin yang cukup lengkap di Python, dengan bimbingan yang sangat banyak (dokumen, kursus, buku-buku). Sayangnya tidak mendukung GPU.
  • deeplearning4j: mungkin librari pembelajaran mesin yang paling lengkap untuk Java. Mendukung GPU juga.
  • mlpack: sekilas sepertinya cukup lengkap dan keren (mendukung reinforcement learning dan memakai idiom C++ moderen seperti template), tapi hanya C++ dan tidak mendukung GPU.

Dengan pertimbangan-pertimbangan di atas, kita akan melakukan percobaan dan pengukuran untuk menjawabnya. Kita akan melihat dari beberapa dimensi:

  • CPU vs GPU: membandingkan program yang sama dijalankan di CPU dan GPU
  • Python vs skrip vs C++: membandingkan performansi bahasa Python vs bahasa skrip lainnya vs C++
  • loop vs tervektorisasi (vectorized): membandingkan implementasi dalam bentuk loop dan yang tervektorisasi
  • Windows vs Linux: semua di atas, dibandingkan di Linux dan Windows , dijalankan di komputer yang sama (dual boot).

Kita akan membandingkan beberapa bahasa pemrograman dan librari:

  • librari C++: CUDA, OpenCL, OpenMP
  • bahasa pemrograman skrip: Python, Java, R, Octave, Julia
  • librari pembelajaran mesin: PyOpenCL, TensorFlow, MXNet

Pertanyaannya tentunya, tes apa yang pas dan cukup representatif, bisa berjalan di semua platform, dan tidak terlalu sulit untuk dilakukan?

SAXPY Benchmark

Berawal dari ide di artikel Six Ways to SAXPY, akhirnya saya buat SAXPY Benchmark, yang bisa Anda clone di https://github.com/bennylp/saxpy-benchmark. SAXPY adalah Single Precision A * X Plus Y, yaitu:

Y = A * X + Y

SAXPY dipilih karena kalkulasi ini umum dilakukan, dan mudah diimplementasikan di berbagai bahasa, baik menggunakan loop maupun tervektorisasi:

Loop:

for i=1 to N:
   y[i] += A * x[i]
Tervektorisasi:

   // Y = array[N]
   // X = array[N]
   Y += A * X

Sedangkan kekurangannya adalah, karena kalkulasi yang dilakukan begitu sederhana, maka porsi yang signifikan dihabiskan untuk mengakses memori.

Untuk N, saya set N=2^26, atau 64 mega, tepatnya 67108864. Pertimbangannya adalah agar eksekusinya tidak terlalu cepat atau lambat, dan jangan memakai terlalu banyak memori.

Untuk semua tes, hanya waktu kalkulasi SAXPY yang dihitung. Operasi lain seperti alokasi/dealokasi memori, inisialisasi array, transfer memori antara host dan GPU jika ada, dan verifikasi hasil tidak dimasukkan dalam pengukuran waktu.

Untuk setiap tes akan diulang berkali-kali (10-20 kali) dan 5 hasil terbaik akan dicatat dan diambil rata-ratanya. Kadang-kadang tes juga saya ulang di lain waktu untku melihat apakah bisa mendapatkan hasil yang lebih cepat.

Tes-tes dilakukan di satu komputer desktop yang dual-boot Windows dan Linux, dengan spesifikasi sbb:

Ubuntu 16.04 Windows 10
Host Intel i7-6700 3.4 GHz, 4 core HT, 16 GB RAM
GPU NVidia GeForce GTX 1080 8GB
OS Ubuntu 16.04 64bit Windows 10 64bit
C++ g++ 5.4.0 Visual Studio 2015 C++
CUDA 9.0 8.0
OpenCL
  • Khronos header 1.2
  • Intel driver 16.1.1
  • NVidia driver 1.2
  • Intel SDK 7.0.0.2519
  • OpenCL from CUDA SDK
OpenMP bawaan kompiler bawaan kompiler
Python 3.5.2  3.5.3 64bit
Java Oracle JDK SE 1.9.0.1 Oracle JDK SE 1.8.0_9
Octave 4.0.0 64bit  4.2.1 64bit
R 3.2.3 64bit 3.4.2 64bit
Julia  0.6.1 64bit 0.6.1 64bit
TensorFlow 1.4 CUDA 9.0 1.3 CUDA 8.0
MXNet mxnet-cu90 (0.12.1) mxnet-cu80 (0.12.0)
PyOpenCL 2015.1  2017.2

Dengan spesifikasi dan keterbatasan di atas, mari kita mulai!

Vektorisasi

Sebelum masuk ke pemrograman paralel, ada satu idiom yang penting untuk dipahami dalam pemrograman numerik. Dalam setiap kesempatan, kita selalu disarankan untuk menghindari loop dan memakai implementasi yang tervektorisasi, artinya implementasi yang menggunakan operasi vektor atau matriks (atau array), seperti pada contoh SAXPY di atas. Tujuannya adalah agar operasi dapat berjalan dengan lebih cepat.

opengraph-icon-200x200Kita akan tinjau misalnya di Python. Di Python, SAXPY bisa kita implementasikan sebagai loop atau dengan Numpy. Implementasi dengan versi loop dapat Anda lihat di kode asal saxpy_loop.py, yang isi garis-besarnya kurang lebih adalah sebagai berikut.

(catatan: untuk kode-kode di bawah, AVAL, XVAL, dan YVAL adalah konstanta random)

x = [XVAL] * N
y = [YVAL] * N
for i in range(N):
    y[i] += AVAL * x[i]

numpy_project_pageDi Python, Numpy adalah librari fundamental untuk komputasi numerik dan sains. Implementasi tervektorisasi dengan Numpy dapat Anda lihat di kode asal saxpy_numpy.py yang isi garis-besarnya kurang lebih adalah sbb:

import numpy as np
x = np.zeros(N, dtype=np.float32) + XVAL
y = np.zeros(N, dtype=np.float32) + YVAL
y += AVAL * x

Hasil berbandingan waktu eksekusi kedua cara di atas adalah sbb:

python-loop-vs-numpy-linux-cpu.png

Seperti terlihat di grafik di atas, merubah implementasi Python dari loop menjadi operasi vektor dengan Numpy bisa mempercepat eksekusi sebanyak kira-kira 155 kali!

Agar lebih lengkap, dan untuk mengenalkan bentuk grafik yang akan kita pakai dalam artikel ini, berikut adalah perbandingan Python loop vs tervektorisasi di Linux dan Windows (dengan memakai kode asal yang sama seperti di atas, dijalankan di dua sistem operasi di komputer yang sama).

python-loop-vs-numpy-cpu.png

Terlihat di Windows vektorisasi juga mempercepat eksekusi kurang-lebih 155-160 kali.

Di Windows terlihat performansi Python lebih jelek dari pada di Linux (sekitar 1.6x lebih lambat), baik versi loop maupun Numpy. Untuk sementara ini kita abaikan dulu, nanti akan kita bahas di bawah.

Vektorisasi dengan Python Pandas

pandas-logo Pandas adalah librari yang sangat popular di Python untuk analisis data dengan struktur tabular seperti tabel.

Operasi di Pandas juga tervektorisasi. Untuk SAXPY, implementasi lengkapnya dapat Anda lihat di kode asal saxpy_pandas.py yang isi garis-besarnya kurang lebih adalah sbb:

import pandas as pd
import numpy as np

df = pd.DataFrame(np.zeros((N, 2), dtype=np.float32), 
                  columns=["y", "x"])
df.x = XVAL
df.y = YVAL
df.y += AVAL * df.x

Berikut adalah perbandingan performansi antara Pandas dan Numpy.

python-loop-vs-numpy-vs-pandas-cpu.png

Ternyata ada perbedaan performansi yang signifikan (sekitar 4x lipat) antara Pandas dan Numpy.

Vektorisasi di Bahasa R

120px-rlogoBahasa R adalah bahasa untuk komputasi numerik dan grafik yang dipakai secara luas di bidang statistika, sains data, dan finansial. Untuk bidang-bidang ini mungkin pemakaiannya bahkan lebih luas dari pada Python.

Bahasa R mendukung eksekusi di GPU dengan API CUDA dan kompilasi kode secara JIT (just in time) menjadi byte-code.

Perlu juga dicatat bahwa R tidak mempunya tipe data floating point dengan presisi tunggal (FP32), sehingga operasinya akan lebih lambat.

Idiom vektorisasi juga terbukti meningkatkan performansi di bahasa R. Berikut kita bandingkan implementasi dalam bentuk loop (saxpy_loop.R) dengan beberapa tipe data vektor di R:

Terus terang saya kurang memahami perbedaan detil antara tipe-tipe data ini. Saya hanya melihat ada beberapa tipe data yang mirip-mirip dan saya ingin membandingkan performansinya.

Berikut adalah hasilnya.

r-loop-vs-vec.png

Seperti di Python, vektorisasi di R juga akan mempercepat eksekusi secara signifikan. Paling parah adalah loop di Linux, yang entah kenapa memakan waktu 75 detik, padahal loop yang sama di Windows hanya 5 detik. Kalau ini kita abaikan, maka penggunaan tipe data tervektorisasi tercepat (dalam hal ini array atau data.frame) akan mempercepat eksekusi kurang lebih 35x.

Python vs Skrip Lain vs Java vs C++

Octave

58db5ad58500c6c5045a9677_octaveOctave adalah bahasa pemrograman berorientasi matematika dengan sintaks kompatibel dengan Matlab. Sangat populer di bidang sains, dan banyak disarankan sebagai sarana untuk membuat purwarupa model pembelajaran mesin karena sintaksnya lebih mudah.

Julia

juliaJulia diklaim sebagai bahasa pemrograman tingkat tinggi dengan performa tinggi untuk komputasi numerik. Bahasa ini mengunggulkan kecepatannya yang lebih cepat dari bahasa-bahasa skrip lain dan Java (lihat di halaman depan laman julialang.org), dan mendukung eksekusi paralel baik dengan CPU atau GPU. Untuk pembelajaran mesin di Julia dapat memakai MXNet.

Java

java_logo2Java tentu semua sudah tahu. Saya ikutkan di sini sebagai komparasi. Dan ternyata hasilnya cukup mengejutkan.

~~~~~

Berikut grafik perbandingan performansi eksekusi non-paralel dari:

script-vs-script-vs-java-vs-c++-cpu

Ternyata R dan Octave tidak lebih cepat dari pada Numpy, walaupun kadang orang mengklaim begitu. Kecepatan Numpy lumayan, di Linux, hanya 2.1x lebih lambat dari pada kode natif C++ (kalau di Windows 3.4x). Dengan demikian kalau ada yang mengklaim kecepatan Numpy mendekati kecepatan natif, maka itu juga tidak benar.

Performansi Java sangat bagus, kecepatannya hanya lebih lambat 10-15% dibandingkan C++.

Namun yang mengejutkan adalah Julia, yang kecepatannya bisa sama dengan C++, bahkan bisa lebih cepat! Sejujurnya, tadinya untuk kompilasi C++ saya pakai opsi optimisasi seadanya (-O3 di gcc atau /Ox di msvc). Tapi dengan opsi ini ternyata kecepatannya kalah dibanding Julia! Oleh karena itu kemudian opsinya saya maksimalkan lagi agar lebih cepat.

Devektorisasi Julia

Hasil pada Julia menunjukkan fenomena menarik dan berkebalikan dengan idiom vektorisasi di bahasa-bahasa lain, yaitu kecepatan implementasi loop di Julia malah justru lebih cepat dari implementasi tervektorisasi. Agar lebih jelas, berikut saya tampilkan lagi perbandingan performansi implementasi dalam loop dan vektor, dibandingkan dengan C++.

julia-loop-vs-vector.png

Julia memang akan mengkompilasi kode kita menjadi kode mesin dengan kompiler JIT (just in time). Julia juga menyediakan beberapa instruksi tingkat bawah pada kode asal kita agar kompiler dapat mengoptimasi secara lebih baik.  Berikut adalah kutipan kode asal SAXPY untuk Julia loop. Anda bisa melihat kode asal lengkapnya di file saxpy_loop.jl.

function saxpy( a, x, y )
    @simd for i=1:length(x)
        @inbounds y[i] += a*x[i]
    end
end

Instruksi untuk membantu optimisasi kompiler dapat Anda lihat di kutipan di atas (yaitu @simd dan @inbound). Ini bisa menjadi fitur plus atau minus dari Julia. Plusnya kita punya fasilitas untuk mengontrol optimasi, sedang minusnya adalah perhatian kita jadi terpecah untuk melakukan optimasi tingkat bawah, sesuatu yang harusnya dilakukan kompiler.

Dengan performansi yang begitu bagus untuk versi loop, sangat disayangkan bahwa versi vektornya lambat. Tapi Julia saat ini masih terus dikembangkan (sekarang masih versi 0.6.1) dan semoga performansinya akan lebih baik di versi-versi berikutnya.

Librari Pembelajaran Mesin Python

Sekarang kita akan menggunakan librari pembelajaran mesin untuk melakukan operasi array. Penggunaan librari ini memungkinkan kita untuk mengeksekusinya di GPU nanti.

Perbandingan performansi operasi array menggunakan Numpy (saxpy_numpy.py) dan TensorFlow (saxpy_tf.py) dan MXNet (saxpy_mxnet.py) di CPU adalah sebagai berikut.

vectorized-numpy-vs-frameworks-cpu.png

Sesuai grafik di atas, kalau hanya  melakukan operasi numerik sederhana seperti SAXPY, librari pembelajaran mesin lebih lambat dari pada memakai Numpy. Mungkin karena pekerjaan yang dilakukan librari tersebut terlalu canggih, yang ongkosnya lebih tinggi dari pada melakukan SAXPY secara langsung. Tapi ini hanya tebakan saja.

Catatan tentang Performansi Windows

Mungkin Anda bertanya-tanya dari tadi, kenapa performansi di Windows hampir selalu lebih buruk dari pada di Linux. Saya belum menyelidiki apakah memang perbedaan performansi ini memang sudah “dari sononya”. Tapi yang jelas kondisi Windows saya, karena sudah dipakai beberapa tahun, sudah banyak sekali isinya. Saya sudah berusaha mematikan sebanyak mungkin program ketika menjalankan pengukuran, namun masih ada puluhan proses lain yang saya tidak tahu juga apa saja yang masih berjalan.

Sedangkan Linux saya baru saja dibangun.

Disamping itu ada perbedaan kompiler, versi program, dan librari, karena perbedaan ketersediaannya. Harusnya perbedaan ini tidak berpengaruh sih, karena tesnya sangat sederhana.

Dengan perbedaan kedua sistem seperti ini, maka perbedaan pada hasil ini jangan dijadikan sebagai referensi yang terlalu serius. Saya sarankan Anda mencoba sendiri SAXPY benchmark di sistem Anda.

Operasi Array pada Python dengan GPU

Seperti dikatakan di atas, penggunaan librari pembelajaran mesin pada Python memungkinkan eksekusi pada GPU. Mari kita lihat perbedaan hasilnya antara eksekusi di CPU dan GPU dengan librari-librari ini.

Sebagai catatan, percobaan ini dilakukan dengan GPU NVidia GeForce GTX 1080. Untuk penggunaan GPU lain tentu hasilnya akan lain. Kode sumber yang dipakai sama dengan untuk versi CPU di atas, yaitu saxpy_numpy.py (Numpy), saxpy_tf.py (TensorFlow) dan saxpy_mxnet.py (MXNet), tinggal dikasi argumen berbeda.

vectorized-numpy-vs-frameworks-gpu.png

Seperti terlihat, di Linux, librari tercepat di GPU (MXNet) bisa mempercepat eksekusi 6x dibanding eksekusi tercepat di CPU (Numpy). Ironisnya, MXNet jalannya paling lambat kalau di CPU, hampir 3x lipat dari Numpy.

Untuk  TensorFlow tidak ada perbedaan kecepatan yang berarti pada eksekusi di GPU antara di Linux dan Windows. Harusnya memang begitu, karena eksekusi di GPU relatif bebas dari pengaruh “junk” yang berjalan di host, jadi mestinya hasilnya lebih konsisten.

Tapi untuk MXNet, ternyata perbedaannya cukup besar, sekitar 2.9x lebih lambat di Windows (sekali lagi walaupun eksekusinya di GPU yang sama). Dalam hal ini, salah satu perbedaan yang mungkin berpengaruh adalah versi CUDA (librari komputasi GPU dari NVidia) yang dipakai MXNet di kedua platform berbeda. Windows memakai CUDA 8.0, sedangkan Linux memakai CUDA 9.0. Mungkin ini penyebabnya, atau ada penyebab lain, tidak jelas. Anehnya perbedaan ini juga ada di TensorFlow, tapi toh performansinya hampir sama.

Python GPU vs C++ CPU

Kita bandingkan kecepatan librari Python GPU tadi dengan loop C++ yang berjalan di CPU.

frameworks-gpu-vs-c++-cpu.png

Librari Python dengan GPU berhasil mengalahkan loop sederhana C++ di CPU.

Tapi peningkatan kecepatannya rasanya kurang maksimal. Librari GPU tercepat dengan Python (MXNet) hanya 2.9x lebih cepat dari loop C++. Padahal sudah memakai GPU yang lumayan canggih dan mahal (GTX 1080 termasuk kartu grafis konsumer papan atas). Padahal sudah memakai librari yang state-of-the-art, melawan program C yang bodo-bodoan. Apalagi kalau memakai TensorFlow, maka hanya lebih cepat 1.2x.

Sepertinya kita harus menggali lebih dalam.

onedoesnotsimplyusegpu

Pemrograman Paralel CPU dan GPU dengan C++

Untuk pemrograman GPU dengan C++, kita akan meninjau tiga librari.

CUDA

cuda-logoDalam kemasan SDK yang dibuat NVidia untuk mengakses GPU, CUDA adalah API di tingkat yang paling bawah. Selain itu, kemasan juga berisi librari-librari tingkat yang lebih tinggi misalnya operasi aritmetika (BLAS), pemrosesan sinyak, dan jaringan syaraf tiruan (neural network).

Contoh program CUDA yang sederhana bisa Anda lihat di kode asal program SAXPY untuk CUDA, yaitu file saxpy_cuda.cpp. Kode asalnya hanya sekitar 50 baris sehingga cukup mudah untuk diikuti. Perintah untuk mengkompilenya dapat Anda lihat di Makefile.

OpenCL

opencl_logo_rgb1OpenCL adalah librari untuk pemrograman paralel baik untuk CPU, GPU, DSP, FPGA, dan piranti akselerator lain. Kelebihan OpenCL adalah dukungan untuk jenis perangkat keras yang lebih banyak (misalnya CPU dari Intel dan kartu grafis AMD), selain kartu grafis NVidia juga, jadi menggunakan ini akan membuat program kita lebih portabel.

Contoh program C++ OpenCL yang sederhana bisa Anda lihat di kode asal SAXPY untuk OpenCL yaitu file saxpy_ocl1.cpp. Perintah untuk mengkompilenya dapat Anda lihat di Makefile. Kalau Anda ingin melihat implementasi SAXPY dalam OpenCL dalam bahasa C, bisa dilihat di file saxpy_ocl2.cpp. File ini jauh lebih panjang dari pada versi C++.

OpenMP

openmp-enabling-hpc-since-1997OpenMP (Open Multi-Processing) adalah salah satu spesifikasi pemrograman paralel yang paling lama. Versi 4 dari spesifikasi OpenMP mendukung pemrograman GPU. Namun untuk percobaan kita, saya hanya mencoba paralelisme di CPU, karena untuk eksekusi di GPU sepertinya kita harus kompile sendiri gcc versi trunk.

Kompiler-kompiler baru sudah mendukung OpenMP. Saya mencoba dengan gcc 5.4 dan Visual C++ 2015, dukungan OpenMP sudah ada dan tinggal memberi opsi -fopenmp dan /fopenmp untuk mengaktifkannya di kedua kompiler tersebut. Untuk di MacOS, kompilernya juga sudah mendukung OpenMP, tapi ada librari lagi yang harus diunduh.

OpenMP adalah pemrograman berbasis direktif. Perintah-perintah untuk OpenMP diberikan dalam #pragma omp seperti contoh berikut dari loop di  kode asal SAXPY. Anda bisa melihat kode asal lengkapnya di file saxpy_omp.cpp.

#pragma omp parallel
{
    int num_threads = omp_get_num_threads();
    for (int i=omp_get_thread_num(); i<N; i+=num_threads)
        y[i] += AVAL * x[i];
}

Dibanding dengan CUDA dan OpenCL, maka pemrograman paralel OpenMP tingkatnya lebih tinggi sehingga lebih mudah dipakai. Namun kekurangannya adalah dia terasa seperti “bahasa dalam bahasa” (yaitu bahasa direktif di dalam bahasa C/C++) dan sifat direktif yang lebih implisit (misalnya direktif di atas kalau dikompile di kompiler yang tidak mendukung OpenMP maka akan diabaikan) membuat kita harus lebih berhati-hati agar program berjalan seperti yang kita inginkan.

Yang Tidak Dicoba

Satu yang tidak sempat saya coba adalah OpenACC (Open Accelerators), sebuah model pemrograman berbasis direktif (seperti OpenMP)  untuk permrograman paralel secara portabel di CPU, GPU, dan piranti akselerator lain. Dukungan untuk OpenACC sepertinya masih kurang. GCC misalnya, hanya ada dukungan eksperimental di GCC 7. Visual C++ tidak/belum mendukung OpenACC.

C++ Paralel di CPU

Kita akan coba dulu di CPU. Kode sumber untuk SAXPY OpenCL dan OpenMP sama baik untuk eksekusi di CPU dan GPU (kalau CUDA hanya bisa jalan di GPU), cukup hanya diberi argumen cpu atau gpu untuk mengaturnya. Harapan saya dengan menggunakan OpenCL dan OpenMP eksekusinya akan lebih cepat dibanding loop C++ biasa, karena CPU saya punya 4 core, dengan hyper-threading maka bisa ada 8 eksekusi paralel yang bisa dijalankan.

parallel-c++-cpu.png

Ternyata hasilnya tidak seperti yang diharapkan. Dari kedua librari tadi tidak satupun yang bisa mempercepat eksekusi program.

Penyebabnya mungkin karena operasinya terlalu sederhana, jadi overhead untuk membuat eksekusi paralel lebih besar dari pada kalau menjalankan operasinya sendiri. Jadi untuk mendapatkan paralelisme yang optimal pada CPU, kita harus memecah program kita menjadi task yang lebih besar, mirip kalau kita memecah task untuk multi-threading mungkin. Ini masuk akal karena implementasi paralelisme OpenCL dan OpenMP di sistem operasi umum seperti Windows dan Linux kemungkinan memakai thread.

C++ Paralel di GPU

Selanjutnya mari kita coba bandingkan performansi C++ loop dengan eksekusi di GPU. Kali ini OpenMP yang tidak ikut karena seperti dibahas di atas SAXPY OpenMP saya belum mendukung eksekusi di GPU.

c++-cpu-vs-gpu.png

Terlihat peningkatannya sangat signifikan, bisa sampai 13x.

Dan terlihat juga, performansi GPU cukup konsisten, dengan dua API berbeda, di sistem operasi berbeda, perbedaan performansinya hanya 6% maksimum.

PyOpenCL dan PyCUDA

Dengan performansi dan konsistensi CUDA dan OpenCL dari C++ yang cukup menjanjikan, mungkin ada jalan tengah untuk mengakses GPU dari Python, dengan menggunakan API yang lebih low level.

Jalan tengah itu adalah:

  • PyOpenCL: wrapper Python yang memberikan API OpenCL di Python
  • PyCUDA: wrapper Python yang memberikan API CUDA di Python

Namun penting untuk diketahui bahwa walaupun kita menggunakan Python, namun fungsi yang dieksekusi di GPU tetap harus dibuat dalam bahasa C (di dalam program Python), yang dikompile oleh PyOpenCL atau PyCUDA ketika program dijalankan. Untuk lebih jelasnya silakan lihat kode sumbersaxpy_pycuda.py dan saxpy_pyocl.py.

PyCUDA

Kode asal untuk pemrograman SAXPY dengan PyOpenCL dapat Anda lihat di file saxpy_pycuda.py. Berikut adalah hasilnya.

pycuda-vs-c++.png

Sesuai dengan grafik di atas, performansi PyCUDA sangat mendekati performansi CUDA dengan C++. Perbedaan performansi antara Windows dan Linux menurut saya karena ada bagian yang dieksekusi di host.

PyOpenCL

Kode asal untuk pemrograman SAXPY dengan PyOpenCL dapat Anda lihat di file saxpy_pyocl.py. Berikut adalah hasilnya.

pyopencl-vs-opencl.png

Terlihat konsistensi performansinya cukup membingungkan.

Untuk CPU, kalau C++ OpenCL tidak bisa mempercepat eksekusi, maka PyOpenCL justru bisa mempercepat eksekusi di CPU! Kalau benar begitu dan bukan karena hal lain (misalnya bugs) maka ini cukup menarik untuk diamati.

Di sisi lain, untuk GPU, performansi PyOpenCL sungguh mengecewakan, sangat jauh dari performansi dan konsistensi OpenCL C++ untuk GPU.

Jadi untuk OpenCL dan PyOpenCL, hanya OpenCL C++ untuk GPU yang memberikan performansi yang konsisten.

Kesimpulan

Berikut adalah grafik selengkapnya dari semua percobaan, minus Python loop dan R loop yang terlalu lambat sehingga grafiknya akan merusak skala grafik lain kalau dipasang.

conclusion.png

Mudah-mudahan hasil percobaan di atas bisa menambah informasi Anda untuk memilih platform, bahasa, dan API/librari apa untuk dipakai di proyek Anda.

Sekali lagi harap diingat bahwa percobaan ini hanyalah mengukur SAXPY, suatu ekspresi sederhana yang hanya merepresentasikan SAXPY dan tidak untuk dipakai untuk menggeneralisir performansi suatu bahasa, librari, atau sistem operasi.

Untuk artikel selanjutnya kita akan bahas perangkat keras GPU yang bisa kita pakai.

Iklan

Satu respons untuk “Mengenal dan Membandingkan Berbagai Pemrograman Paralel pada CPU dan GPU [GGPU – Bag. 2]

Add yours

Tinggalkan Balasan

Isikan data di bawah atau klik salah satu ikon untuk log in:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout /  Ubah )

Foto Google+

You are commenting using your Google+ account. Logout /  Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout /  Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout /  Ubah )

Connecting to %s

Buat situs web atau blog gratis di WordPress.com.

Atas ↑

%d blogger menyukai ini: