• Tidak ada hasil yang ditemukan

BAB II DASAR TEORI. 2.1 Pergerakan Telepon Seluler Sebagai Sumber Data

N/A
N/A
Protected

Academic year: 2021

Membagikan "BAB II DASAR TEORI. 2.1 Pergerakan Telepon Seluler Sebagai Sumber Data"

Copied!
22
0
0

Teks penuh

(1)

II-1

BAB II

DASAR TEORI

Bab ini memuat beberapa dasar teori yang mendukung dalam pengerjaan Tugas Akhir, serta penjelasan mengenai berbagai metode yang digunakan untuk menyelesaikan permasalahan dalam Tugas Akhir.

2.1 Pergerakan Telepon Seluler Sebagai Sumber Data

Umumnya, pengenalan kemacetan otomatis yang terjadi di jalan raya dapat dilakukan menggunakan data kecepatan pergerakan kendaraan. Salah satu metode untuk mendapatkannya adalah dengan menggunakan sensor yang dipasang di segenap penjuru kota. Dengan asumsi bahwa dalam setiap kendaraan terdapat minimal sebuah telepon seluler, maka dalam Tugas Akhir ini sendiri diusulkan penggunaan pergerakan telepon seluler sebagai sumber data. Alasan pemilihan pergerakan telepon seluler sebagai sumber data dapat dilihat pada Tabel II-1.

Tabel II-1: Perbedaan pendeteksian kecepatan kendaraan dengan sensor dan telepon seluler

Sensor Kecepatan Telepon Seluler

Kecepatan kendaraan adalah kecepatan sesaat. Kecepatan kendaraan adalah kecepatan rata-rata antara t1 dan t0.

Tidak dapat mengidentifikasi kecepatan kendaraan individual. Untuk dapat mengidentifikasi kecepatan individual kendaraan, dibutuhkan sensor tambahan.

Dapat mengidentifikasi kecepatan kendaraan individual dengan cara melihat ID dari telepon seluler.

Membutuhkan pembangunan infrastruktur baru di segenap penjuru kota.

Dapat menggunakan infrastruktur telepon seluler yang dimiliki operator telepon seluler. Akurasi posisi dan kecepatan tinggi. Akurasi posisi tergantung metode yang

digunakan, untuk signal-based dapat mencapai 10 m, untuk LBS dapat mencapai 1 m. Akurasi kecepatan tinggi.

Pergerakan dan posisi setiap telepon seluler direpresentasikan sebagai sebuah vektor. Sebuah data telepon seluler memiliki enam properti, yakni:

1. Nomor telepon seluler (ID), dilambangkan dengan id. 2. Timestamp, dilambangkan dengan t(i).

(2)

II-2

3. Latitude pada timestamp ini, dilambangkan dengan xt(i).

4. Longitude pada timestamp ini, dilambangkan dengan yt(i).

5. Arah kecepatan terhadap Utara, dilambangkan dengan θt(i). Data ini

diambil dengan rumus:

𝜃𝑡(𝑖)= tan−1

𝑦𝑡 𝑖 − 𝑦𝑡(𝑖−1) 𝑥𝑡(𝑖)− 𝑥𝑡(𝑖−1)

6. Besar kecepatan, dilambangkan dengan vt(i). Data ini diambil dengan

rumus kecepatan rata-rata:

𝑣𝑡 𝑖 =

(𝑥𝑡(𝑖)− 𝑥𝑡(𝑖−1))2+ (𝑦𝑡(𝑖)− 𝑦𝑡(𝑖−1))2

𝑡 𝑖 − 𝑡(𝑖 − 1)

2.2 Teori Graf

Dalam Tugas Akhir ini, Teori Graf digunakan untuk merepresentasikan jalan-jalan yang terdapat dalam sebuah kota. Graf adalah sebuah struktur data yang terdiri atas simpul-simpul (vertex) dan sisi-sisi (edge). Sebuah sisi menghubungkan dua buah simpul [MUN06].

2.2.1 Jenis Graf

Graf dapat dikelompokkan menjadi beberapa kategori (jenis) bergantung pada sudut pandang pengelompokannya. Pengelompokan graf dapat dipandang berdasarkan ada tidaknya sisi ganda atau gelang, berdasarkan jumlah simpul, atau berdasarkan orientasi arah pada sisi. Penjelasannya pada Tabel II-2.

Tabel II-2: Jenis graf

Sudut Pandang Pengelompokan Jenis Graf Keterangan

Ada atau tidaknya sisi ganda Graf sederhana (simple graph)

