Testowanie obciążenia: jak generować żądania na sekundę?

14

Mam komponent serwera, który działa na Zeroc-ICE. Kiedy chciałem załadować test, pomyślałem, że skorzystanie z biblioteki równoległej do utworzenia wielu żądań to wystarczy. Ale tak się nie kończy. Korzystanie z biblioteki Parallel (Parallel.For) z C # najwyraźniej było łatwiejsze, ale nie wydaje się, aby dokładnie generowało wszystko równolegle w tym samym momencie. Nie może to być definicja tworzenia N żądań na sekundę. Jak mam to zrobić? Myślę, że każdy, kto chce najpierw przeprowadzić test obciążenia, naprawdę by o tym pomyślał.

  1. Jaki jest efektywny sposób tworzenia N żądań w ciągu sekundy?

  2. Kolejny mit dotyczy programowania równoległego. Proszę nas oświecić, jeśli użyliście wzorców programowania równoległego w C # lub .Net w ogóle. Wyobraź sobie, że mam 5 procesów. Jak rozpocznie się wszystkie pięć procesów jednocześnie. Co to znaczy dla mojego zużycia zasobów? Próbowałem zapoznać się z wieloma materiałami dostępnymi w sieci, ale otrzymuję coraz więcej pytań niż odpowiedzi na moje pytania.

  3. Użyłem Parallel.For i stworzyłem N wątków i zmierzyłem czas. Następnie spróbowałem tego samego za pomocą Task.Factory.start do wyliczenia zadań. Zmierzony czas był inny. Więc czym dokładnie różni się używanie ich? Kiedy powinienem używać odpowiednich klas i do jakich celów? często mamy wiele bogactw, ale po prostu nie wiemy, jak odróżnić jedno od drugiego. To dla mnie jeden taki przypadek, ponieważ nie mogę znaleźć powodu, dla którego nie powinienem używać jednego od drugiego.

  4. Użyłem klasy stopera, aby zmierzyć te czasy, które uważają za najlepsze. W scenariuszu obciążenia testuję komponent, jaki byłby sposób pomiaru czasu odpowiedzi. Stoper wydaje mi się najlepszym rozwiązaniem dla mnie. Wszelkie opinie są mile widziane.

ps: Istnieje wiele narzędzi do testowania obciążenia dla aplikacji internetowych. Mój jest niestandardowym przypadkiem komponentów serwera. Moje pytanie dotyczy bardziej tworzenia N wątków na sekundę.

Wszystkie opinie są mile widziane. Tylko nie myśl, że to nie tyle pytanie programistyczne. Oczywiście jest. Powinien zadzwonić do każdego programisty, który chce samodzielnie sprawdzić jakość swojego produktu, aby samemu poznać wydajność swojego produktu. Próbowałem wielu opcji, a potem musiałem się zastanowić, jak powinienem to zrobić?

Król
źródło
Faq mówi, że jeśli dotyczy konkretnego problemu programistycznego i jeśli jest to praktyczny problem w zawodzie programisty, można go zapytać. ludzie, którzy sceptycznie podchodzą do tego. proszę skomentuj.
Król
Co rozumiesz przez „tę samą chwilę”? Zastanawiam się, czy możesz zmusić TPL lub PLinq w jakikolwiek sposób, aby to osiągnąć.
Gert Arnold
Moje pytanie dotyczy generowania N żądań na sekundę. Tak więc ta sama chwila w tym scenariuszu miała na celu moje zrozumienie używania równoległości, aby równolegle uruchamiać wątki.
Król
Czy zrobiłeś jakąś sekwencyjną analizę?
3
Może dotyczyć programowania, ale w twoim poście jest zbyt wiele pytań (co najmniej 4). Ograniczę to do jednego pytania, które chcesz zadać, zanim zostanie zamknięte, ponieważ jest zbyt szerokie. Podaj odpowiednie informacje, takie jak 10000, o którym właśnie wspomniałeś, liczbę rdzeni w maszynie testowej). Wyświetlanie kodu zwykle pomaga.
Gert Arnold

Odpowiedzi:

10

Nie mam wszystkich odpowiedzi. Mam nadzieję, że mogę rzucić na to trochę światła.

