Programowanie równoległe – Parallel Extensions

Transkrypt

Programowanie równoległe – Parallel Extensions
Programowanie równoległe – Parallel Extensions
W. Grześkowiak
Instytut Informatyki, Wydział Elektroniki i Technik Informacyjnych, Politechnika Warszawska,
ul. Nowowiejska 15/19, 00-665 Warszawa, Polska
[email protected]
Streszczenie. Parallel Extensions to zbiór
narzędzi przeznaczonych do programowania
równoległego, wprowadzony w czwartej wersji
platformy .NET Framework firmy Microsoft.
Zaproponowano nowy model programowania,
oparty na fizycznej równoległości wykonywanych
zadań. Takie podejście pozwala na pełne
wykorzystanie
aktualnie
produkowanych
procesorów wielordzeniowych.
umieszczać w jednym układzie krzemowym, a jak
wiadomo ich liczba decyduje pośrednio
o częstotliwości pracy procesora. Im więcej
tranzystorów umieści się w jednym układzie, tym
większą częstotliwość można osiągnąć. Z czasem
inżynierowie doszli do rozmiarów rzędu 32 nm. [2],
czyli bardzo blisko rozmiarów atomu (10-10m).
Pomimo faktu że produkcja w coraz to mniejszym
wymiarze jest bardzo trudna, Intel aktualnie
inwestuje w technologię 22 nm [3]. Procesory
wytworzone w tym rozmiarze technologicznym nie
trafią jednak na rynek wcześniej niż w 2011 roku.
Na problem zwiększania częstotliwości od
dawna szukano alternatywy chociaż odpowiedz
wydawała się w miarę oczywista. Skoro nie można
zwiększać prędkości rdzeni procesorów, to należy
umieszczać ich więcej w jednym układzie. Takie
rozwiązanie jako pierwszy wprowadził Intel
tworząc rodzinę procesorów Core Duo [4].
Ciekawostką jest fakt, że funkcjonowały także
jednostki Core Solo [5], które tak naprawdę były
ukrytymi jednostkami Duo, z pracującym jednym
rdzeniem. Zastosowano taką praktykę ponieważ
proces technologiczny w którym produkuje się
procesory jest bardzo skomplikowany i często
dochodzi do uszkodzeń podzespołów. Intel
stwierdził, że jeśli usterka nastąpi w jednym
rdzeniu, to nie warto wyrzucać całego układu do
kosza. Wyłączano uszkodzony rdzeń i sprzedawano układ pod inna nazwą.
Intel z czasem wprowadził na rynek jednostki 4
rdzeniowe, a ostatnie z nich (np. model Intel Core
1. Wstęp. Kiedy należy ocenić wydajność
komputera, pierwszą rzeczą na którą powinno się
zwrócić uwagę jest częstotliwość taktowania
procesora. Większa liczba może oznaczać
większą wydajność. Producenci układów nie
mogli jednak przyśpieszać swoich procesorów
w nieskończoność. Problemem okazała się
technologia wytwarzania i rozmiary otrzymywanych tranzystorów bliskie rozmiarom atomów.
Powstał problem który należało rozwiązać w inny
sposób. Tak powstała idea procesorów wielordzeniowych i programowana kierowanego na te
środowisko, określanego mianem programowania
równoległego.
2. Rynek procesorów. W latach 90 w półświatku
informatyków głośno krążyło prawo sformujłowane założyciela firmy Intel - Gordona Moora
[1]. Mówiło ono że: Liczba tranzystorów
w układzie elektrycznym podwaja się co 18-24
miesiące. Prawo to w tamtych czasach zgadzało się
z rzeczywistością, postęp techniczny był bardzo
gwałtowny, coraz więcej tranzystorów można było
1
7 950 [6]) wyposażone w technologie HT [7]
pozwalają na uruchamianie do 8 wątków
równolegle.
Na rynku pojawiły się więc procesory
wielordzeniowe, co zyskano? Aby odpowiedzieć
na to pytanie najlepiej posłużyć się wypowiedzią
Dana Reeda z Microsoftu „Różnica jest taka jak
między szybkim sportowym autem, a autobusem
szkolnym. Pierwszy szybko przewiezie dwie osoby,
a drugi, choć trochę wolniej – czterdzieści”.
Zauważyć należy, że
rdzenie jednostek
wielordzeniowych nie są tak samo szybkie jak
rdzenie z procesorów jednordzeniowych, to jest
ciągle jedna płytka krzemu i ciągle bardzo mało
miejsca na umieszczenie wszystkich modułów
procesora.
Analitycy z firmy Forrester Research
przewidują, że już w 2012 roku zbudowane
zostaną procesory wyposażone w 64 rdzenie. Dan
Reed ostrzega
„Już niedługo zabraknie
programistów z doświadczeniem w tworzeniu
aplikacji
wykorzystujących
przetwarzanie
równoległe.”. „To już ostatni dzwonek, aby
przekonać młodych programistów o wartości
przetwarzania równoległego” – dodaje.
Obliczenia równoległe są realizowane fizycznie
jednocześnie, na wielu jednostkach obliczeniowych. Obliczenia równoległe mogą być
wykonywane co najmniej na dwóch jednostkach
obliczeniowych. Rozważając dwa zadania
z poprzedniego
przykładu,
możliwość
wykonywania ich równolegle wymaga dwóch
jednostek obliczeniowych (np. dwóch procesorów
lub dwóch rdzeni).
4. Parallel Extensions. Opisane na początku
wydarzenia z rynku procesorów miały miejsce na
początku tego dziesięciolecia. Już wtedy firma
Microsoft
widziała
w
procesorach
wielordzeniowych duży potencjał. Oddział
badawcza firmy, Microsoft Research [8], rozpoczął
wtedy pacę nad narzędziem które pozwoliłoby
programistą w łatwy i przyjemny sposób tworzyć
aplikację wykorzystujące możliwość równoległego
wykonywania kodu. W ten sposób powstała
biblioteka TPL (Task Parallel Library), która
stanowi fundament Parallel Extensions. Biblioteka
ta jest częścią czwartej wersji platformy .NET
Framework [9].
5. Równoległość w praktyce. W rozdziale tym
zostanie zaprezentowany przykład praktycznego
wykorzystania zalety przetwarzania równoległego.
Do tego celu posłużono się aplikacją
demonstracyjną, dostarczoną wraz z nową wersją
platformy. Aplikacja ta generuje dynamiczne sceny
3D wykorzystując czasochłonną technikę śledzenia
promieni, z ang. Ray Tracing. Sposób generowania
obrazu zaimplementowano dwojako, w pierwszym
wypadku
wykorzystano
typowe
podejście
sekwencyjne, w drugim wykorzystano bibliotekę
Parallel Extensions.
Maszyna testowa to: procesor Intel Core Quad
(4x 2.2GHz), 1GB pamięci RAM, system
operacyjny Windows Server 2008 zwirtualizowany
przy pomocy technologii Hyper-V.
Przy uruchomieniu aplikacji z sekwencyjną
generacją obrazu, menadżer zadań systemu
Windows pokazał zużycie procesora w granicach
25-26%, co w przypadku czterech rdzeni świadczy
o tym że wtkorzystywano tylko jeden z nich.
3. Równoległość, a współbieżność.
Zanim
zaprezentowane zostanie rozwiązanie Parallel
Extensions, na potrzeby artykułu zostaną
rozróżnione
dwa
aspekty
programowania
równoległego, które nazwane zostaną współbieżnością i równoległością.
Obliczenia są współbieżne, jeżeli kolejne do
wykonanie obliczenie, rozpocznie się wcześniej
niż skończy się poprzednie. Obliczenia
współbieżne wykonywane są jednej jednostce
obliczeniowej (np. procesorze), pracującej
z podziałem czasu. Aby łatwiej zobrazować
współbieżność, należy zdefiniować dwa zadania,
które powinny wykonać się współbieżnie –
Zadanie 1 oraz Zadanie 2. Podział czasu polega na
udostępnieniu zasobów jednostki obliczeniowej na
określony czas na przemian jednemu i drugiemu
zadaniu. W każdym cyklu jednostka wykona
jedynie część zadania i przełączy się na drugie.
2
Współbieżnie
Równolegle
Ilość
FPS
0,7
2,8
może wyglądać podobnie do funkcji przedstawionej na listingu 2.
Zużycie
procesora
26 %
100%
using
using
using
using
Tabela 1 - Obciążenie procesora aplikacją Ray Tracing
Uruchomienie aplikacji w trybie równoległym
(z
wykorzystaniem
Parallel
Extensions)
spowodowało wzrost obciążenia procesora do
100%, dzięki czemu wykorzystywano pełną moc
dostępnej maszyny testowej. Liczba generowanych
klatek na sekundę FPS (z ang. frame per second)
w drugim przypadku była 4 razy większa niż
w przypadku pierwszym (Tabela 1). Różnica
w kodzie
algorytmu
generowania
obrazu
równolegle, różni się od wersji synchronicznej
jedynie jedna instrukcją. Biblioteka Parallel
Extsnions wprowadza wprowadza bardzo wysoka
abstrakcję
równoległości,
dzięki
czemu
implementacja takiego podejścia jest bardzo łatwa.
System;
System.Diagnostics;
System.Threading;
System.Threading.Tasks;
namespace Tree
{
class Program
{
static void Main(string[] args)
{
TNode root = TNode.CreateTree(9, 1);
Stopwatch watch = Stopwatch.StartNew();
WalkTree(root);
Console.WriteLine(
String.Format("Elapsed = {0}",
watch.ElapsedMilliseconds));
}
public static void WalkTree(TNode node)
{
/* To co musimy zaimplementować. */
}
public static int ProcessItem(int value)
{
Thread.SpinWait(4000000);
return value;
}
6. Imperatywny paralelizm. Przykłady programowania równoległego zostaną omówione na
podstawie prezentacji Daniela Motha „Parallel
Programming for Managed Developers with the
Next Version of Microsoft Visual Studio” [10]
przedstawionej na corocznej konferencji dla
programistów związanych z technologiami
Microsoftu, Professional Developer Conference.
Konferencja ta odbyła się w październiku 2008
roku.
Przykład polega na implementacji metody
pewnej aplikacji, której celem jest wykonanie
czasochłonnych obliczeń na każdym węźle
dostarczonego drzewa binarnego. Obliczenia na
węzłach są niezależne względem siebie. Kod
aplikacji prezentuje listing 1, ciało metody
przechodzącej po drzewie jest puste. Zadaniem jest
napisanie tej metody w sposób minimalizujący
czas potrzebny do przetworzenia całego drzewa
o wysokości 9 węzłów.
Pierwszym pomysłem na rozwiązaniem tego
problemu jest rekurencja. W ten sposób stworzono
drzewo więc w podobny sposób można je
przeglądać. Funkcja wykorzystująca rekurencje
}
class TNode
{
public TNode LeftNode { get; set; }
public TNode RightNode { get; set; }
public int Value { get; set; }
public static TNode CreateTree(
int deep, int start)
{
TNode root = new TNode();
root.Value = start;
if (deep > 0)
{
root.LeftNode = CreateTree(
deep - 1, start + 1);
root.RightNode = CreateTree(
deep - 1, start + 1);
}
return root;
}
}
}
Listing 1- Kod aplikacji wykonującej obliczenia
na węzłach drzewa
3
Rozwiązania będą testowe na maszynie
z procesorem Intel Core 2 Duo T7100 (2x 1,8GHz),
4GB pamięci RAM oraz systemem operacyjnym
Windows 7 64bit.
wersji 3.5, użyto także metody Join, aby
rozwiązanie było funkcjonalnie porównywalne
z rekurencją (czyli operacje na węzłach będą się
odbywać w tej samej kolejności). Zmierzony czas
to 14,116 s., czyli prawie 10 sekund szybciej.
Podczas testowania uruchomiony menadżer zadań
pokazywał stuprocentowe wykorzystanie mocy
procesora, oto właśnie chodziło. Rozwiązanie
wydaje się poprawne jednak należy dokładnie
przemyśleć, takie podejście. Podczas obliczeń
powstaje tyle wątków ile jest węzłów na których
należy wykonać operację. W idealnym drzewie
binarnym o wysokości 9 jest 511 węzłów,
każdorazowe tworzenie wątku jest operacją bardzo
czasochłonna ponieważ za każdym razem należy
odwołać się do systemu, który przydzieli
odpowiednie zasoby m.in. pamięć, licznik
rozkazów itp. Ponadto przełączanie pomiędzy tak
dużą liczbą wątków jest również czasochłonne. Jak
można zrobić to lepiej? Tutaj z pomocą przychodzi
Parallel Extensions, do takich właśnie celów
stworzono
tą
bibliotekę.
Rozwiązanie
wykorzystujące nowe narzędzia i struktury danych,
które zostaną opisane w dalszej części artykułu.
Listing 4 prezentuje implementacje rozwiązania
przy użyciu tej biblioteki.
public static void WalkTree(TNode node)
{
if (node == null)
return;
WalkTree(node.LeftNode);
WalkTree(node.RightNode);
ProcessItem(node.Value);
}
Listing 2 - Funkcja przeglądania drzewa
wykorzystująca rekurencje
Czas uzyskany przez rozwiązanie rekurencyjne
to 24,998 s.. Zużycie procesora w tym teście
wynosiło 50% (taką wartość pokazywał podczas
testów menadżer zadań systemu Windows), czyli
na dwa dostępne rdzenie, wykorzystywano tylko
jeden z nich. Połowa mocy maszyny została
niespożyta. Błędem w tym podejściu jest
wykorzystanie jednego wątku (tak działa
rekurencja). Skoro mamy procesor wielordzeniowy,
to można wykorzystać wiele wątków, wtedy
wszystkie rdzenie powinny pracować na pełnym
obciążeniu. Funkcja implementująca takie
podejście przedstawia listing 3.
public static void WalkTree(TNode node)
{
if (node == null)
return;
public static void WalkTree(TNode node)
{
if (node == null)
return;
Task left = Task.Create((o) =>
WalkTree(node.LeftNode));
Task right = Task.Create((o) =>
WalkTree(node.RightNode));
left.Wait();
right.Wait();
Thread left = new Thread((o) =>
WalkTree(node.LeftNode));
left.Start();
Thread right = new Thread((o) =>
WalkTree(node.RightNode));
right.Start();
ProcessItem(node.Value);
}
left.Join();
right.Join();
Listing 4 - Funkcja przeglądania drzewa
wykorzystująca bibliotekę Parallel Extensions
ProcessItem(node.Value);
}
Jak można zauważyć kod tej metody nie różni
się znacząco od rozwiązania wielowątkowego.
Słowo kluczowe Thread zastąpiono słowem Task,
a metodę Join, metodą Wait. Zmierzony czas to
13,203 ms, czyli jedynie sekundę szybciej od
rozwiązania wykorzystujące wątki. Największą
Listing 3 - Funkcja przeglądania drzewa
wykorzystująca wątki
W powyższym rozwiązaniu wykorzystano
składnie Lambda, dostępną w platformie .NET od
4
zaleta takiego podejścia jest pozyskana pamięć,
która w niektórych przypadkach może mieć
kluczowe znaczenie dla algorytmu przetwarzania.
Czasy tych rozwiązań na maszynie 4
rdzeniowej, wykorzystanej podczas prezentacji
Daniela Motha przedstawia Tabela 2, jak widać w
tym przypadku otrzymano prawie 4 krotny wzrost
wydajności.
Rekurencja
Wątki
Zadania
na rozłączne zbiory i analizowanie ich osobno np.
na innych rdzeniach. Po zakończeniu wyniki
osobnych analiz powinny być łączone. Takie
podejście
właśnie
wykorzystuje
Parallel
Extensions. Zmodyfikowane zapytanie prezentuje
listing 6.
var q = from p in people.AsParallel()
where p.age < MaxAge &&
p.age > MinAge &&
p.state == STATE
order by age ascending
select p;
.NET 4.0
15,022 s
5,801 s
3,918 s
Listing 6 - Przykładowe zapytanie LINQ,
zrównoleglone przy pomocy Parallel Extensions
Tabela 2 - Czasy rozwiązań zadania przechodzenia
po drzewie otrzymane przez Daniela Motha
podczas prezentacji PDC
Jak widać równoległe zapytanie różni się od
wersji sekwencyjnej jedynie jedną instrukcją - na
obiekcie kolekcji wykonano dodatkowo metodę
AsParallel. Powoduje to rozdzielenie kolekcji do
analizy. Podział kolekcji może nie być równy
i zależeć może od aktualnego obciążenia rdzeni np.
jeden z rdzeni może dostać większą część kolekcji,
niż inne rdzenie. Deklaratywny paralelizm
wewnętrznie
działa
na
fundamentach
imperatywnego podejścia tj. tworzone są obiekty
typu Task, które rozdziale są poprzez menadżera
zadań.
Podejście której stosuje bezpośrednio obiekty
typu Task, nazywane jest imperatywnym
paralelizmem.
7. Deklaratywny paralelizm. Drugim podejściem
do równoległości w bibliotece Parallel Extensions
jest tzw. deklaratywny paralelizm. Polega on na
wprowadzeni równoległości w zapytaniach LINQ
[11]. Aby zobrazować taki przypadek należy
prześledzić przykładowe zapytanie przedstawione
na listingu 5.
8. Zasada działania. Podstawą kwestią jaką
należy zrozumieć w Parallel Extensions jest sposób
działania menadżera zadań tej biblioteki. Rysunek
1 przedstawia schematyczną budowie tego
elementu na maszynie 4 rdzeniowej. Dokładną
budowę menadżera zadań można opisać
następująco: jeśli dany jest procesor o N rdzeniach,
to dla każdego rdzenia tworzona jest grupa robocza
(z ang. work group), zaznaczona na rysunku
zielona elipsą z napisem WG). Każda taka grupa
robocza posiada jednego aktywnego pracownika
(z ang. worker). Pracownik to czerwona elipsa na
obręczy doczepionej do grupy roboczej.
Pracowników może być więcej, ale tylko jeden
może być aktywny w danej chwili, tzn. nie
w stanie zawieszenia.
Pracownik wykonuje zadania na rdzeniu, do
którego przypisana jest grupa robocza. Każda
var q = from p in people
where p.age < MaxAge &&
p.age > MinAge &&
p.state == STATE
order by age ascending
select p;
Listing 5 - Przykładowe zapytanie LINQ
Zapytanie to ma na celu wybranie w kolejności
rosnącej tych obiektów z kolekcji people, które
spełniają pewne zadane kryteria. Normalne
wykonanie tego zapytanie polega na przeglądaniu
kolekcji obiekt po obiekcie. Te obiekty które
spełniają
podane
kryteria
są
zwracane.
W przypadku kiedy kolekcja liczy tysiące
obiektów operacja ta jest czasochłonna i może
mieć bezpośredni wpływ na wydajność całego
programu. Wprowadzanie równoległości w takim
przypadku, może polegać na rozdzieleniu kolekcji
5
grupa robocza posiada własną kolejkę zadań, które
utożsamiane są z obiektami klasy Task tworzonymi
w kodzie programu. Dodatkowo istnieje także
globalna kolejka zadań. Grupa robocza przypisana
jest do konkretnego rdzenia procesora i wszystkie
obliczenia w ramach tej grupy wykonywane są
w jednym wątku.
Biblioteka Parallel Extensions tworzy tylko tyle
wątków ile dostępnych jest rdzeni na maszynie na
której aktualnie jest uruchomiona. Nie ma tutaj
kosztu przełączania, ponieważ każdy rdzeń ma
swój wątek.
skończył wykonywać swoje zadanie a w kolejce
jego grupy nie ma kolejnych zadań? Pracownik
chce dalej pracować, zagląda więc do globalnej
kolejki z nadzieją że znajdzie tam kolejne zadania
do wykonania. Jeśli są tam nierozdzielone jeszcze
zadania, pracownik pobiera jedno z nich i zajmuje
się jego wykonywaniem. Co jednak jeśli tam
również jest pusto? W tym momencie stosowany
jest mechanizm kradzieży pracy (z ang. work
stealing).
Kradzież pracy polega na przeglądaniu przez
pracownika
wszystkich
lokalnych
kolejek
sąsiednich grup roboczych w poszukiwaniu
wolnego zadania do wykonania. Jeśli któraś
z kolejek zawiera zadania, to te znajdujące się na
końcu tej kolejki zostaje skradzione. Celem tego
mechanizmu jest całkowite wykorzystanie
wszystkich dostępnych jednostek obliczeniowych.
W przykładzie o przechodzeniu drzewa,
zadania tworzyły kolejne zadania do wykonania.
Jeśli pracownik wykonuje zadanie, które tworzy
nowe zadania, to te nowe zadania umieszczane są
na początku kolejki grupy roboczej tego
pracownika. Taka reguła ma bezpośredni związek
z mechanizmem kradzieży pracy. Zadanie które
zostało stworzone jako ostatnie,
będzie
wykonywane jako następne, ponieważ znajduje się
na pierwszym miejscu kolejki. Dane potrzebne do
wykonania tego zadania mogą być jeszcze
trzymane w pamięci podręcznej rdzenia, więc
dostęp do nich będzie o wiele szybszy niż do
kolejnych zadań z kolejki. Z kolei zadania na
końcu kolejki były stworzone najwcześniej, więc
dane o nich mogły zostać wyrzucone z pamięci
podręcznej. Właśnie te zadania są kradzione
podczas działania mechanizmu kradzieży pracy.
Przy takiej kradzieży prawdopodobnie nie
potrzebna będzie synchronizacja pomiędzy
rdzeniami ponieważ dane o zadaniach zostały
wyrzucone z pamięci podręcznych.
Zadania mogą się zablokować czekając na
zakończenie wykonywania innych zadań, do tego
celu służy metoda Wait. Menadżer zadań po
wykryciu że zadanie się zablokowało, zamraża
Rysunek 1 - Menadżer zadań Parallel Extensions
Pierwsze tworzone zadania trafiają do globalnej
kolejki zadań (z ang. global queue). Z tego miejsca
zostają rozdzielone do poszczególnych kolejek
grup roboczych. Podział ten nie musi być
sprawiedliwy i może zależeć od aktualnego
obciążenia rdzeni. Kiedy pracownik nie wykonuje
żadnych zadań sięga do kolejki swojej grup
roboczej i pobiera z niej pierwsze zadania do
wykonania. Zakładając że w przykładowym
programie do każdej lokalnej kolejki trafiły jakieś
zadania, każdy pracownik sięga zadanie z lokalnej
kolejki i zaczyna je wykonywać. W tym momencie
na każdym rdzeniu wykonywany jest jeden wątek
obsługujący któreś z zadań, a zużycie procesora
wynosi 100%, czyli wykorzystywany jest cały
potencjał maszyny. Co się stanie jeśli pracownik
6
pracownika który je wykonywał, a grupa robocza
tego pracownika tworzy kolejnego, który staje się
jedynym aktywnym pracownikiem. Pracownik ten
wykonuje kolejne zadania znajdujące się
w lokalnej kolejce lub kradnie je innym grupom
roboczym. Należy zaznaczyć, że tworzenie
nowego pracownika nie wiąże się z tworzeniem
nowego wątku. Cała grupa robocza oraz wszyscy
jej pracownicy wykonują się w tym samym wątku.
Kiedy zadanie się odblokowuje oczekuje na
zakończenie zadania aktualnego pracownika i jest
wykonywane poza kolejnością tj. przed zadaniami
czekającymi w kolejce lokalnej grupy roboczej.
[5] Intel® Core™ Solo Processor Family
http://ark.intel.com/ProductCollection.aspx?fa
milyId=18995
[6] Specyfikacja procesora Intel® Core™ i7-950
Processor,
http://ark.intel.com/Product.aspx?id=37150
[7] Hyper - Threading Technology, Intel
Technology Journal, Volume 06, Issue 01, 14
luty 2002, ISSN 1535766X,
ftp://download.intel.com/technology/itj/2002/
volume06issue01/vol6iss1_hyper_threading_t
echnology.pdf
[8] Microsoft Research
http://research.microsoft.com
[9] .NET Framework 4.0, Microsoft Software
Developer Network,
http://msdn.microsoft.com/plpl/library/w0x726c2(en-us).aspx
[10] Parallel Programming for Managed
Developers with the Next Version of
Microsoft Visual Studio, Daniel Moth,
Professional Developer Conference 2008,
http://channel9.msdn.com/blogs/pdc2008/tl26
[11] LINQ, Microsoft Software Developer
Network, http://msdn.microsoft.com/enus/netframework/aa904594.aspx
9. Podsumowanie. Parallel Extensions jest
podstawowa
biblioteką
programowania
równoległego dla platformy .NET i kierowana jest
głownie na maszyny wieloprocesorowe lub
wielordzeniowe. Nowe podejście stanowi godną
alternatywę
dla
dotychczasowego,
wielowątkowego modelu programowania. Główną
zaletą tej biblioteki nie jest szybkość przetwarzania
zadań, oszczędność pamięć, przenośność oraz
skalowalność. W erze procesorów wielordzeniowych, stosowanie tej biblioteki staje się
obowiązkowe.
Bibliografia.
[1] Gordon E. Moore: Cramming more
components onto integrated circuits,
Electronics Magazine 38 (8),
19/04/1965, ftp://download.intel.com/museum
/Moores_Law/Articles-Press_Releases/
Gordon_Moore_1965_Article.pdf
[2] Introducing to Intel’s 32nm Process
Technology, Whitepaper, 2009,
http://download.intel.com/pressroom/kits/32n
m/westmere/Intel_32nm_Overview.pdf
[3] Intel News Fact Sheet, Intel Developer Forum
22nm News Facts, 2009,
http://download.intel.com/pressroom/kits/even
ts/idffall_2009/pdfs/22nm_factsheet.pdf
[4] Intel® Core™ Duo Processor Family
http://ark.intel.com/ProductCollection.aspx?fa
milyId=22731
7