Tidak mengandung sisi ganda

Graf tak-sederhana (unsimple graph)

Mengandung sisi ganda

Jumlah simpul Graf berhingga Jumlah simpul berhingga

Graf tak-berhingga Jumlah simpul tidak berhingga

Orientasi arah pada sisi Graf tak-berarah Sisi tidak memiliki orientasi arah

(3)

II-3

Graf yang digunakan dalam Tugas Akhir ini adalah graf tak-sederhana, berhingga, dan berarah.

2.2.2 Implementasi Graf

Dalam memori komputer, graf dapat diimplementasikan dengan struktur data sebagai berikut:

1. adjacency list 2. adjacency matrix 3. edge list

Dalam Tugas Akhir ini, representasi yang dipilih adalah adjacency list. Bahasa yang digunakan untuk merepresentasikan graf dalam sebuah file adalah GraphViz DOT. Sedangkan library graf yang dipakai untuk membaca bahasa GraphViz dan membangun reprsentasi graf dalam memori komputer adalah Boost Graph Library (BGL).

2.3 Stream Programming

Stream programming adalah sebuah teknik pemrograman konkuren yang dapat digunakan untuk melakukan komputasi paralel. Dalam stream programming, data diorganisasi menjadi sekumpulan stream, dan sebuah program yang disebut kernel

diaplikasikan pada setiap elemen stream [BUC06]. Dari Gambar II-1, dapat dilihat bahwa stream programming menggunakan paradigma Single Instruction, Multiple Data (SIMD). Artinya, program yang sama dieksekusi pada sekumpulan data secara paralel.

Salah satu perangkat keras yang dapat digunakan untuk melakukan stream programming adalah Graphic Processing Unit (GPU). GPU sangat cocok untuk diprogram dengan teknik ini, karena GPU dirancang dari awal untuk melakukan proses rendering objek 3D, dimana proses tersebut membutuhkan hardware yang dirancang secara khusus untuk memproses data secara SIMD. Untuk mengimplementasikan stream programming pada GPU, ada banyak framework

(4)

II-4

yang dapat dipakai. Beberapa framework yang dapat dipakai dapat dilihat pada Tabel II-3:

Tabel II-3: Framework untuk stream programming pada GPU Nama / Istilah

Framework Interface Tipe Isi

Versi Terbaru Direct Programming Direct3D 9 OpenGL 1.5 API C++ Language Cg / HLSL - BrookGPU Direct3D9 OpenGL 1.5

Bahasa Brook Language Brook Compiler Brook Runtime 0.3 Beta Compute Unified Device Architecture (CUDA) nVidia G80 Series Driver Ekstensi Bahasa C Nvcc Compiler CUDA API CUDA Toolkit CUDA Basic Linear Algebra System CUDA Fast Fourier Transform 1.0 Parallel Programming Single Instruction Multiple Data (SIMD) Multiple Instruction Multiple Data (MIMD) Stream programming Concurrent programming On GPU (GPGPU) On Cell Broadband Engine Direct (via

OpenGL) ATI CTM nVidia CUDA

(5)

II-5

Pada Tugas Akhir ini, stream programming dilakukan di atas sebuah GPU nVidia GeForce 8800GTS 320MB, yang merupakan GPU jenis G80, sedangkan

framework yang dipilih adalah Compute Unified Device Architecture (CUDA). CUDA dipilih karena memiliki robustness yang tinggi (menggunakan ekstensi bahasa C) dan tingkat kesulitannya di antara framework yang lain tidak begitu rumit untuk diimplementasikan dalam Tugas Akhir ini.

2.3.1 Kemampuan Komputasi GPU

Graphic Processing Unit (GPU) adalah salah satu peripheral komputer yang bertugas untuk melakukan proses rendering objek 3D ke layar. Karena rendering

merupakan proses yang sangat paralel, maka evolusi arsitektur GPU pun mengikutinya: GPU berevolusi menjadi sebuah multiprosesor yang mampu menjalankan sebuah tugas secara paralel.

GPU dapat disebut unit komputasi yang terpisah dari CPU karena GPU memiliki prosesor dan memori sendiri. GPU memiliki jumlah Arithmetic Logical Unit

(ALU) yang jauh lebih besar dibandingkan komputer biasa. Oleh karena itu, kemampuan GPU dalam mengolah data yang besar dapat diandalkan. Pada Gambar II-2, dapat dilihat bahwa pertumbuhan kemampuan GPU jika diukur dengan Floating Point Operations per Second (FLOPS) jauh meninggalkan CPU, bahkan meninggalkan hukum Moore.