Aby uprościć moje poprzednie stwierdzenia dotyczące modeli wątków .NET, wystarczy wiedzieć, że Parallel Library używa Tasks, a domyślny TaskScheduler dla Tasks używa ThreadPool. Im wyżej w hierarchii (ThreadPool znajduje się na dole), tym więcej narzutów masz przy tworzeniu elementów. To dodatkowe obciążenie z pewnością nie oznacza, że ​​jest wolniejsze, ale dobrze wiedzieć, że tam jest. Ostatecznie wydajność twojego algorytmu w środowisku wielowątkowym sprowadza się do jego konstrukcji. To, co działa dobrze sekwencyjnie, może nie działać równie dobrze równolegle. Jest zbyt wiele czynników, aby dać ci twarde i szybkie reguły, zmieniają się w zależności od tego, co próbujesz zrobić. Ponieważ zajmujesz się żądaniami sieci, postaram się podać mały przykład.

Pozwól mi powiedzieć, że nie jestem ekspertem od gniazd i nie wiem prawie nic o Zeroc-Ice. Wiem trochę o operacjach asynchronicznych i właśnie tam naprawdę ci to pomoże. Jeśli wyślesz żądanie synchroniczne przez gniazdo, podczas połączenia Socket.Receive()Twój wątek zostanie zablokowany do momentu otrzymania żądania. To nie jest dobre. Wątek nie może wysyłać więcej żądań, ponieważ jest zablokowany. Za pomocą Socket.Beginxxxxxx () żądanie I / O zostanie wykonane i umieszczone w kolejce IRP dla gniazda, a Twój wątek będzie kontynuowany. Oznacza to, że Twój wątek może faktycznie wysyłać tysiące żądań w pętli bez żadnego blokowania!

Jeśli dobrze cię rozumiem, używasz wywołań za pomocą Zeroc-Ice w kodzie testowym, a nie próbujesz dotrzeć do punktu końcowego http. W takim przypadku mogę przyznać, że nie wiem, jak działa Zeroc-Ice. Chciałbym jednak zasugerować po poradę wymienionych tutaj , zwłaszcza część: Consider Asynchronous Method Invocation (AMI). Strona pokazuje to:

Korzystając z AMI, klient odzyskuje wątek kontroli, gdy tylko wywołanie zostanie wysłane (lub, jeśli nie można go wysłać natychmiast, zostanie umieszczone w kolejce), umożliwiając klientowi użycie tego wątku do wykonania innej użytecznej pracy w międzyczasie .

Co wydaje się być odpowiednikiem tego, co opisałem powyżej przy użyciu gniazd .NET. Mogą istnieć inne sposoby poprawy wydajności, gdy próbuję wykonać wiele wysyłek, ale zacznę tutaj lub z dowolnymi innymi sugestiami wymienionymi na tej stronie. Byłeś bardzo niejasny co do projektu swojej aplikacji, więc mogę być bardziej szczegółowy niż wcześniej. Pamiętaj tylko, że nie używaj więcej wątków, niż jest to absolutnie konieczne, aby uzyskać to, czego potrzebujesz, w przeciwnym razie aplikacja będzie działać wolniej niż chcesz.

Kilka przykładów w pseudokodzie (próbowałem zbliżyć się do lodu, jak to możliwe, ale ja nie musiałem się go uczyć):

var iterations = 100000;
for (int i = 0; i < iterations; i++)
{
    // The thread blocks here waiting for the response.
    // That slows down your loop and you're just wasting
    // CPU cycles that could instead be sending/receiving more objects
    MyObjectPrx obj = iceComm.stringToProxy("whateverissupposedtogohere");
    obj.DoStuff();
}

Lepszy sposób:

public interface MyObjectPrx : Ice.ObjectPrx
{
    Ice.AsyncResult GetObject(int obj, Ice.AsyncCallback cb, object cookie);
    // other functions
}

public static void Finished(Ice.AsyncResult result)
{
    MyObjectPrx obj = (MyObjectPrx)result.GetProxy();
    obj.DoStuff();
}

static void Main(string[] args)
{
    // threaded code...
    var iterations = 100000;
    for (int i = 0; i < iterations; i++)
    {
        int num = //whatever
        MyObjectPrx prx = //whatever
        Ice.AsyncCallback cb = new Ice.AsyncCallback(Finished);
        // This function immediately gets called, and the loop continues
        // it doesn't wait for a response, it just continually sends out socket
        // requests as fast as your CPU can handle them.  The response from the
        // server will be handled in the callback function when the request
        // completes.  Hopefully you can see how this is much faster when 
        // sending sockets.  If your server does not use an Async model 
        // like this, however, it's quite possible that your server won't 
        // be able to handle the requests
        prx.GetObject(num, cb, null);
    }
}

