Unix / Linux o niskim opóźnieniu

11

Większość zadań programowania o niskich opóźnieniach / wysokiej częstotliwości (na podstawie specyfikacji zadań) wydaje się być realizowana na platformach unix. W wielu specyfikacjach zwracają się one szczególnie do osób z doświadczeniem typu „Linux z niskim opóźnieniem”.

Zakładając, że nie oznacza to systemu Linux działającego w czasie rzeczywistym, czy ludzie mogliby mi pomóc z tym, co to może oznaczać? Wiem, że możesz ustawić powinowactwo procesora dla wątków, ale zakładam, że proszą o wiele więcej niż to.

Strojenie jądra? (mimo to słyszałem, że producenci tacy jak Solarflare produkują karty sieciowe z obejściem jądra)?

Co z DMA lub ewentualnie pamięcią współdzieloną między procesami? Gdyby ludzie mogli dać mi krótkie pomysły, mogę przejść do badań w Google.

(To pytanie prawdopodobnie będzie wymagać osoby zaznajomionej z transakcjami wysokiej częstotliwości)

użytkownik997112
źródło
2
Strojenie jądra jest sposobem, aby system operacyjny działający poza czasem rzeczywistym działał tak długo, jak to możliwe. Przypinanie nici jest również obowiązkowe. Możesz przeczytać więcej na ten temat w tym artykule: coralblocks.com/index.php/2014/04/…
rdalmeida
Powiązane również: stackoverflow.com/q/15702601/632951
Pacerier

Odpowiedzi:

26

Wykonałem sporo pracy, wspierając grupy HFT w ustawieniach IB i Hedge Fund. Odpowiem z widoku sysadmin, ale niektóre z nich dotyczą również programowania w takich środowiskach.

Jest kilka rzeczy, których zwykle szuka pracodawca, gdy odnoszą się do wsparcia „Low Latency”. Niektóre z nich to pytania „surowej prędkości” (czy wiesz, jaki typ karty 10 g kupić i jakie gniazdo do włożenia?), Ale więcej z nich dotyczy sposobów, w jakie środowisko handlu wysokoczęstotliwościowego różni się od tradycyjnego Środowisko Unix. Kilka przykładów:

  • Unix jest tradycyjnie dostrojony do obsługi dużej liczby procesów bez głodzenia zasobów, ale w środowisku HFT prawdopodobnie będziesz chciał uruchomić jedną aplikację z absolutnie minimalnym narzutem na przełączanie kontekstu i tak dalej. Jako klasyczny mały przykład włączenie hyperthreading na procesorze Intel pozwala na jednoczesne uruchomienie większej liczby procesów - ale ma znaczący wpływ na wydajność na szybkość wykonywania każdego procesu. Jako programista będziesz również musiał spojrzeć na koszt abstrakcji, takich jak wątki i RPC, i dowiedzieć się, gdzie bardziej monolityczne rozwiązanie - choć mniej czyste - pozwoli uniknąć kosztów ogólnych.

  • Protokół TCP / IP jest zazwyczaj dostrojony, aby zapobiegać zrywaniu połączeń i efektywnie wykorzystywać dostępną przepustowość. Jeśli Twoim celem jest uzyskanie możliwie najniższego opóźnienia z bardzo szybkiego łącza - zamiast uzyskania jak największej przepustowości z bardziej ograniczonego łącza - będziesz chciał dostosować strojenie stosu sieciowego. Od strony programowania będziesz również chciał spojrzeć na dostępne opcje gniazd i dowiedzieć się, które z nich mają ustawienia domyślne bardziej dostosowane do przepustowości i niezawodności niż do zmniejszenia opóźnień.

  • Podobnie jak w przypadku sieci, tak też w przypadku pamięci masowej - będziesz chciał wiedzieć, jak odróżnić problem z wydajnością pamięci masowej od problemu z aplikacją i dowiedzieć się, jakie wzorce użycia we / wy najmniej zakłócają wydajność programu (jako na przykład dowiedz się, gdzie złożoność korzystania z asynchronicznego we / wy może się opłacić i jakie są wady).

  • Wreszcie, bardziej boleśnie: my, administratorzy uniksowi, chcemy uzyskać jak najwięcej informacji o stanie monitorowanych przez nas środowisk, więc lubimy uruchamiać narzędzia takie jak agenci SNMP, aktywne narzędzia monitorowania takie jak Nagios i narzędzia do gromadzenia danych, takie jak sar (1). Jednak w środowisku, w którym przełączniki kontekstu muszą być absolutnie zminimalizowane, a użycie dysku i sieci IO ściśle kontrolowane, musimy znaleźć właściwy kompromis między kosztem monitorowania a czystą wydajnością monitorowanych urządzeń. Podobnie, jakich technik używasz, aby ułatwić kodowanie, ale kosztuje to wydajność?