(6)

II-6

2.4

Compute Unified Device Architecture

(CUDA)

CUDA adalah sebuah framework yang dapat digunakan untuk melakukan stream programming pada GPU nVidia seri G8x. Saat ini, CUDA sudah memasuki versi 1.0. GPU GeForce 8800GTS yang digunakan dalam Tugas Akhir ini merupakan anggota seri nVidia G80.

Prosesor yang ada dalam GeForce 8800GTS berjumlah 96 buah [NVI05]. Prosesor tersebut dinamakan stream processor. Stream processor tersebut dikelompokkan menjadi 12 buah multiprocessor yang masing-masing berisi 8

stream processor. Setiap stream processor akan menjalankan sebuah thread

dalam satu waktu.

Gambar II-3 menerangkan tentang hirarki memori dalam CUDA. Setiap stream processor, yang dianalogikan dengan thread(x,y) dikelompokkan dalam

block-block. Setiap block memiliki memori bersama yang dinamakan shared memory.Shared memory adalah memori berukuran kecil berkecepatan tinggi yang

hanya dapat diakses stream processor dalam block yang sama. Bandwidth antara

stream processor dan shared memory sangat besar, mencapai 70,7 GB/s. Ada lagi jenis memory lain yang bernama device memory. Memori ini dapat diakses oleh seluruh multiprocessor, akan tetapi dengan bandwidth yang lebih lambat. Adapun memori komputer diistilahkan dengan host memory.

Implementasi konsep stream programming pada CUDA adalah sebagai berikut: a. Stream: Dalam CUDA, stream disimpan dalam sebuah memori milik GPU

yang bernama global memory. Stream diorganisasi berdasarkan jumlah

thread yang akan mengaksesnya secara paralel. Setelah itu, thread tersebut harus diorganisasikan menjadi thread block.

b. Kernel: Dalam CUDA, kernel tetap merupakan sebuah program yang beroperasi pada data yang terdapat pada global memory.

(7)

II-7

Gambar II-3: Hirarki memori pada CUDA

Langkah-langkah umum untuk melakukan komputasi pada CUDA adalah sebagai berikut:

1. Tentukan jumlah thread yang akan beroperasi pada stream. Kemudian tentukan jumlah thread per block dan jumlah thread block yang efisien pada hardware CUDA. Jumlah tersebut dinamakan execution configuration. Contoh execution configuration dapat dilihat pada Gambar II-4. Pada gambar itu, sebuah kernel dieksekusi dengan blok berukuran 2x3, dimana setiap blok terdiri atas 5x3 thread.

2. Pindahkan semua data dari host memory ke device memory. Ini harus dilakukan karena GPU tidak bisa membaca host memory.

3. Lakukan komputasi pada device memory beserta execution configuration -nya. Data dalam device memory ini yang akan menjadi stream.

(8)

II-8

Gambar II-4: Thread dan thread block

2.4.1 Contoh Komputasi pada CUDA: Parallel Scan

Agar konsep CUDA dapat dimengerti, maka lebih baik disertakan contoh kasus yang menggunakan CUDA sebagai solusinya. Dalam sub-bab ini akan dijelaskan algoritma parallel prefix sum sebagai contoh penggunaan CUDA. Parallel prefix sum, juga dikenal sebagai parallel scan, adalah sebuah building block untuk banyak algoritma paralel seperti sorting dan pembuatan struktur data. Di sini akan dicontohkan implementasi parallel scan secara efisien pada CUDA beserta perbandingannya dengan metode sekuensial biasa.

Contoh ini juga dimaksudkan untuk menjelaskan konsep pada CUDA yakni:

1. Thread 2. Thread block

(9)

II-9

3. Warp

4. Shared memory

5. Memory banks 6. Memory bank conflict

2.4.1.1 Definisi All-Prefix-Sum

Operasi all-prefix-sum didefinisikan sebagai berikut: Sebuah operasi all-prefix-sum membutuhkan sebuah operator biner asosiatif ⊕ dan sebuah array dari elemen

[a0, a1, …, an] dan mengembalikan array

[a0, (a0 ⊕ a1), …, (a0 ⊕ a1 ⊕ … ⊕ an)]

Contoh: Jika ⊕ adalah penambahan, maka sebuah operasi all-prefix-sum pada array

[3 1 7 0 4 1 6 3] akan menghasilkan

[3 4 11 11 15 16 22 25]

