Cilk
Paradygmat | imperatyw ( proceduralny ), ustrukturyzowany , równoległy |
---|---|
Zaprojektowany przez | Laboratorium Informatyki MIT |
Deweloper | Intel |
Po raz pierwszy pojawiły się | 1994 |
Dyscyplina pisania | statyczny , słaby , oczywisty |
Strona internetowa | |
Dialekty | |
Cilk++, Cilk Plus, OpenCilk | |
Pod wpływem | |
C | |
Pod wpływem | |
OpenMP 3.0, Rayon ( biblioteka Rust ) |
Zaprojektowany przez | MIT |
---|---|
Deweloper | MIT |
Po raz pierwszy pojawiły się | 2020 |
Wersja stabilna | 2.0.1 / 3 września 2022 r
|
system operacyjny | Uniksopodobny , macOS |
Licencja | MIT |
Strona internetowa |
Zaprojektowany przez | Intel |
---|---|
Deweloper | Intel |
Po raz pierwszy pojawiły się | 2010 |
Wersja stabilna | 1.2 / 9 września 2013
|
Rozszerzenia nazw plików | (Tak samo jak C lub C++) |
Strona internetowa | http://cilkplus.org/ |
Cilk , Cilk++ , Cilk Plus i OpenCilk to języki programowania ogólnego przeznaczenia przeznaczone do wielowątkowych obliczeń równoległych . Oparte są na C i C++ , które rozszerzają o konstrukcje wyrażające pętle równoległe oraz idiom fork-join .
Pierwotnie opracowany w latach 90. w Massachusetts Institute of Technology (MIT) w grupie Charlesa E. Leisersona , Cilk został później skomercjalizowany jako Cilk ++ przez firmę spinoff, Cilk Arts. Firma ta została następnie przejęta przez firmę Intel , która zwiększyła kompatybilność z istniejącym kodem C i C++, nazywając wynik Cilk Plus. Po tym, jak Intel przestał wspierać Cilk Plus w 2017 roku, MIT ponownie rozwija Cilk w formie OpenCilk.
Historia
MIT Cilk
Język programowania Cilk wyrósł z trzech oddzielnych projektów w MIT Laboratory for Computer Science:
- Teoretyczne prace nad szeregowaniem aplikacji wielowątkowych.
- StarTech - program do gry w szachy równoległe zbudowany do działania na maszynie łączącej Thinking Machines Corporation model CM-5.
- PCM/Threaded-C – oparty na C pakiet do planowania wątków w stylu przekazywania kontynuacji w CM-5
W kwietniu 1994 roku te trzy projekty zostały połączone i ochrzczone „Cilk”. Nazwa Cilk nie jest akronimem, ale aluzją do „ładnych nici” ( silk ) i języka programowania C. Kompilator Cilk-1 został wydany we wrześniu 1994 roku.
Oryginalny język Cilk był oparty na ANSI C , z dodatkiem specyficznych dla Cilk słów kluczowych w celu sygnalizacji równoległości. Kiedy słowa kluczowe Cilk są usuwane z kodu źródłowego Cilk, rezultatem zawsze powinien być prawidłowy program C, zwany serial elision (lub C elision ) pełnego programu Cilk, z taką samą semantyką jak program Cilk działający na pojedynczym procesorze. Pomimo kilku podobieństw, [ które? ] Cilk nie jest bezpośrednio powiązany z Concurrent C firmy AT&T Bell Labs.
Cilk został zaimplementowany jako tłumacz do C, ukierunkowany na GNU C Compiler (GCC). Ostatnia wersja, Cilk 5.4.6, jest dostępna w MIT Computer Science and Artificial Intelligence Laboratory (CSAIL), ale nie jest już obsługiwana.
Wizytówką możliwości Cilk był program do gry w szachy równoległe Cilkchess, który zdobył kilka nagród w szachach komputerowych w latach 90., w tym Otwarte Holenderskie Mistrzostwa w Szachach Komputerowych w 1996 roku.
Cilk Arts i Cilk++
przed ok. 2006 rynek Cilk był ograniczony do komputerów o wysokiej wydajności. Pojawienie się procesorów wielordzeniowych w głównym nurcie komputerów oznaczało, że każdego roku wysyłano setki milionów nowych komputerów równoległych. Cilk Arts powstało, aby wykorzystać tę szansę: w 2006 roku Leiserson uruchomił Cilk Arts, aby stworzyć i wprowadzić na rynek nowoczesną wersję Cilk, która obsługuje komercyjne potrzeby nadchodzącej generacji programistów. Firma zamknęła rundę finansowania przedsięwzięć typu A w październiku 2007 r., a jej produkt, Cilk++ 1.0, został wprowadzony na rynek w grudniu 2008 r.
Cilk++ różni się od Cilk na kilka sposobów: wsparcie dla C++, wsparcie dla pętli i hiperobiektów – nowa konstrukcja zaprojektowana do rozwiązywania problemów związanych z wyścigiem danych stworzonym przez równoległy dostęp do zmiennych globalnych. Cilk++ było oprogramowaniem zastrzeżonym . Podobnie jak jego poprzednik, został zaimplementowany jako kompilator Cilk-to-C++. Obsługuje Microsoft i GNU.
Intel Cilk Plus
31 lipca 2009 r. firma Cilk Arts ogłosiła na swojej stronie internetowej, że jej produkty i zespół inżynierów są teraz częścią firmy Intel Corp. Na początku 2010 r. witryna Cilk pod adresem www.cilk.com
zaczęła przekierowywać do witryny firmy Intel , oryginalna witryna Cilk nie jest już rozpoznawana jako host). Firmy Intel i Cilk Arts zintegrowały i udoskonaliły tę technologię, co zaowocowało wydaniem we wrześniu 2010 r. Intel Cilk Plus . Cilk Plus przyjmuje uproszczenia zaproponowane przez Cilk Arts w Cilk++, aby wyeliminować potrzebę stosowania kilku oryginalnych słów kluczowych Cilk, jednocześnie dodając możliwość odradzania funkcji i radzenia sobie ze zmiennymi zaangażowanymi w operacje redukcji. Cilk Plus różni się od Cilk i Cilk++ dodaniem rozszerzeń tablic, włączeniem do komercyjnego kompilatora (firmy Intel) oraz kompatybilnością z istniejącymi debuggerami.
Cilk Plus został po raz pierwszy zaimplementowany w kompilatorze Intel C++ wraz z wydaniem kompilatora Intel w Intel Composer XE 2010. [ Potrzebne źródło ] Implementacja typu open source ( na licencji BSD ) została wniesiona przez firmę Intel do GNU Compiler Collection (GCC), która dostarczona obsługa Cilk Plus w wersji 4.9, z wyjątkiem słowa kluczowego _Cilk_for , które zostało dodane w GCC 5.0. W lutym 2013 roku Intel ogłosił widelec Clang z obsługą Cilk Plus. Intel Compiler, ale nie implementacje open source, jest wyposażony w wykrywacz wyścigów i analizator wydajności.
Intel później zaprzestał tego, zalecając swoim użytkownikom przejście zamiast tego na korzystanie z OpenMP lub własnej biblioteki TBB firmy Intel do ich potrzeb w zakresie programowania równoległego.
Różnice między wersjami
W oryginalnej implementacji MIT Cilk pierwszym słowem kluczowym Cilk jest w rzeczywistości cilk
, które identyfikuje funkcję napisaną w Cilk. Ponieważ procedury Cilk mogą bezpośrednio wywoływać procedury C, ale procedury C nie mogą bezpośrednio wywoływać ani tworzyć procedur Cilk, to słowo kluczowe jest potrzebne do odróżnienia kodu Cilk od kodu C. Cilk Plus usuwa to ograniczenie, jak również cilk
, więc funkcje C i C++ mogą wywoływać kod Cilk Plus i odwrotnie.
Wycofanie Cilk Plus
W maju 2017 roku wydano GCC 7.1 i oznaczono obsługę Cilk Plus jako przestarzałą. Sam Intel ogłosił we wrześniu 2017 r., Że wycofa Cilk Plus wraz z wydaniem w 2018 r. Intel Software Development Tools. W maju 2018 roku wydano GCC 8.1 z usuniętą obsługą Cilk Plus.
OpenCilk
Po tym, jak Intel wycofał obsługę Cilk Plus, MIT zajął się rozwojem Cilk w implementacji OpenCilk, koncentrując się na rozwidleniu LLVM / Clang, obecnie określanym jako „Tapir”. OpenCilk pozostaje w dużej mierze kompatybilny z Intel Cilk Plus. Jego pierwsza stabilna wersja została wydana w marcu 2021 roku.
Funkcje językowe
Zasada leżąca u podstaw projektowania języka Cilk polega na tym, że programista powinien być odpowiedzialny za ujawnienie równoległości, identyfikując elementy, które można bezpiecznie wykonać równolegle; należy wtedy pozostawić środowisku wykonawczemu, zwłaszcza programowi planującemu , podjęcie decyzji podczas wykonywania, jak faktycznie podzielić pracę między procesory. Ponieważ te obowiązki są rozdzielone, program Cilk może działać bez przepisywania na dowolnej liczbie procesorów, w tym na jednym.
Równoległość zadań: odradzanie i synchronizacja
Głównym dodatkiem Cilk do C są dwa słowa kluczowe, które razem umożliwiają pisanie programów równoległych zadań.
- Słowo kluczowe spawn poprzedzające wywołanie funkcji ( spawn f(x) ) wskazuje, że wywołanie funkcji ( f(x) ) może bezpiecznie działać równolegle z następującymi po nim instrukcjami w funkcji wywołującej. Należy zauważyć, że program planujący nie jest zobowiązany do równoległego uruchamiania tej procedury; słowo kluczowe jedynie ostrzega program planujący, że może to zrobić.
- Instrukcja sync wskazuje, że wykonanie bieżącej funkcji nie może być kontynuowane, dopóki wszystkie wywołania wcześniej wywołanej funkcji nie zostaną zakończone. Jest to przykład barierowej .
(W Cilk Plus słowa kluczowe są zapisywane jako _Cilk_spawn i _Cilk_sync lub cilk_spawn i cilk_sync , jeśli uwzględniono nagłówki Cilk Plus.)
Poniżej znajduje się rekurencyjna implementacja funkcji Fibonacciego w Cilk, z równoległymi wywołaniami rekurencyjnymi, która demonstruje słowa kluczowe spawn i sync . Oryginalny Cilk wymagał, aby każda funkcja wykorzystująca te funkcje była opatrzona słowem cilk , które zniknęło od Cilk Plus. (Kod programu Cilk nie jest numerowany; numery zostały dodane tylko po to, aby ułatwić śledzenie dyskusji.)
cilk int fib ( int n ) { if ( n < 2 ) { return n ; } else { int x , y ; x = spawn fib ( n - 1 );
y = spawn fib ( n - 2 );
synchronizacja ;
zwróć x + y ; } }
Gdyby ten kod został wykonany przez pojedynczy procesor w celu określenia wartości fib(2) , ten procesor utworzyłby ramkę dla fib(2) i wykonałby linie od 1 do 5. W linii 6 utworzyłby spacje w ramce do trzymaj wartości x i y . W linii 8 procesor musiałby zawiesić bieżącą ramkę, utworzyć nową ramkę w celu wykonania procedury fib(1) , wykonać kod tej ramki aż do osiągnięcia instrukcji return, a następnie wznowić ramkę fib(2) z wartość fib(1) umieszczona w zmiennej x fib(2) . W następnym wierszu musiałby się ponownie zawiesić, aby wykonać fib(0) i umieścić wynik w zmiennej y fib (2) .
gdy kod jest wykonywany na maszynie wieloprocesorowej , wykonanie przebiega inaczej. Jeden procesor rozpoczyna wykonywanie fib(2) ; kiedy jednak dociera do linii 8, słowo spawn modyfikujące wywołanie fib(n-1) mówi procesorowi, że może bezpiecznie przekazać zadanie drugiemu procesorowi: ten drugi procesor może utworzyć ramkę dla fib(1) , wykonać jego kod i zapisz jego wynik w ramce fib(2) po jego zakończeniu; pierwszy procesor kontynuuje wykonywanie kodu fib(2) w tym samym czasie. Procesor nie jest zobowiązany do przypisania zrodzionej procedury w innym miejscu; jeśli maszyna ma tylko dwa procesory, a drugi jest nadal zajęty fib(1) , kiedy procesor wykonujący fib(2) dochodzi do wywołania procedury, pierwszy procesor zawiesi fib(2) i sam wykona fib(0) , jak tak by było, gdyby był to jedyny procesor. Oczywiście, jeśli dostępny jest inny procesor, zostanie on uruchomiony, a wszystkie trzy procesory wykonają jednocześnie oddzielne ramki.
(Poprzedni opis nie jest całkowicie dokładny. Chociaż powszechna terminologia dotycząca Cilk odnosi się do procesorów podejmujących decyzję o przekazywaniu pracy innym procesorom, w rzeczywistości to program planujący przypisuje procesorom procedury do wykonania, używając polityki zwanej work- kradzież , opisana później).
Gdyby procesor wykonujący fib(2) wykonał linię 13, zanim oba pozostałe procesory zakończyły swoje ramki, wygenerowałby niepoprawny wynik lub błąd; fib(2) próbowałby dodać wartości przechowywane w x i y , ale brakowałoby jednej lub obu tych wartości. To jest cel słowa sync , które widzimy w wierszu 11: mówi ono procesorowi wykonującemu ramkę, że musi zawiesić swoje własne wykonywanie, dopóki nie powrócą wszystkie wywołane przez niego wywołania procedur. Kiedy fib(2) może przejść poza instrukcję sync w wierszu 11, może to być spowodowane tym, że fib(1) i fib(0) zakończyły i umieściły swoje wyniki w x i y , dzięki czemu można bezpiecznie wykonywać obliczenia na tych wyniki.
Powyższy przykład kodu wykorzystuje składnię Cilk-5. Oryginalny Cilk (Cilk-1) używał raczej innej składni, która wymagała programowania w jawnym stylu przekazywania kontynuacji , a przykłady Fibonacciego wyglądają następująco:
fib wątku ( cont int k , int n ) { if ( n < 2 ) { send_argument ( k , n ); } else { cd int x , y ; spawn_następna suma ( k , ? x , ? y ); spawn fib ( x , n - 1 ); spawn fib ( y , n - 2 ); } } suma wątków ( cont int k , int x , int y ) { send_argument ( k , x + y ); }
Wewnątrz przypadku rekurencyjnego fib słowo kluczowe spawn_next wskazuje na utworzenie wątku następcy (w przeciwieństwie do wątków potomnych tworzonych przez spawn ), który wykonuje podprogram sumy po odczekaniu na wypełnienie zmiennych kontynuacji x i y przez rekurencję połączenia. Przypadek podstawowy i sum używają operacji send_argument(k, n) , aby ustawić ich zmienną kontynuacji k na wartość n , skutecznie „zwracając” wartość do wątku następcy.
Wloty
Dwa pozostałe słowa kluczowe Cilk są nieco bardziej zaawansowane i dotyczą wykorzystania wlotów . Zwykle, gdy procedura Cilk jest wywoływana, może ona zwrócić swoje wyniki do procedury nadrzędnej tylko poprzez umieszczenie tych wyników w zmiennej w ramce rodzica, ponieważ w przykładzie przypisaliśmy wyniki wywołań naszej wywołanej procedury do x
i y
.
Alternatywą jest użycie wlotu. Wlot jest wewnętrzną funkcją procedury Cilk, która obsługuje wyniki zrodzonego wywołania procedury w miarę ich powrotu. Jednym z głównych powodów używania wlotów jest to, że wszystkie wloty procedury mają gwarancję, że będą działać atomowo względem siebie i procedury nadrzędnej, unikając w ten sposób błędów, które mogłyby wystąpić, gdyby wielokrotne procedury zwracające próbowały zaktualizować te same zmienne w ramkę nadrzędną w tym samym czasie.
- Słowo kluczowe
inlet
identyfikuje funkcję zdefiniowaną w procedurze jako wlot. - Słowo kluczowe
abort
może być użyte tylko wewnątrz wlotu; informuje program planujący, że wszelkie inne procedury, które zostały uruchomione przez procedurę nadrzędną, można bezpiecznie przerwać.
Wloty zostały usunięte, gdy Cilk stał się Cilk++ i nie występują w Cilk Plus.
Pętle równoległe
Cilk++ dodał dodatkową konstrukcję, pętlę równoległą, oznaczoną cilk_for w Cilk Plus. Te pętle wyglądają
void loop ( int * a , int n ) { #pragma cilk rozmiar ziarna = 100 // opcjonalnie
0 cilk_for ( int i = ; i < n ; i ++ ) {
a [ i ] = f ( a [ i ]); } }
To implementuje idiom mapy równoległej : treść pętli, tutaj wywołanie f , po którym następuje przypisanie do tablicy a , jest wykonywane dla każdej wartości i od zera do n w nieokreślonej kolejności. Opcjonalna pragma „rozmiaru ziarna” określa zgrubne: każda podtablica zawierająca sto lub mniej elementów jest przetwarzana sekwencyjnie. Chociaż specyfikacja Cilk nie określa dokładnego zachowania konstrukcji, typową implementacją jest rekurencja typu „dziel i zwyciężaj”, tak jakby programista napisał
0
static void recursion ( int * a , int start , int end ) { if ( end - start <= 100 ) { // 100 tutaj to rozmiar ziarna. for ( int ja = początek ; ja < koniec ; ja ++ ) { za [ ja ] = fa ( za [ ja ]); } } else { int punkt środkowy = początek + ( koniec - początek ) / 2 ; rekurencja cilk_spawn ( a , początek , punkt środkowy ); rekurencja ( a , punkt środkowy , koniec ); cilk_sync ; } } pusta pętla ( int * a , int n ) { rekurencja ( a , , n ); }
Powody generowania programu typu „dziel i zwyciężaj” zamiast oczywistej alternatywy, pętli, która odradza się i wywołuje ciało pętli jako funkcję, leżą zarówno w obsłudze rozmiaru ziarna, jak i wydajności: wykonanie całego tarła w jednym zadaniu powoduje obciążenie równoważenie wąskiego gardła.
Przegląd różnych konstrukcji pętli równoległych na HPCwire wykazał, że konstrukcja cilk_for jest dość ogólna, ale zauważono, że specyfikacja Cilk Plus nie określa, że jej iteracje muszą być niezależne od danych, więc kompilator nie może automatycznie wektoryzować pętli cilk_for . W przeglądzie zwrócono również uwagę na fakt, że redukcje (np. sumy na tablicach) wymagają dodatkowego kodu.
Reduktory i hiperobiekty
Cilk++ dodał rodzaj obiektów zwanych hiperobiektami , które pozwalają wielu pasmom dzielić stan bez warunków wyścigu i bez używania jawnych blokad. Każda nić ma widok na hiperobiekt, którego może używać i aktualizować; podczas synchronizacji nici widoki są łączone w sposób określony przez programistę.
Najpopularniejszym typem hiperobiektu jest reduktor, który odpowiada klauzuli redukcji w OpenMP lub algebraicznemu pojęciu monoidu . Każdy reduktor ma element tożsamości i operację asocjacyjną , która łączy dwie wartości. Archetypowym reduktorem jest sumowanie liczb: elementem tożsamości jest zero, a operacja redukcji asocjacyjnej oblicza sumę. Ten reduktor jest wbudowany w Cilk++ i Cilk Plus:
0
0
// Oblicz ∑ foo(i) dla i od 0 do N, równolegle. cilk :: reducer_opadd < float > wynik ( ); cilk_for ( int i = ; i < N ; i ++ ) wynik += foo ( ja );
Inne reduktory mogą być używane do konstruowania połączonych list lub łańcuchów, a programiści mogą definiować niestandardowe reduktory.
Ograniczeniem hiperobiektów jest to, że zapewniają one jedynie ograniczoną determinację . Burckhardta i in. zwróć uwagę, że nawet reduktor sumy może skutkować zachowaniem niedeterministycznym, pokazując program, który może generować 1 lub 2 w zależności od kolejności planowania:
0
0
void add1 ( cilk :: reducer_opadd < int > & r ) { r ++ ; } // ... cilk :: reducer_opadd < int > r ( ); cilk_spawn dodaj1 ( r ); jeśli ( r == ) { r ++ ; } cilk_sync ; wyjście ( r . get_value ());
Notacja tablicowa
Intel Cilk Plus dodaje notację, aby wyrazić operacje wysokiego poziomu na całych tablicach lub sekcjach tablic ; np. axpy , która jest zwykle zapisywana
0
// y ← α x + y void axpy ( int n , float alpha , const float * x , float * y ) { for ( int i = ; i < n ; i ++ ) { y [ ja ] += alfa * x [ ja ]; } }
można w Cilk Plus wyrazić jako
y[0:n] += alfa * x[0:n];
Ta notacja pomaga kompilatorowi efektywnie wektoryzować aplikację. Intel Cilk Plus umożliwia równoległe wykonywanie operacji C/C++ na wielu elementach tablicy, a także zapewnia zestaw wbudowanych funkcji, których można używać do wykonywania wektorowych przesunięć, obrotów i redukcji. Podobna funkcjonalność istnieje w Fortranie 90 ; Cilk Plus różni się tym, że nigdy nie przydziela tymczasowych tablic, więc użycie pamięci jest łatwiejsze do przewidzenia.
Funkcje elementarne
W Cilk Plus funkcja elementarna jest zwykłą funkcją, którą można wywołać albo na argumentach skalarnych, albo równolegle na elementach tablicy. Są podobne do funkcji jądra OpenCL .
#pragma simd
Ta pragma daje kompilatorowi uprawnienia do wektoryzacji pętli nawet w przypadkach, w których automatyczna wektoryzacja może się nie powieść. Jest to najprostszy sposób na ręczne zastosowanie wektoryzacji.
Kradzież pracy
Harmonogram Cilk wykorzystuje politykę zwaną „kradzieżą pracy” w celu efektywnego podziału wykonywania procedur między wiele procesorów. Ponownie, najłatwiej jest to zrozumieć, jeśli najpierw przyjrzymy się, jak kod Cilk jest wykonywany na maszynie jednoprocesorowej.
Procesor utrzymuje stos , na którym umieszcza każdą ramkę, którą musi zawiesić, aby obsłużyć wywołanie procedury. Jeśli wykonuje fib(2) i napotka rekurencyjne wywołanie fib(1) , zapisze stan fib(2) , w tym jego zmienne i miejsce, w którym kod zawiesił wykonywanie, i umieści ten stan na stosie. Nie usunie stanu zawieszenia ze stosu i nie wznowi wykonywania, dopóki wywołanie procedury, które spowodowało zawieszenie, oraz wszystkie procedury wywołane kolejno przez tę procedurę nie zostaną w pełni wykonane.
W przypadku wielu procesorów rzeczy oczywiście się zmieniają. Każdy procesor nadal ma stos do przechowywania ramek, których wykonanie zostało zawieszone; jednak te stosy bardziej przypominają deques , ponieważ stany zawieszenia można usunąć z dowolnego końca. Procesor nadal może usuwać stany ze swojego stosu tylko z tego samego końca, na którym je umieszcza; jednak każdy procesor, który obecnie nie działa (zakończył swoją własną pracę lub nie został jeszcze przydzielony), wybierze losowo inny procesor za pośrednictwem programu planującego i spróbuje „ukraść” pracę z przeciwnego końca ich stosu - stany zawieszenia, które kradnący procesor może następnie rozpocząć wykonywanie. Stany, które zostały skradzione, to stany, z których skradziony procesor mógłby wykonać się jako ostatni.
Zobacz też
- Wysyłka Grand Central
- Kolekcje Intel Concurrent (CnC)
-
Bloki Intel Parallel Building Blocks (PBB) Bloki
- Intel Array Building Blocks (ArBB)
- Studio równoległe Intela
- NESL
- OpenMP
- Równoległe obliczenia
- System programowania równoległego Sieve C++
- Bloki konstrukcyjne gwintowania (TBB)
- Ujednolicony równoległy C
Linki zewnętrzne
- Oficjalna strona OpenCilk
- Witryna Cilk Plus firmy Intel
- Witryna projektu Cilk w MIT
- Arch D. Robison, „Cilk Plus: Obsługa języków dla równoległości wątków i wektorów” oraz „Programowanie równoległe z Cilk Plus” , 16 lipca 2012 r.