Wreszcie, są inne rzeczy, które przychodzą z czasem; sztuczki i szczegóły, których uczysz się z doświadczeniem. Ale są one bardziej wyspecjalizowane (kiedy używam epoll? Dlaczego dwa modele serwerów HP z teoretycznie identycznymi kontrolerami PCIe działają tak inaczej?), Bardziej powiązane z tym, z czego korzysta konkretny sklep, i częściej zmieniają się z roku na rok .

jimwise
źródło
1
Dziękuję, chociaż byłem zainteresowany odpowiedzią programową, była to bardzo przydatna i pouczająca informacja.
user997112
5
@ user997112 To jest odpowiedź programowa. Jeśli tak się nie wydaje, czytaj dalej, aż się zmieni :)
Tim Post
15

Oprócz doskonałej odpowiedzi dotyczącej strojenia sprzętu / konfiguracji z @jimwise, „linux o niskim opóźnieniu” oznacza:

  • C ++ ze względu na determinizm (brak opóźnienia zaskoczenia podczas uruchamiania GC), dostęp do urządzeń niskiego poziomu (I / O, sygnały), mocy językowej (pełne wykorzystanie TMP i STL, bezpieczeństwo typu).
  • wolą szybkość nad pamięcią: częste jest> 512 Gb pamięci RAM; bazy danych są w pamięci, buforowane z góry lub egzotyczne produkty NoSQL.
  • wybór algorytmu: tak szybko, jak to możliwe w porównaniu z rozsądnym / zrozumiałym / rozszerzalnym, np. tablice wielobitowe bez blokady, zamiast właściwości obiektów z funkcją bool.
  • pełne wykorzystanie funkcji systemu operacyjnego, takich jak pamięć współdzielona między procesami na różnych rdzeniach.
  • bezpieczne. Oprogramowanie HFT jest zwykle zlokalizowane na giełdzie, więc możliwości złośliwego oprogramowania są niedopuszczalne.

Wiele z tych technik pokrywa się z tworzeniem gier, co jest jednym z powodów, dla których branża oprogramowania finansowego absorbuje ostatnio zwolnionych programistów gier (przynajmniej dopóki nie spłacą zaległych czynszów).

Podstawową potrzebą jest możliwość słuchania bardzo szerokiego pasma danych rynkowych, takich jak ceny papierów wartościowych (akcje, towary, kursy walutowe), a następnie podejmowanie bardzo szybkich decyzji kupna / sprzedaży / braku działania w oparciu o bezpieczeństwo, cenę i bieżące gospodarstwa.

Oczywiście to wszystko może również pójść spektakularnie źle .


Omówię więc punkt dotyczący tablic bitów . Załóżmy, że mamy system transakcyjny wysokiej częstotliwości, który działa na długiej liście zamówień (Kup 5 tys. IBM, Sprzedaj 10 tys. DELL itp.). Powiedzmy, że musimy szybko ustalić, czy wszystkie zamówienia są wypełnione, abyśmy mogli przejść do następnego zadania. W tradycyjnym programowaniu OO będzie to wyglądać następująco:

class Order {
  bool _isFilled;
  ...
public:
  inline bool isFilled() const { return _isFilled; }
};

std::vector<Order> orders;
bool needToFillMore = std::any_of(orders.begin(), orders.end(), 
  [](const Order & o) { return !o.isFilled(); } );