Ada banyak kegunaan all-prefix-sum, meliputi –tetapi tidak terbatas pada- pengurutan (sorting), analisis leksikal, perbandingan string, evaluasi polinom, pemampatan stream, membuat histogram, dan struktur data (graf, pohon, dll) secara paralel [HAR07].

2.4.1.2 Inclusive Scan dan Exclusive Scan

All-prefix-sum pada sebuah array sering disebut scan. Terminologi ini akan dipakai sampai akhir bab 2. Seperti yang dilihat dari contoh di atas, sebuah scan pada array akan menghasilkan array baru dimana setiap elemen j adalah jumlah dari elemen sampai dan termasuk j. Ini disebut inclusive scan. Ada pula jenis scan

lain dimana setiap elemen j pada array hasil berisi jumlah dari seluruh elemen sebelumnya, namun tidak termasuk j itu sendiri. Operasi ini biasa disebut

(10)

II-10

Definisi: Sebuah operasi exclusive scan membutuhkan operator asosiatif biner ⊕ dengan identitas I, dan sebuah array n-elemen

[a0, a1, …, an] dan mengembalikan array

[I, a0, (a0 ⊕ a1), …, (a0 ⊕ a1 ⊕ … ⊕ an-2)]

Contoh: jika ⊕ adalah sebuah penjumlahan, maka exclusive scan pada array [3 1 7 0 4 1 6 3]

akan menghasilkan

[0 3 4 11 11 15 16 22]

Sebuah array exclusive scan bisa dibangkitkan dengan menggeser array inclusive scan ke kanan sebanyak 1 elemen kemudian menyisipkan identitas. Dengan hal yang sama, sebuah array inclusive scan bisa dibangkitkan dengan menggeser array

exclusive scan ke kiri sebanyak 1 elemen kemudian menyisipkan elemen terakhir array hasil dan elemen terakhir array input pada akhir array hasil. Contoh komputasi CUDA yang akan dijelaskan akan memakai exclusive scan.

2.4.1.3 Scan Sekuensial

Pengimplementasian versi sekuensial dari scan (contohnya yang berjalan pada

thread tunggal pada 1 CPU) adalah trivial. Cukup dengan melakukan looping

pada setiap elemen array-input dan menjumlahkan nilai dari elemen array-input yang sebelumnya dengan hasil penjumlahan yang telah dihitung untuk elemen array-output, dan menulis hasilnya pada elemen array-output yang sedang diiterasi.

void scan (float *output, float *input, int length)

{

output[0] = 0; // karena ini adalah prescan, bukan scan

for (int j = 1; j < length; j++) {

output[j] = input[j-1] + output [j-1]; }

}

(11)

II-11

Listing Code II-1 menjelaskan penjumlahan sebanyak kali untuk array n-elemen; ini adalah jumlah minimum penjumlahan yang diperlukan untuk menghasilkan array-output. Ketika versi paralel dibuat, algoritmanya harus work-efficient. Artinya, algoritma ini tidak boleh melakukan lebih banyak penjumlahan dibandingkan versi sekuensialnya. Atau dengan kata lain, kedua implementasi harus memiliki kompleksitas yang paling tidak sama, yakni O(n).

2.4.1.4 Naïve Parallel Scan

for d := 1 to log2n do

forall k in parallel do

if k >= 2d then x[k] := x[k-2d-1] + x[k]

Algoritma II-1: Algoritma paralel scan yang tidak efisien

Pseudocode pada Algoritma II-1 menunjukkan implementasi naïve parallel scan. Sedangkan ilustrasinya dapat dilihat pada Gambar II-5. Permasalahan pada Algoritma II-1 akan terlihat apabila work-complexity-nya diperiksa. Algoritma ini melakukan log𝑑=12𝑛𝑛 − 2𝑑−1 = O (n log2n) operasi penjumlahan, sedangkan algoritma sekuensial melakukan O(n) penjumlahan. Faktor log2n dapat berpengaruh besar pada performa komputasi. Dalam kasus 1 juta elemen, perbedaan performa antara naïve parallel scan dengan algoritma yang work-efficient dapat memiliki faktor 20.

Algoritma II-1 mengasumsikan bahwa ada banyak prosesor sejumlah elemen data. Pada sebuah GPU yang menjalankan CUDA, hal ini tidak selalu berlaku. Keyword forall akan dipecah secara otomatis menjadi batch paralel kecil (dinamakan warps) yang dieksekusi secara sekuensial pada sebuah

