2015-03-11 28 views
38

Tło:Czym różnią się układy w stosy od stacked coroutines?

Pytam, bo to obecnie mają aplikację z wielu setek (tysięcy) nici. Większość tych wątków jest bezużyteczna przez większą część czasu, czekając na elementy pracy, które mają zostać umieszczone w kolejce. Kiedy element pracy jest dostępny, jest on przetwarzany przez wywołanie dowolnego dowolnie złożonego istniejącego kodu. W niektórych konfiguracjach systemu operacyjnego aplikacja uderza w parametry jądra, które regulują maksymalną liczbę procesów użytkownika, dlatego chciałbym eksperymentować z metodami zmniejszania liczby wątków roboczych.

Moja Proponowane rozwiązanie:

Wydaje się podejściem współprogram oparte, gdzie zastąpi każdy wątek roboczy z współprogram, przyczyniłoby się do osiągnięcia tego celu. Mogę wtedy mieć kolejkę roboczą popartą pulą rzeczywistych wątków (kernel). Gdy element zostanie umieszczony w kolejce określonego programu do przetwarzania, wpis zostanie umieszczony w kolejce puli wątków. Następnie wznowiłby odpowiedni coroutine, przetworzył swoje kolejkowane dane, a następnie zawiesił je ponownie, uwalniając wątek roboczy, aby wykonać inną pracę.

szczegóły

realizacji:

W myśleć o tym, jak to zrobić, mam problemy ze zrozumieniem funkcjonalnych różnic pomiędzy Stackless i stackful współprogram. Mam pewne doświadczenie przy użyciu stosu coroutines przy użyciu biblioteki Boost.Coroutine. Uważam, że jest to stosunkowo łatwe do zrozumienia na poziomie konceptualnym: dla każdego z nich zachowuje kopię kontekstu i stosu procesora, a po przełączeniu się na współprowadzącego przełącza się do zapisanego kontekstu (podobnie jak harmonogram harmonogramu jądra).

To, co jest dla mnie mniej jasne, to to, jak różni się od nich stos pozbawiony stosu. W mojej aplikacji bardzo ważne jest obciążenie związane z wyżej opisanym kolejkowaniem elementów pracy. Większość implementacji, które widziałem, takich jak the new CO2 library, sugeruje, że stosy niewiążące się ze stosem zapewniają znacznie niższe przełączniki kontekstowe.

W związku z tym chciałabym lepiej zrozumieć różnice funkcjonalne między stosami bez stosu i stosu. Konkretnie, myślę, że z tych pytań:

  • References like this one sugerują, że różnica polega na którym można wydajność/CV w sposób stackful vs. Stackless współprogram. Czy tak jest? Czy istnieje prosty przykład czegoś, co mogę zrobić w stosie coroutine, ale nie w trybie stackless?

  • Czy są jakieś ograniczenia dotyczące korzystania z automatycznych zmiennych pamięci (tj. Zmiennych "na stosie")?

  • Czy istnieją jakiekolwiek ograniczenia dotyczące funkcji, które mogę wywoływać z nie stosującego się w stosie coroutine?

  • Jeśli nie ma zapisywania kontekstu stosu dla stosu niewiążącego w stos, gdzie zmienne automatycznej pamięci pojawiają się, gdy działa coroutine?

+2

"Większość tych wątków jest bezużyteczna przez większą część czasu, czekając na elementy pracy, które mają zostać umieszczone w kolejce" - jeśli tak, to dlaczego istnieje tak wiele wątków? –

+1

@MartinJames: Ze względów starszych. Nie twierdzę, że jest to dobry projekt, a więc moje pragnienie poprawy. Refaktoryzacja całej hurtowni aplikacji nie jest opcją krótkoterminową, dlatego na początku szukam stosunkowo prostych modernizacji. Potencjalnie komplikując dalej, wywołanie blokujące do kolejki jest zwykle wprowadzane na kilka poziomów w stos wywołań (tj. Nie jest to funkcja najwyższego poziomu wątku roboczego). * Myślę, że * to wykluczałoby użycie wątków bezsiadów w tym konkretnym kontekście. –

