Raport Hurtownie Danych Algorytm Apriori na indeksie bitmapowym oraz OpenCL Mikołaj Dobski, Mateusz Jarus, Piotr Jessa, Jarosław Szymczak Cel projektu: Implementacja algorytmu Apriori oraz jego optymalizacja. Środowisko: Oprogramowanie: Oracle Java 1.60_24 x86_64 Maven 3.03 Sprzęt: jocl 0.1.6 OpenCL bindings for Java weka 3.7.3 Intel Core i7-2630qm (4x2,6GHz (8x with HT)) nvidia GT550M (96 CUDA cores) 16GB RAM 1333MHz Źródła: https://github.com/dzess/dmarf Realizacja: Platforma testowa: Utworzono środowisko umożliwiające testowanie poprawności oraz wydajności dowolnych algorytmów zgodnych ze zdefiniowanym przez nas interfejsem. Poprawnośd działania algorytmu sprawdzana jest poprzez uruchomienie referencyjnej implementacji z biblioteki weka na wybranym do testowania zbiorze danych. Porównywane są w ten sposób utworzone przez wekę oraz testowany algorytm zbiory reguł asocjacyjnych ich ilośd, poprawnośd oraz cechy każdej z nich. Wydajnośd mierzona jest poprzez standardowe mechanizmy pomiaru czasu i pamięci maszyny wirtualnej Java. Platforma umożliwia wykorzystywanie w testach plików w formacie zgodnym z tym udostępnionym na fimi.ua.ac.be Dane te są automatycznie konwertowane do postaci zrozumiałem przez wekę. Algorytm oraz implementacje: Zdecydowano się zaimplementowad wpierw algorytm apriori w postaci naiwnej bez żadnych optymalizacji. Wykorzystuje ona dynamiczne struktury języka Java. Transakcje przechowuje w pamięci jako listy integerów. Algorytm ten działa jednowątkowo.
Druga implementacja algorytmu bazuje na indeksach bitmapowych opisanych między innymi w projekcie gpuminer (http://code.google.com/p/gpuminer/). Powyższy rysunek obrazuje jak kolejne atrybuty istniejące w danej transakcji odpowiadają bitom zapalonym w dwubajtowych klastrach przechowywanych jako pojedyncze chary. Dzięki zastosowaniu takiej reprezentacji sprawdzanie wsparcia zbiorów kandydujących jest szalenie szybkie jednorazowo sprawdzamy 16 atrybutów. Optymalizacja ta działa jednak dobrze tylko dla zbiorów gęstych np. pumsb_star. Zdecydowanie źle zachowuje się rzadki zbiór kosarak, który posiada ponad 40.000 atrybutów, przez co jego bitmapa jest ogromna (około 7GB) oraz prawie pusta. Przykład przedstawia kolejny rysunek:
W algorytmie korzystającym z reprezentacji binarnej zoptymalizowany został również element generowania kandydatów na zbiory częste poprzez wprowadzenie tzw. wędrującej jedynki. a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 A 1 0 1 1 0 0 0 0 0 0 v1 0 0 0 0 1 0 0 0 0 0 v2 0 0 0 0 0 1 0 0 0 0 vi 0...... 1... Jak widad dla zbioru częstego posiadającego atrybuty a1, a3, a4, a5 wystarczy teraz zapalad pojedyncze bity odpowiadające kolejnym atrybutom, dzięki czemu generowanie kandydatów również może się odbywad z wykorzystaniem reprezentacji binarnej. Dla zbiorów gęstych algorytm korzystający z reprezentacji binarnej okazał się wielokrotnie szybszy od naiwnej implementacji. Opisywana powyżej implementacja na reprezentacji binarnej była jednak tylko podstawą do implementacji wykorzystującej mechanizm weryfikacji kandydatów na zbiory częste w technologii OpenCL. Dzięki niej pisząc jeden kod uzyskaliśmy uniwersalną, maksymalnie wielowątkową (nota bene niekorzystającą z wątków systemowych, lecz bezpośrednio sprzętowo-sterownikowych) realizację tego mechanizmu uruchamialną na CPU oraz na GPU. Poniższy obrazek prezentuje ideę: Uruchamiając kod z wykorzystaniem OpenCL na CPU uzyskaliśmy przyspieszenie 4x (4 sprzętowe core y) oraz niewiele większe na GPU. Należy jednak wziąd pod uwagę, że kod w OpenCL w chwili pisania raportu winien byd raczej traktowany, jako działający eksperyment i wymaga jeszcze sporych optymalizacji i refaktoryzacji. Równie ważne jest, że wykorzystany w testach procesor jest jedną z najwydajniejszych jednostek mobilnych dostępnych na rynku, natomiast karta graficzna jest z
Time [s] kategorii mid-range i ma marginesową wydajnośd w porównaniu z profesjonalnymi jednostkami obliczeniowymi Tesla firmy nvidia. Wyniki eksperymentu algorytmów z reprezentacją binarną: Opisy implementacji: BinST_1 - opisywana implementacja jednowątkowa tylko w Java. GPU_4 - implementacja wykorzystująca OpenCL na GPU CPU_4 - implementacja wykorzystująca OpenCL na CPU Liczba za opisem oznacza wyrównanie do najbliżej ilości klastrów (charów 16bitowych) atrybutów podzielnych przez nią. Wyrównanie wynika z faktu, iż w OpenCL dokonaliśmy dodatkowej optymalizacji: nie porównujemy 16 a 64 bity na raz. Zbiór mushroom: 4 3,5 3 2,5 2 1,5 1 0,5 0 30% 40% 50% 60% BinST_1 0,796 0,172 0,047 0,031 GPU_4 3,654 0,894 0,265 0,094 CPU_4 1,084 0,265 0,093 0,046 Wydajnośd na tak małym zbiorze danych (kilkaset kilobajtów) jest nieosiągalna. Więcej czasu trwała inicjalizacja oraz synchronizacja całego środowiska OpenCL niż realizacja w Javie.
Time [s] Time [s] Zbiór accidents: 120 100 80 60 40 20 0 60% 70% 80% 90% BinST_1 72,89 19,29 5,3 0,826 GPU_4 104,84 50,56 14,4 2,683 CPU_4 23,93 6,583 2,04 0,531 Zbiór ten jest nadal za mały do wykorzystania potencjału obliczeniowego GPU (posiada niecałe 500 atrybutów). Wydajnośd bieżącej implementacji niestety silnie zależy od ilości atrybutów. Widad jednakże, że wykonanie na CPU dało już prawie 4x przyspieszenie zgodnie z oczekiwaniem. Zbiór pumsb_star: 70 60 50 40 30 20 10 0 50% 60% 70% BinST_1 64,67 14,78 3,42 GPU_4 38,67 8,79 3,3 CPU_4 12,23 2,8 0,639 Na tym zbiorze, który posiada około 8000 atrybutów widzimy, jak kod OpenCL na CPU uzyskał ponad 5x przyspieszenie. Kod na GPU był już szybszy od wykonania w Javie.
Uwagi: W raporcie zostały wykorzystane materiały dostępne w: Projekcie gpuminer Dokumentacji nvidii do OpenCL/CUDA