Eksperymentowałem z wielowątkowością i przetwarzaniem równoległym i potrzebowałem licznika, aby wykonać podstawowe zliczanie i analizę statystyczną szybkości przetwarzania. Aby uniknąć problemów z jednoczesnym używaniem mojej klasy, użyłem instrukcji lock na zmiennej prywatnej w mojej klasie:
private object mutex = new object();
public void Count(int amount)
{
lock(mutex)
{
done += amount;
}
}
Ale zastanawiałem się ... ile kosztuje blokowanie zmiennej? Jakie są negatywne skutki dla wydajności?
c#
.net
multithreading
locking
parallel-processing
Kees C. Bakker
źródło
źródło
Odpowiedzi:
Oto artykuł, który wchodzi w koszty. Krótka odpowiedź to 50ns.
źródło
lock
, tak samo jak nie martwisz się o koszt użycia zmiennej.Odpowiedź techniczna jest taka, że nie można tego określić ilościowo, w dużej mierze zależy to od stanu buforów zapisujących w pamięci procesora oraz od tego, ile danych zebranych przez moduł wstępnego pobierania danych musi zostać odrzuconych i ponownie odczytanych. Które są bardzo niedeterministyczne. Używam 150 cykli procesora jako przybliżenia, które pozwala uniknąć większych rozczarowań.
Odpowiedź praktyczne jest to, że jest waaaay tańsze niż ilość czasu spalisz na debugowanie kodu, gdy myślisz, że możesz pominąć blokadę.
Aby uzyskać twardą liczbę, musisz zmierzyć. Program Visual Studio ma zręczny analizator współbieżności dostępny jako rozszerzenie.
źródło
Dalsze czytanie:
Chciałbym przedstawić kilka moich artykułów, które są zainteresowane ogólnymi prymitywami synchronizacji i zagłębiają się w Monitor, zachowanie instrukcji blokady C #, właściwości i koszty w zależności od różnych scenariuszy i liczby wątków. W szczególności interesuje się marnotrawstwem procesora i okresami przepustowości, aby zrozumieć, ile pracy można wykonać w wielu scenariuszach:
https://www.codeproject.com/Articles/1236238/Unified-Concurrency-I-Introduction https://www.codeproject.com/Articles/1237518/Unified-Concurrency-II-benchmarking-methodologies https: // www. codeproject.com/Articles/1242156/Unified-Concurrency-III-cross-benchmarking
Oryginalna odpowiedź:
O jej!
Wygląda na to, że poprawna odpowiedź oznaczona tutaj jako ODPOWIEDŹ jest z natury niepoprawna! Chciałbym prosić autora odpowiedzi, z szacunkiem, aby przeczytał do końca artykuł, do którego prowadzi link. artykuł
Autor artykułu z 2003 roku dokonywał pomiarów tylko na maszynie Dual Core iw pierwszym przypadku pomiarowym mierzył blokowanie tylko jednym gwintem i wynik wynosił około 50ns na dostęp do zamka.
Nie mówi nic o blokadzie w środowisku współbieżnym. Musimy więc kontynuować czytanie artykułu, aw drugiej połowie autor mierzył scenariusz blokowania z dwoma i trzema wątkami, który zbliża się do poziomów współbieżności dzisiejszych procesorów.
Tak więc autor mówi, że przy dwóch wątkach na Dual Core zamki kosztują 120ns, a przy 3 wątkach idzie to 180ns. Wydaje się więc, że jest to wyraźnie zależne od liczby wątków jednocześnie uzyskujących dostęp do blokady.
Jest to więc proste, nie jest to 50 ns, chyba że jest to pojedynczy wątek, w którym blokada staje się bezużyteczna.
Inną kwestią do rozważenia jest to, że jest mierzony jako średni czas !
Gdyby mierzyć czas iteracji, byłyby nawet czasy od 1 ms do 20 ms, po prostu dlatego, że większość była szybka, ale kilka wątków będzie czekało na czas procesora i będzie miało nawet milisekundowe opóźnienia.
To zła wiadomość dla każdego rodzaju aplikacji, która wymaga dużej przepustowości i małych opóźnień.
Ostatnią kwestią do rozważenia jest to, że wewnątrz zamka mogą występować wolniejsze operacje i bardzo często tak jest. Im dłużej blok kodu jest wykonywany wewnątrz zamka, tym większa rywalizacja i opóźnienia rosną niebotycznie.
Proszę wziąć pod uwagę, że od 2003 roku minęła już ponad dekada, czyli kilka generacji procesorów zaprojektowanych specjalnie do pracy w pełni równoległej, a blokowanie znacznie szkodzi ich wydajności.
źródło
To nie odpowiada na twoje pytanie dotyczące wydajności, ale mogę powiedzieć, że .NET Framework oferuje
Interlocked.Add
metodę, która pozwoli ci dodaćamount
twójdone
element do swojego członka bez ręcznego blokowania innego obiektu.źródło
lock
(Monitor.Enter / Exit) jest bardzo tani, tańszy niż alternatywy, takie jak Waithandle lub Mutex.Ale co by było, gdyby był (trochę) wolny, czy wolałbyś mieć szybki program z nieprawidłowymi wynikami?
źródło
Koszt zamka w ciasnej pętli w porównaniu z alternatywą bez zamka jest ogromny. Możesz sobie pozwolić na wielokrotne zapętlanie i nadal być bardziej wydajnym niż zamek. Dlatego kolejki bez blokad są tak wydajne.
Wynik:
źródło
Istnieje kilka różnych sposobów definiowania „kosztu”. Istnieje rzeczywisty koszt uzyskania i zwolnienia blokady; Jak pisze Jake, jest to nieistotne, chyba że ta operacja zostanie wykonana miliony razy.
Bardziej istotny jest wpływ, jaki ma to na przebieg egzekucji. Ten kod można wprowadzić tylko w jednym wątku na raz. Jeśli masz 5 wątków wykonujących tę operację regularnie, 4 z nich będą czekać na zwolnienie blokady, a następnie będą pierwszym wątkiem zaplanowanym do wprowadzenia tego fragmentu kodu po zwolnieniu blokady. Więc twój algorytm znacznie ucierpi. To, jak bardzo zależy od algorytmu i jak często wywoływana jest operacja. Nie da się tego uniknąć bez wprowadzenia warunków wyścigu, ale można to poprawić, minimalizując liczbę wywołań zablokowanego kodu.
źródło