Odpowiedz

34

Po pierwsze, dziękuję za przyjrzeniu CO2 :)

Boost.Współprogram doc opisuje zalety stackful współprogram dobrze:

stackfulness

W przeciwieństwie do współprogram Stackless stackful współprogram może być zawieszony w zagnieżdżonej StackFrame. Wykonanie wznowiono pod numerem dokładnie w tym samym punkcie kodu, w którym został on wcześniej zawieszony. Z bezkontekstowym korpusem, tylko procedura najwyższego poziomu może zostać zawieszona. Każda procedura wywoływana przez tę procedurę najwyższego poziomu nie może sama zawiesić. To zabrania udostępniania operacji zawieszenia/wznowienia w procedurach w ramach biblioteki uniwersalnej o numerze .

pierwszorzędny kontynuacja

pierwszorzędny kontynuacja mogą być przekazywane jako argumentu, zwrócony przez funkcję i przechowywane w strukturze danych do być wykorzystane później. W niektórych implementacjach (na przykład wydajność C#) kontynuacja nie może być bezpośrednio dostępna lub bezpośrednio manipulowana.

Bez stosu i pierwszorzędnej semantyki, niektóre użyteczne wykonania nie mogą być obsługiwane (np. Kooperatywne wielozadaniowość lub punkt kontrolny).

Co to oznacza dla Ciebie? na przykład wyobrazić, że masz funkcję, która pobiera odwiedzającego:

template<class Visitor> 
void f(Visitor& v); 

Chcesz przekształcić go iterator, z stackful współprogram można:

asymmetric_coroutine<T>::pull_type pull_from([](asymmetric_coroutine<T>::push_type& yield) 
{ 
    f(yield); 
}); 

Ale z Stackless współprogram, nie ma mowy Aby to zrobić:

generator<T> pull_from() 
{ 
    // yield can only be used here, cannot pass to f 
    f(???); 
} 

Ogólnie rzecz biorąc, stos coroutine jest silniejszy niż stackless coroutine. Dlaczego więc potrzebujemy kumulacji w stosach? krótka odpowiedź: efektywność.

Układająca się w stos coroutine zwykle musi przydzielić określoną ilość pamięci, aby pomieścić swój stos środowiska wykonawczego (musi być wystarczająco duży), a przełącznik kontekstu jest droższy w porównaniu do układu bez układu, np. Boost.Coroutine trwa 40 cykli, podczas gdy CO2 zajmuje średnio 7 cykli na mojej maszynie, ponieważ jedyną rzeczą, której nie wymaga układanie w stos, jest licznik programu.

To powiedziawszy, ze wsparciem językowym, prawdopodobnie stosy coroutine mogą również skorzystać z obliczonej przez kompilację maksymalnej wielkości dla stosu, o ile nie ma rekurencji w coroutine, więc użycie pamięci może być poprawione.

Mówiąc o stosie bez stosu, pamiętaj, że nie oznacza to, że w ogóle nie ma stosu runtime, oznacza to tylko, że używa tego samego zestawu runtime, co strona hosta, więc możesz wywoływać funkcje rekursywne jako cóż, po prostu wszystkie rekurencje będą się odbywały na stosie środowiska wykonawczego hosta. W przeciwieństwie do stosu coroutine, gdy wywołasz funkcje rekursywne, rekursje będą się odbywać na stosie własnego konta.

Aby odpowiedzieć na pytania:

  • Czy są jakieś ograniczenia dotyczące korzystania z automatycznych zmiennych przechowywania (tj zmiennych „na stosie”)?

Nie. To ograniczenie emulacji CO2. Dzięki obsłudze języków, automatyczne pamięci zmienne widoczne dla coroutine zostaną umieszczone w pamięci wewnętrznej CorEUINE. Zauważ, że kładę nacisk na "widoczny dla współprowadzącego", jeśli coroutine wywoła funkcję wewnętrznie wykorzystującą zmienne magazynu automatycznego, wtedy te zmienne zostaną umieszczone na stosie środowiska wykonawczego. Mówiąc dokładniej, nie stosujący się w stosie coroutine musi jedynie zachować zmienne/tymczasowe, które mogą być użyte po wznowieniu.

Żeby było jasne, można używać zmiennych automatycznych magazynów w współprogram ciała CO2, jak również:

auto f() CO2_RET(co2::task<>,()) 
{ 
    int a = 1; // not ok 
    CO2_AWAIT(co2::suspend_always{}); 
    { 
     int b = 2; // ok 
     doSomething(b); 
    } 
    CO2_AWAIT(co2::suspend_always{}); 
    int c = 3; // ok 
    doSomething(c); 
} CO2_END 

Dopóki definicja nie poprzedzać każdą await.

  • Czy istnieją jakiekolwiek ograniczenia dotyczące funkcji, które mogę wywołać z kumulacji w trybie stosu ?

nr

  • Jeśli nie ma oszczędności z kontekstu stosu dla współprogram Stackless, gdzie zmienne automatyczne przechowywanie iść gdy współprogram jest działa?

W odpowiedzi na powyższe, sterowany w stosie program typu coroutine nie dba o zmienne automatycznej pamięci używane w wywołanych funkcjach, zostaną one umieszczone na zwykłym stosie środowiska wykonawczego.

Jeśli masz jakiekolwiek wątpliwości, wystarczy sprawdzić kod źródłowy CO2, może on pomóc zrozumieć mechanikę pod maską;)