multiprocessor. Sebuah GPU G80 mengeksekusi sebuah warp yang terdiri atas 32

thread secara paralel. Karena tidak semua thread berjalan secara bersamaan untuk array yang lebih besar daripada ukuran warp, algoritma di atas tidak akan berjalan sebab ia menulis hasil scan pada array-input. Hasil dari warp yang satu akan

di-overwrite oleh thread pada warp yang lain.

Untuk menyelesaikan masalah ini, sebuah array double-buffer dibutuhkan untuk melakukan scan. Array temporer (temp[2][n]) dibutuhkan untuk melakukan ini. Pseudocode untuk algoritma ini diberikan pada Algoritma II-2, dan kode

(12)

II-12

CUDA C untuk naïve scan diberikan pada Listing Code II-2. Perlu diperhatikan bahwa kode ini akan berjalan hanya pada thread block tunggal di GPU, sehingga ukuran array yang dapat diproses terbatas (hanya 512 elemen pada GPU G80). Ekstensi algoritma untuk array berukuran sangat besar akan dibahas selanjutnya.

for d := 1 to log2n do

forall k in parallel do

if k >= 2d then

x[out][k] := x[in][k-2d-1] + x[in][k]

else

x[out][k] := x[in][k] swap(in, out)

Algoritma II-2: Versi double buffer dari parallel scan

Gambar II-5: Melakukan scan pada array 8 elemen dengan algoritma naive parallel scan

__global__ void scan(float *g_odata, float *g_idata, int n)