złożoność algorytmiczna tego kodu będzie równa O (N), ponieważ jest to skan liniowy. Spójrzmy na profil wydajności pod względem dostępu do pamięci: każda iteracja pętli wewnątrz std :: any_of () wywoła o.isFilled (), która jest wstawiona, więc staje się dostępem do pamięci _isFilled, 1 bajt (lub 4 w zależności od architektury, kompilatora i ustawień kompilatora) w obiekcie, powiedzmy 128 bajtów łącznie. Mamy więc dostęp do 1 bajtu na każde 128 bajtów. Kiedy czytamy 1 bajt, zakładając, że jest to najgorszy przypadek, otrzymamy brak pamięci podręcznej danych procesora. Spowoduje to żądanie odczytu do pamięci RAM, które odczytuje całą linię z pamięci RAM ( więcej informacji tutaj ), aby odczytać 8 bitów. Profil dostępu do pamięci jest więc proporcjonalny do N.

Porównaj to z:

const size_t ELEMS = MAX_ORDERS / sizeof (int);
unsigned int ordersFilled[ELEMS];

bool needToFillMore = std::any_of(ordersFilled, &ordersFilled[ELEMS+1],
   [](int packedFilledOrders) { return !(packedOrders == 0xFFFFFFFF); }

profil dostępu do pamięci, przy założeniu, że jest to najgorszy przypadek, to ELEMS podzielony przez szerokość linii RAM (różni się - może być dwukanałowy lub potrójny, itp.).

W efekcie optymalizujemy algorytmy pod kątem wzorców dostępu do pamięci. Żadna ilość pamięci RAM nie pomoże - to wielkość pamięci podręcznej procesora powoduje tę potrzebę.

czy to pomaga?


Na YouTube jest doskonała rozmowa o CPPCon na temat programowania z niskim opóźnieniem (dla HFT): https://www.youtube.com/watch?v=NH1Tta7purM

JBRWilkinson
źródło
„tablice wielu bitów zamiast tablicy obiektów z właściwościami bool” co przez to rozumiesz?
user997112,
1
Opracowałem przykłady i linki.
JBRWilkinson
Idąc o krok dalej - zamiast używać całego bajtu do wskazania, czy zamówienie jest wypełnione, czy nie - możesz użyć tylko jednego bitu. Tak więc w jednej linii podręcznej (64 bajty) - możesz reprezentować stan 256 zamówień. Więc - mniej chybień.
quixver,
Ponadto - jeśli wykonujesz liniowe skanowanie pamięci - preselektor sprzętowy świetnie ładuje dane. Pod warunkiem, że masz dostęp do pamięci sekwencyjnie, krocząc lub coś prostego. Ale jeśli uzyskujesz dostęp do pamięci w jakikolwiek niesekwencyjny sposób - moduł wstępny procesora się myli. Np. Wyszukiwanie binarne. W tym momencie programista może pomóc procesorowi ze wskazówkami - _mm_prefetch.
quixver,
-2

Ponieważ nie wprowadziłem do produkcji jednego lub dwóch programów wysokiej częstotliwości, powiedziałbym najważniejsze rzeczy:

  1. Konfiguracja sprzętu i administratorzy systemu wraz z inżynierami sieci NIE określają dobrego wyniku liczby zleceń przetwarzanych przez system transakcyjny, ale mogą obniżyć go znacznie, jeśli nie znają podstaw opisanych powyżej.
  2. Jedyną osobą, która faktycznie sprawia, że ​​system wykonuje transakcje z wysoką częstotliwością, jest informatyk, który tworzy kod w c ++

    Wśród wykorzystanej wiedzy jest

    A. Operacje porównania i zamiany.

    • w jaki sposób CAS jest wykorzystywany w procesorze i jak komputer go obsługuje w tak zwanym przetwarzaniu struktury bez blokowania. Lub przetwarzanie bez blokady. Nie będę tu pisać całej książki. W skrócie kompilator GNU i kompilator Microsoft obsługują bezpośrednie użycie instrukcji CAS. Pozwala to Twojemu kodowi na „NoWair” podczas wydobywania elementu z kolejki lub umieszczania nowego w kolejce.
  3. Utalentowany naukowiec wykorzysta więcej. Powinien znaleźć w ostatnich nowych „wzorach” ten, który pojawił się jako pierwszy w Javie. Nazywany wzorem DISRUPTOR. Fold w wymianie LMAX w Europie wyjaśnił społeczności o wysokiej częstotliwości, że wykorzystanie wątków we współczesnych procesorach straciłoby czas przetwarzania przy zwolnieniu pamięci podręcznej przez procesor, jeśli kolejka daya nie jest dopasowana do wielkości nowoczesnej pamięci podręcznej procesora = 64

    Więc dla tego odczytu opublikowali kod Java, który pozwala procesowi wielowątkowemu na prawidłowe używanie sprzętowej pamięci podręcznej procesora bez rozwiązywania konfliktów. I dobry informatyk MUSI odkryć, że ten wzorzec został już przeniesiony do c ++ lub sam się przeportował.

    Jest to biegłość wykraczająca poza konfigurację administratora. Jest to dziś prawdziwe serce wysokiej częstotliwości.

  4. Informatyk MUSI napisać dużo kodu C ++ nie tylko po to, by pomóc pracownikom QA. Ale także
    • zatwierdzić przed traderami sprawdzoną osiągniętą prędkość
    • potępił zastosował różne stare technologie i ujawnił je własnym kodem, aby pokazać, że nie dają one dobrych wyników
    • napisać własny wielowątkowy kod komunikacyjny c ++ oparty na sprawdzonej szybkości jądra pupe / select zamiast korzystać ze starych technologii. Podam przykład - nowoczesna biblioteka tcp to ICE. A ludzie, którzy to zrobili, są bystrzy. Ale ich priorytety dotyczyły kompatybilności z wieloma językami. Więc. Możesz zrobić lepiej w c ++. Poszukaj więc najlepszych przykładów wydajności opartych na ASYNCHRONOUS select-call. I nie idź do wielu konsumentów wielu producentów - nie do HF.
      Będziesz zaskoczony, gdy zobaczysz, że potok jest używany TYLKO DO powiadomienia jądra o nadejściu wiadomości. Możesz tam umieścić 64-bitowy numer wiadomości - ale w przypadku treści przechodzisz do kolejki CAS bez blokowania. Wyzwalane przez asynchroniczne select()wywołanie jądra .
    • co więcej. Dowiedz się więcej o przypisywaniu za pomocą powinowactwa wątku c ++ do wątku, który wykonuje potokowanie / kolejkowanie wiadomości. Ten wątek powinien mieć podstawowe powinowactwo. Nikt inny nie powinien używać tego samego numeru rdzenia procesora.
    • i tak dalej.

Jak widać - wysoka częstotliwość to DZIAŁANIE ROZWOJOWE. Aby odnieść sukces, nie możesz być programistą C ++.

A kiedy mówię, że odniesie sukces, mam na myśli to, że fundusz hedgingowy, dla którego pracowałbyś, rozpozna wysiłki związane z trasami koncertowymi w rocznym wynagrodzeniu przekraczającym liczbę osób i rekruterów, o których mówią.

Czasy najczęstszych pytań o konstruktor / destruktor minęły bezpowrotnie. Sama c ++… migrowała z nowymi kompilatorami, aby uwolnić cię od zarządzania pamięcią i wymusić brak dziedziczenia o dużej głębi w klasach. Strata czasu. Zmieniono paradygmat ponownego wykorzystywania kodu. Nie chodzi tylko o to, ile klas wykonałeś polimorfem. Chodzi o prosto potwierdzoną wydajność kodu, którego można użyć ponownie.

Więc to jest twój wybór, aby przejść do krzywej uczenia się, czy nie. Nigdy nie trafi w znak stopu.

alex p
źródło
6
Możesz zaryzykować pisownię i formatowanie. W obecnej formie ten post jest ledwo zrozumiały.
CodesInChaos
1
Opisujesz sytuację sprzed 10 lat. Rozwiązania sprzętowe z łatwością wyprzedzają obecnie czysty C ++, bez względu na to, jak zoptymalizowany jest Twój C ++.
Sjoerd
Dla tych, którzy chcą wiedzieć, co to są rozwiązania sprzętowe - w większości są to układy FPGA, w których kod jest wypalany w szybkiej pamięci i nie ulega zmianie bez ponownego zapisywania tak zwanej pamięci ROM. Tylko do odczytu
alex p
@alexp Wyraźnie nie wiesz o czym mówisz. FPGA to coś innego niż „kod wypalony w szybkiej pamięci”.
Sjoerd