2

Co chcesz są wątki użytkownik-land/włókna - zazwyczaj chcesz zawiesić swój kod (działający w światłowodzie) w głęboko zagnieżdżonym stosie wywołań (na przykład analizowanie wiadomości z połączenia TCP). W tym przypadku nie można używać przełączania kontekstowego bez stosu (stos aplikacji jest współdzielony między schematami stosu bez stacków -> ramki stosów wywoływanych podprogramów zostaną nadpisane).

Możesz użyć czegoś takiego jak boost.fiber, które implementuje wątki/włókna użytkownika na podstawie boost.context.

+0

Moim głównym wyzwaniem związanym z wdrażaniem tego przy użyciu włókien lub coroutines jest kwestia planowania. Chciałbym zaimplementować model gwintowania M: N, w którym N włókien/coroutines są obsługiwane przez wątki jądra M, ale chciałbym, aby te gwinty M były w stanie obsłużyć dowolne z N włókien/coroutines w razie potrzeby.Czy jest możliwe wznowienie 'boost :: fiber' z innego wątku jądra niż wcześniej zostało zawieszone? Czy istnieje taki sukces? A co z 'boost :: asymmetric_coroutine'? –

+0

migracja włókna z jednego wątku do drugiego jest obecnie w fazie rozwoju, ale nie polecam go. przesuwanie jednego włókna ma sens tylko wtedy, gdy światłowód jest wykonywany na innym procesorze. przeniesienie światłowodu z jednego procesora na drugi spowodowałoby chybienie pamięci podręcznej. boost.fiber zapewnia klasy synchronizacji, takie jak mutext/condition variables itp. dla włókien. – olk

+0

Dzięki za informacje. Czuję, że byłoby to przydatną funkcją. Tak jak powiedziałem, w swoim wniosku chcę zrobić M: N threading. Miałbym N włókien, z których każdy reprezentuje rurociąg technologiczny. Te potoki są asynchronicznie zasilane danymi z innego wątku. Dlatego chciałbym móc obsłużyć każde z N włókien za pomocą wątków M (dużo mniejszych niż N). Oznacza to, że gdy dane włókno jest gotowe do uruchomienia, ponieważ dostępne są nowe dane wejściowe, chciałbym wziąć jeden z wątków jądra w puli i obsługiwać włókna. –