{

extern __shared__ float temp[]; // dialokasi saat invokasi

int thid = threadIdx.x;

int pout = 0, pin = 1;

// load inputs into shared memory

// this is exclusive scan, so shift right by one and set the first elmt to 0

temp[pout*n + thid] = (thid > 0)? g_idata[thid-1] : 0; __syncthreads();

for (int offset = 1; offset < n; offset *= 2)

{

(13)

II-13 pin = 1 - pout;

if (thid >= offset)

temp[pout*n + thid] += temp[pin*n + thid - offset];

else

temp[pout*n + thid] = temp[pin*n + thid]; __syncthreads();

}

g_odata[thid] = temp[pout*n+thid]; // write output }

Listing Code II-2: Kode CUDA C untuk algoritma naïve scan. Versi ini hanya dapat menangani array yang besar maksimumnya sama dengan jumlah maksimum thread dalam sebuah thread block

2.4.1.5 Algoritma Parallel Scan yang Work-Efficient

Dalam sub-bab ini, akan dijelaskan sebuah algoritma scan yang work-efficient, yang menghindari faktor ekstra log2n yang dihasilkan oleh naïve scan pada sub-bab sebelumnya. Untuk melakukan ini, akan digunakan sebuah algorithmic-pattern yang sering digunakan dalam komputasi paralel: balanced-tree. Idenya adalah membangun balanced binary tree pada input data dan menyapunya ke dan dari akar untuk menghitung prefix sum. Dalam algoritma scan yang work-efficient

ini, dua buah fase operasi dilakukan pada array di shared memory, yakni: fase reduksi (juga disebut fase up-sweep) dan fase down-sweep. Dalam fase reduksi, traversal dilakukan pada pohon dari daun untuk menghitung jumlah parsial pada node internal dalam pohon, seperti yang ditunjukkan pada Gambar II-6. Ini dikenal juga dengan nama reduksi paralel, karena sesudah fase ini node root (node terakhir pada array) akan berisi hasil dari seluruh node pada array. Pseudocode

untuk fase reduksi diberikan pada Algoritma II-3.

Dalam fase down-sweep, traversal balik dilakukan pada pohon menggunakan hasil penjumlahan parsial untuk membangun scan pada array dengan menggunakan hasil penjumlahan parsial pada fase reduksi. Fase down-sweep ditunjukkan pada Gambar II-7 dan pseudocode ditunjukkan pada Algoritma II-4. Karena ini adalah

exclusive scan, di antara dua fase elemen terakhir diisi dengan nol. Angka nol ini terpropagasi balik ke elemen pertama array dalam fase down-sweep. Kode CUDA C untuk algoritma selengkapnya diberikan dalam Listing Code II-3. Seperti kode

(14)

II-14

naïve scan pada sub-bab sebelumnya, kode pada Listing Code II-3 akan berjalan pada thread block tunggal. Karena ia memproses dua elemen array per thread, maka ukuran array maksimal pada kode ini adalah 1024 elemen pada G80. Scan

dari array berukuran besar akan dijelaskan pada sub bab berikutnya.

Algoritma scan ini melakukan O(n) operasi (yakni 2 * (n-1) penjumlahan dan n-1

swap); sehingga algoritma ini bekerja secara efisien pada array berukuran besar daripada algoritma naïve pada sub bab sebelumnya. Meskipun begitu, efisiensi algoritma belumlah cukup: algoritma tersebut harus berjalan pada hardware

secara efisien. Jika operasi scan ini diperiksa pada GPU yang menjalankan CUDA, akan terlihat bahwa algoritma ini mengalami banyak konflik shared memory bank (memory bank conflict). Hal ini mengurangi performa pada setiap akses shared memory, dan berefek negatif secara signifikan pada performa keseluruhan. Dalam sub-bab berikutnya akan dibahas sedikit modifikasi sederhana yang dapat dilakukan untuk menghitung alamat memori pada GPU untuk mengembalikan banyak performa yang hilang.

for d := 0 to log2(n-1) do

for k from 0 to n-1 by 2d+1 in parallel do

x[k+d2+1-1] = x[k+2d-1] + x[k+2d+1-1]

Algoritma II-3: fase reduksi (up-sweep) dari algoritma scan yang work-efficient

(15)

II-15 x[n-1] := 0

for d := log2n down to 0 do

for k from 0 to n-1 by 2d+1 in parallel do

t := x[k+2d-1]

x[k+2d-1] := x[k+2d+1-1]

x[k+2d+1-1] := t + x[k+2d+1-1]

Algoritma II-4: Fase down-sweep dari algoritma parallel-sum yang work-efficient

Gambar II-7: Ilustrasi fase down-sweep dari algoritma parallel-sum yang work-efficient. Perhatikan bahwa langkah pertama me-nol-kan elemen terakhir array

__global__ void prescan(float *g_odata, float *g_idata, int n)

{

extern __shared__ float temp[]; // dialokasi saat invokasi

int thid = threadIdx.x;

int offset = 1;

// Blok A

temp[2*thid] = g_idata[2*thid]; // load input to shared

memory

temp[2*thid+1] = g_idata[2*thid+1];

for (int d = n>>1; d > 0; d >>= 1) // build sum in place up the tree

{

__syncthreads();

(16)

II-16 { // Blok B int ai = offset*(2*thid+1)-1; int bi = offset*(t*thid+2)-1; temp[bi] += temp[ai]; } offset *= 2; } // Blok C if (thid == 0)

temp[n-1] = 0; // set last element = 0

for (int d = 1; d < n; d *= 2) { offset >= 1; __syncthreads(); if (thid < d) { // Blok D int ai = offset*(2*thid+1)-1; int bi = offset*(2*thid+2)-1; float t = temp[ai]; temp[ai] = temp[bi]; temp[bi] += t; } } __syncthreads(); // Blok E

g_odata[2*thid] = temp[2*thid]; // write result to

shared memory

g_odata[2*thid+1] = temp[2*thid+1]; }

Listing Code II-3: Kode CUDA untuk algoritma parallel scan yang work-efficient

2.4.1.6 Menghindari Konflik Bank

Algoritma scan pada sub-bab sebelumnya memiliki kompleksitas yang kira-kira sama dengan algoritma sekuensial. Akan tetapi, algoritma ini masih belum efisien pada nVidia CUDA karena pola akses memorinya. Seperti yang dijelaskan pada [NVI07], shared memory yang dimanfaatkan oleh algoritma scan ini terdiri atas banyak bank. Ketika banyak thread dalam satu warp mengakses bank yang sama,

(17)

II-17

alamat dalam 32-bit word yang sama. Jumlah thread yang mengakses sebuah

bank dinamakan bank conflict degree. Konflik bank menimbulkan serialisasi dari akses bersamaan pada sebuah bank memory, sehingga sebuah akses shared memory dengan derajat n membutuhkan n kali cycle daripada sebuah akses tanpa konflik. Pada G80, yang mengeksekusi 16 thread secara paralel pada setengah

warp, kasus terburuk adalah konflik berderajat 16.

Algoritma pohon biner seperti algoritma scan yang work-efficient menggandakan

stride antara memori akses pada setiap level dalam pohon, sehingga menggandakan pula jumlah thread yang mengakses bank yang sama. Untuk pohon yang dalam, ketika algoritma ini mencapai level tengah pohon, derajat konflik bank akan meningkat, dan menurun lagi di dekat akar ketika jumlah

thread yang aktif berkurang (karena statement if pada Listing Code II-3). Sebagai contoh, bila scan dilakukan pada array 512-elemen, pembacaan dan penulisan shared memory dalam inner loop dari Listing Code II-3 akan menimbulkan konflik bank berderajat 16. Ini akan menimbulkan efek yang signifikan pada performa komputasi.

Konflik bank dapat dihindari apabila array memory __shared__ diperhatikan. Sebagai contoh, dalam konvolusi, ini hanya masalah bagaimana melakukan padding pada array 2D dengan sebuah ukuran yang tidak habis dibagi oleh jumlah

shared memory bank. Scan, karena pendekatannya yang memakai balanced-tree, memerlukan pendekatan yang agak rumit. Konflik bank pada scan dapat dihindari dengan menambahkan padding yang besarnya bervariasi tergantung pada index

memory array yang akan dihitung. Secara spesifik, sebuah nilai ditambahkan pada index, yakni nilai dari index tersebut ditambah jumlah shared memory bank. Hal ini ditunjukkan pada Gambar II-8. Dengan melihat pada Listing Code II-3, blok yang disoroti dari A sampai E dimodifikasi. Untuk memudahkan perubahan kode, maka didefinisikan sebuah makro CONFLICT_FREE_OFFSET, ditunjukkan pada Listing Code II-4.

(18)

II-18 #define NUM_BANKS 16 #define LOG_NUM_BANKS 4 #ifdef ZERO_BANK_CONFLICTS #define CONFLICT_FREE_OFFSET(n) \ ((n) >> NUM_BANKS + (n) >> (2 * LOG_NUM_BANKS)) #else

#define CONFLICT_FREE_OFFSET(n) ((n) >> LOG_NUM_BANKS)

#endif

Listing Code II-4: Macro CONFLICT_FREE_OFFSET

Blok A sampai E pada Listing Code II-3 harus dimodifikasi menggunakan macro

ini untuk menghindari konflik bank. Dua perubahan harus dilakukan pada blok A. Setiap thread me-load 2 elemen array dari __device__ array g_idata ke

__shared__ array temp. Dalam kode yang asli, setiap thread me-load 2 elemen berdekatan, menghasilkan indexing yang berselang-seling pada shared memory array, menimbulkan konflik bank berderajat dua. Dengan me-load dua elemen dari setengah bagian array yang berlainan, sebuah cara untuk menghindari konflik bank telah dilakukan. Juga, untuk menghindari konflik bank saat melakukan traversal pohon, sebuah padding harus ditambahkan pada shared memory array setiap NUM_BANKS(16) elemen. Hal ini dilakukan dengan menggunakan macro pada Listing Code II-4 seperti yang terlihat pada kode blok A sampai E. Perhatikan bahwa offset disimpan pada index shared memory,

sehingga dapat digunakan lagi ketika scan berakhir, yakni ketika menulis hasil scan ke output-arrayg_odata pada blok E.

Blok A:

int ai = thid;

int bi = thid + (n/2);

int bankOffsetA = CONFLICT_FREE_OFFSET(ai);

int bankOffsetB = CONFLICT_FREE_OFFSET(bi);

temp[ai + bankOffsetA] = g_idata[ai]; temp[bi + bankOffsetB] = g_idata[bi];

(19)

II-19

Gambar II-8: Padding sederhana diterapkan pada alamat memori dapat menghilangkan konflik bank berderajat tinggi pada algoritma berbasis tree seperti scan. Diagram bagian atas menunjukkan pengalamatan tanpa padding dan konflik bank yang dihasilkan. Bagian bawah menunjukkan pengalamatan dengan padding tanpa konflik bank.

(20)

II-20 Blok B dan D identik:

int ai = offset * (2 * thid + 1) - 1;

int bi = offset * (2 * thid + 2) - 1;

ai += CONFLICT_FREE_OFFSET(ai); bi += CONFLICT_FREE_OFFSET(bi);

Listing Code II-6: Perubahan pada blok B dan D

Blok C:

if (thid == 0) { temp[n - 1 + CONFLICT_FREE_OFFSET(n-1)] = 0; }

Listing Code II-7: Perubahan pada blok C

Blok E:

g_odata[ai] = temp[ai + bankOffsetA]; g_odata[bi] = temp[bi + bankOffsetB];

Listing Code II-8: Perubahan pada blok E

2.4.1.7 Array dengan Ukuran Bebas

Algoritma yang diberikan dalam sub-bab sebelumnya melakukan scan pada array dalam sebuah thread block. Hal ini tidak bermasalah pada array berukuran kecil, yang ukuran maksimalnya dua kali jumlah maksimum thread dalam sebuah

thread block. Dalam GPU G80, hal ini membatasi hanya 1024-elemen. Jumlah elemen juga harus merupakan kelipatan 2. Dalam sub-bab ini akan dijelaskan bagaimana menyempurnakan algoritma untuk melakukan scan pada array berukuran bebas (tidak pangkat 2).

Ide dasarnya sederhana: Sebuah array berukuran besar dibagi menjadi blok-blok, dimana setiap blok dapat di-scan oleh sebuah thread block tunggal, dan menulis

sum dari setiap blok pada array-of-block-sum lain. Setelah itu, scan dilakukan pada block sum tersebut, membangkitkan sebuah array dari inkremen blok yang ditambahkan pada seluruh elemen pada bloknya yang sesuai.

(21)

II-21

Untuk lebih detail, perhatikan contoh berikut: Misalkan N adalah jumlah elemen array input IN, dan B adalah jumlah elemen yang diproses oleh sebuah blok. Oleh karena itu, N/B thread block akan dialokasi, dengan B/2 thread setiap thread block-nya (pada sub-bab ini, diasumsikan bahwa N adalah kelipatan B dan akan disempurnakan untuk jumlah elemen bebas pada paragraf selanjutnya). Pilihan umum untuk B adalah 512 pada G80. Algoritma scan pada sub-bab sebelumnya digunakan untuk melakukan scan pada setiap blok j secara independen, menyimpan hasil scan pada lokasi sekuensial array-output OUT.

Gambar II-9: Algoritma untuk melakukan scan pada array berukuran besar

Selain itu, sebuah modifikasi minor dilakukan pada algoritma scan. Sebelum me-nol-kan elemen terakhir dari blok j (label B), total sum dari blok j disimpan pada sebuah array bantu SUMS. Array ini kemudian dapat di-inkremen dengan cara yang sama, menuliskan jumlah pada array INCR. Selanjutnya, INCR(j)

(22)

II-22

pada N/B thread block yang berisi B/2 thread. Hal ini ditunjukkan pada Gambar II-9.

Untuk menangani dimensi array yang tidak kelipatan pangkat 2, array tersebut tinggal dibagi menjadi kelipatan B elemen dan diproses dengan cara yang sama seperti di atas (menggunakan B/2 thread per block), dan memproses sisanya dengan kernel yang dimodifikasi untuk menangani elemen yang bukan kelipatan 2 dalam sebuah block. Kernel ini melakukan padding pada shared memory array

dan menginisialisasi memori ekstra ini dengan nol ketika data di-load dari device memory ke shared memory.

Gambar

Tabel II-1: Perbedaan pendeteksian kecepatan kendaraan dengan sensor dan telepon seluler
Tabel II-2: Jenis graf
Tabel II-3: Framework untuk stream programming pada GPU  Nama / Istilah
Graphic  Processing  Unit  (GPU)  adalah  salah  satu  peripheral  komputer  yang
+7

Referensi

Dokumen terkait

pembudidaya di kelurahan Meruya Selatan atau tepatnya di kawasan Kayon, dengan bapak H.Gupron,…..saya mulai usaha penjualan dan budidaya sejak tahun 1980 sebelum dibangunnya

Pembangunan Rumah Oleh Perum Perumnas Provinsi Jawa Barat Tahun 2011. No Nama

Secara terinci, matriks aktualisasi telah mencoba menjabarkan masing-masing sila dari Pancasila mulai dari perwujudan dan aktualisasinya, hingga implikasinya dalam penanganan

Hasil kesimpulan menyatakan bahwa baik secara parsial maupun berganda kualitas produk, kepercayaan, dan kepuasan pelanggan mempengaruhi terhadap loyalitas

Adapun dalam hal ini yang menjadi poin-poin yang observasi pada dasarnya diawali dengan argumen yang diberikan oleh informan pada saat wawancara, namun dalam hal ini sebagai

ناو ضرلا ىف امو تومسلا ىف ام هلل(. وا مـكسفـنا ىف

DATA UJI STATISTIK RANCANGAN ACAK LENGKAP TERHADAP PARAMETER PERTUMBUHAN TANAMAN KEDELAI ( Glyzine max L.).

Penelitian ini digunakan untuk menjawab permasalahan, apakah penggunaan metode Numbered Heads Together (NHT) dapat meningkatkan hasil belajar mata pelajaran