Pamiętaj, że więcej wątków! = Lepsza wydajność podczas próby wysłania gniazd (lub naprawdę robiąc cokolwiek). Wątki nie są magiczne, ponieważ automatycznie rozwiążą każdy problem, nad którym pracujesz. Idealnie, chcesz 1 wątek na rdzeń, chyba że wątek spędza dużo czasu na oczekiwaniu, możesz uzasadnić, że masz więcej. Uruchamianie każdego żądania w jego własnym wątku jest złym pomysłem, ponieważ nastąpi zmiana kontekstu i marnowanie zasobów. (Jeśli chcesz zobaczyć wszystko, co o tym napisałem, kliknij edytuj i spójrz na poprzednie wersje tego postu. Usunąłem go, ponieważ wydawało się, że tylko zasłania główny problem.)

Zdecydowanie możesz je wysłać w wątkach, jeśli chcesz wysyłać dużą liczbę żądań na sekundę. Jednak nie przesadzaj z tworzeniem wątków. Znajdź równowagę i trzymaj się jej. Osiągniesz lepszą wydajność, jeśli użyjesz modelu asynchronicznego w porównaniu z modelem synchronicznym.

Mam nadzieję że to pomogło.

Christopher Currens
źródło
Dlaczego tak dużo mówisz o wydajności? Wydaje się, że nie tego chce OP.
svick,
1
@svick cóż, oryginalny post ops miał 4 pytania i zadawali pytania dotyczące wydajności zadań równoległych vs. Ostatecznie, chociaż jego pytanie ma związek z wydajnością, ponieważ ma on ogólny pomysł poprawny, ale najwyraźniej brakuje mu jego implementacji. Wierzę, że moje sprecyzowane odpowiedzi na końcu odpowiadają na pytanie, którego nie edytował.
Christopher Currens,
1
Byłem zmuszony ograniczyć moje pytania, ponieważ chcieli zagłosować za ich zamknięciem. Teraz wydaje się, że warto je tutaj mieć. @ChristopherCurrens +1 dobry punkt za różnicę z pulą wątków do zadań. To poszerzyło moje zrozumienie. Ale nadal utknąłem, jak generowanie niektórych N żądań na sekundę jest naprawdę możliwe? Jaki jest najlepszy sposób, aby to zrobić?
Król
@King - Chyba nie byłem tak jasny, jak myślałem. Pomyślałem, że ostatnie 3-4 akapity ci pomogą. Zakładałem, że już korzystasz z pętli. Jeśli to robisz, problem polega na tym, że twoje gniazda wysyłają / odbierają blokują, a tym samym spowalniają twoje żądania. Może znajdę trochę czasu, aby opublikować przykładowy pseudo kod.
Christopher Currens,
Nie mam problemu z wysłaniem ich przez ICE. Problem polega na tym, co definiuje implementację, która faktycznie utworzyłaby N żądań, i coś, co można powiedzieć, że jest zgodne z tą liczbą, N.
Król
2

Pominę pytanie 1) i przejdę do punktu 2, ponieważ jest to ogólnie akceptowalny sposób na osiągnięcie tego, czego szukasz. W przeszłości, aby osiągnąć n wiadomości na sekundę, możesz utworzyć pojedynczy proces, który następnie uruchomi p AppDomains. Każda AppDomain po prostu zaczyna uruchamiać pętlę żądań po osiągnięciu określonego punktu w czasie (za pomocą Timera). Ten czas powinien być taki sam dla każdej AppDomain, aby zapewnić, że zaczną uderzać o serwer w tym samym momencie.

Coś takiego powinno działać przy wysyłaniu twoich wniosków:

WaitCallback del = state => 
{ 
    ManualResetEvent[] resetEvents = new ManualResetEvent[10000]; 
    WebClient[] clients = new WebClient[10000]; 

    for (int index = 0; index < 10000; index++) 
    { 
        resetEvents[index] = new ManualResetEvent(false); 
        clients[index] = new WebClient(); 

        clients[index].OpenReadCompleted += new OpenReadCompletedEventHandler (client_OpenReadCompleted); 

        clients[index].OpenReadAsync(new Uri(@"<REQUESTURL>"), resetEvents[index]); 
    } 

    bool succeeded = ManualResetEvent.WaitAll(resetEvents, 10000); 
    Complete(succeeded); 

    for (int index = 0; index < 10000; index++) 
    { 
        resetEvents[index].Dispose(); 
        clients[index].Dispose(); 
    } 
}; 

while(running)
{
    ThreadPool.QueueUserWorkItem(del);
    Thread.Sleep(1000);
}

Prawdopodobnie zmniejszy to wydajność na dowolnym komputerze, na którym go uruchomisz, więc zawsze możesz zaimplementować podobny typ pętli z kilku różnych komputerów, jeśli masz zasoby (używając procesów zamiast domen aplikacji).

W przypadku trzeciego pytania proszę przeczytać ten link na stronie http://www.albahari.com/threading/

Wreszcie, stoper powinien być sparowany z licznikiem trafień, aby śledzić zarówno czas trwania, jak i niepowtarzalne trafienia na serwerze. To powinno pozwolić ci przeprowadzić analizę po fakcie.

Chris
źródło
2
Z jakiego możliwego powodu musiałbyś tutaj utworzyć osobne domeny aplikacji? To wydaje się zupełnie niepotrzebne.
svick
0

Nie przejmuj się wątkami, jeśli N jest stosunkowo mały. Aby wygenerować N żądań na sekundę, użyj zegara ściennego ( DateTime.Now). Poświęć trochę czasu przed i po żądaniu, a następnie dodaj znak, Sleepaby opóźnić następne żądanie.

Na przykład przy N = 5 (200 ms):

Before request: 12:33:05.014
After request: 12:33:05.077
Sleep(137)
Before request: 12:33:05.214
After request: 12:33:05.271
Sleep(131)

To nie jest idealne; może się okazać, że Sleepto nie jest dokładne. Możesz zachować bieżącą liczbę odchyleń (przed X żądaniami czas powinien wynosić X-1 / N później) i odpowiednio dostosować okres uśpienia.

Gdy N stanie się zbyt duży, wystarczy utworzyć M wątków i pozwolić, aby każdy wątek wygenerował N / M żądań w ten sam sposób.

MSalters
źródło
Muszę wygenerować bardzo dużą liczbę żądań. Więc nie może to być opcja, ponieważ spowoduje to zużycie mojej pamięci (4 GB pamięci RAM) nawet przed 100 wątkami.
Król
Utworzyłem 20 000 żądań na sekundę z jednego wątku w 250 KB kodu. W każdym razie nie masz wystarczającej liczby procesorów, aby uruchomić 100 wątków (ta klasa maszyn nie ma 4 GB). Kolejnym problemem byłoby wypchnięcie wszystkich tych żądań; czy masz Ethernet 10 Gbit / s między twórcą obciążenia a serwerem? Możesz więc sprawdzić swoje rzeczywiste wymagania.
MSalters
Aby to wyjaśnić, mam coś w rodzaju ponad 20 Gb / s. To nie jest problem. O klasie maszyn, do czego byś się odnosił? liczba procesorów?
Król
@King: wcisnąć 100 wątków, oczekiwałbym 48-rdzeniowej maszyny. SGI sprzedaje na przykład maszyny z tak dużą liczbą rdzeni, ale na tych zwykle dostajesz 32 GB lub więcej.
MSalters
0

Najłatwiejszym sposobem przeprowadzenia testu obciążenia dla dowolnego projektu .NET jest zakup wersji Ultimate programu Visual Studio. Jest wyposażony w zintegrowane narzędzia testowe, które pomagają w przeprowadzaniu wszelkiego rodzaju testów, w tym testów obciążenia. Testy obciążenia można wykonać, tworząc użytkowników wirtualnych na jednym komputerze lub rozdzielonych między kilku użytkowników dla większej liczby użytkowników. Istnieje również mały program, który można zainstalować na serwerach docelowych w celu zwrócenia dodatkowych danych na czas trwania testu.

Jest to jednak drogie, ale wersja ostateczna ma wiele funkcji, więc gdyby wszystkie były wykorzystane, byłaby to bardziej rozsądna cena.

Ryathal
źródło
0

Jeśli chcesz, aby wszystkie wątki X trafiły do ​​twojego zasobu dokładnie w tym samym czasie, możesz umieścić każdy wątek za zatrzaskiem odliczania i określić krótki okres oczekiwania między sprawdzeniami semaforów.

C # ma implementację (http://msdn.microsoft.com/en-us/library/system.threading.countdownevent(VS.100).aspx).

Jednocześnie, jeśli testujesz system w warunkach skrajnych, możesz również sprawdzić warunki wyścigu, w którym to przypadku chcesz ustawić okresy uśpienia wątku dla każdego wątku, które oscylują w czasie z losową częstotliwością i szczytami / przerwami.

Podobnie możesz nie chcieć po prostu szybko wysyłać wielu żądań, możesz mieć większy sukces w wprowadzaniu serwera w zły stan / testowaniu jego wydajności w świecie rzeczywistym, konfigurując mniejszą liczbę wątków, które spędzają więcej czasu na konsumowaniu i wysyłaniu wiadomości z powrotem i dalej nad gniazdem, ponieważ Twój serwer prawdopodobnie będzie musiał rozwinąć własne wątki, aby obsłużyć wolno trwające wiadomości.

Keith przynosi
źródło