Czym różnią się koroutiny bezstopniowe od koroutin bezstopniowych?

Background:

Pytam o to, ponieważ obecnie mam aplikację z wieloma (setkami do tysięcy) wątków. Większość z tych wątków jest bezczynna przez większą część czasu, czekając na pozycje robocze, które zostaną umieszczone w kolejce. Gdy element roboczy jest dostępny, jest on następnie przetwarzany przez wywołanie dowolnego złożonego istniejącego kodu. W niektórych konfiguracjach systemu operacyjnego aplikacja porównuje parametry jądra regulujące maksymalną liczbę procesów użytkownika, chciałbym więc poeksperymentować ze środkami zmniejszającymi liczbę wątków roboczych.

Moje proponowane rozwiązanie:

Wydaje się, że podejście oparte na koroutinie, w którym zamieniam każdy wątek roboczy na koroutine, pomogłoby to osiągnąć. Wtedy mogę mieć kolejkę roboczą popartą pulą rzeczywistych (jądra) wątków roboczych. Gdy element zostanie umieszczony w określonej kolejce coroutine do przetworzenia, wpis zostanie umieszczony w kolejce puli wątków. Następnie wznowi odpowiedni coroutine, przetwarza jego dane w kolejce, a następnie zawiesza je ponownie, uwalniając wątek roboczy do wykonywania innych prac.

Szczegóły realizacji:

Myśląc o tym, jak bym to zrobił, mam problem ze zrozumieniem różnic funkcjonalnych między koroutinami stackless i stackful. Mam pewne doświadczenie w używaniu stackful coroutines za pomocą Boost.Coroutine biblioteka. Uważam, że jest to stosunkowo łatwe do zrozumienia z poziomu konceptualnego: dla każdego coroutine, przechowuje kopię kontekstu procesora i stosu, a kiedy przełączysz się na coroutine, przełącza się do tego zapisanego kontekstu (tak jak zrobiłby to harmonogram trybu jądra).

Mniej jasne jest dla mnie, czym różni się od tego koroutine bez stosu. W moim zgłoszeniu bardzo ważna jest ilość nakładów związanych z wyżej opisanym kolejkowaniem elementów pracy. Większość implementacji, które widziałem, jak Nowa biblioteka CO2 sugeruje, że bezstopniowe coroutiny zapewniają wiele przełączniki kontekstowe dolnoprzepustowe.

Dlatego chciałbym lepiej zrozumieć różnice funkcjonalne między koroutinami bezstopniowymi i stackful. Konkretnie, myślę o tych pytaniach:

  • Odniesienia takie jak ten sugerują, że rozróżnienie polega na tym, gdzie można ustąpić / wznowić w stosie vs. bez stosu coroutine. Tak jest? Czy jest prosty przykład czegoś, co mogę zrobić w stosowym koroutinie, ale nie w stosie jeden?

  • Czy istnieją jakieś ograniczenia dotyczące stosowania zmiennych automatycznego przechowywania (np. zmiennych "na stosie")?

  • Czy istnieją jakieś ograniczenia dotyczące funkcji, które mogę wywoływać z coroutine bez stosu?

  • Jeśli nie ma zapisywania kontekstu stosu dla bezstopniowego coroutine, gdzie idą automatyczne zmienne magazynujące, gdy coroutine jest uruchomiony?

Author: Jason R, 2015-03-11

2 answers

Po pierwsze, dziękuję za przyjrzenie się CO2 :)

Doładowanie.Koroutine doc opisuje zaletę stackful koroutine well:

Stackfulness

W przeciwieństwie do koroutine bez stosu koroutine bez stosu może być zawieszony wewnątrz zagnieżdżonej ramki stosu . Wznowienie egzekucji na dokładnie ten sam punkt w kodzie, w którym został wcześniej zawieszony. Z koroutine bez stosu, tylko rutyna najwyższego poziomu może być zawieszony. Każda rutyna wywoływana przez tę procedurę najwyższego poziomu nie może sama się zawiesić. Zakazuje to wykonywania operacji zawieszania/wznawiania w procedurach wewnątrz biblioteka ogólnego przeznaczenia.

Kontynuacja pierwszej klasy

Kontynuacja pierwszej klasy może być przekazana jako argument, zwracany przez funkcję i przechowywany w strukturze danych do być wykorzystane później. W niektórych implementacjach (np. C # yield) kontynuacja nie może być bezpośrednio dostępna lub bezpośrednio zmanipulowany.

Bez stackfulness i pierwszej klasy semantyki, niektóre użyteczne wykonanie nie można obsługiwać przepływów sterowania (na przykład cooperative wielozadaniowość lub checkpointing).

Co to dla ciebie znaczy? na przykład wyobraź sobie, że masz funkcję, która zabiera odwiedzającego:
template<class Visitor>
void f(Visitor& v);

Chcesz przekształcić go w iterator, za pomocą stackful coroutine możesz:

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

Ale z koroutine bez stosu, nie da się tego zrobić:

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

Ogólnie, stackful coroutine jest mocniejszy niż stackless coroutine. Dlaczego więc chcemy coroutine bez stosu? krótka odpowiedź: efektywność.

Stackful coroutine zazwyczaj musi przeznaczyć pewną ilość pamięci, aby pomieścić swój stos runtime( musi być wystarczająco duży), a przełącznik kontekstowy jest droższy w porównaniu do przełącznika bezstopniowego, np.Coroutine zajmuje 40 cykli, podczas gdy CO2 zajmuje średnio tylko 7 cykli na mojej maszynie, ponieważ jedyną rzeczą, którą potrzebuje bezstopniowy coroutine restore jest licznikiem programu.

To powiedziawszy, z obsługą języka, prawdopodobnie stackful coroutine może również skorzystać z obliczonego przez kompilator maksymalnego rozmiaru stosu, tak długo, jak nie ma rekurencji w coroutine, więc użycie pamięci może być również ulepszone.

Mówiąc o koroutine bez stosu, należy pamiętać, że nie oznacza to, że w ogóle nie ma runtime-stack, oznacza to tylko, że używa tego samego runtime-stack co strona hosta, więc można wywoływać funkcje rekurencyjne jako cóż, tylko, że wszystkie rekurencje będą miały miejsce na stosie uruchomieniowym hosta. W przeciwieństwie do stackful coroutine, kiedy wywołasz funkcje rekurencyjne, rekurencje będą miały miejsce na własnym stosie coroutine ' a.

Aby odpowiedzieć na pytania:

  • czy są jakieś ograniczenia dotyczące stosowania zmiennych automatycznego przechowywania (czyli zmienne "na stosie")?
Nie. To ograniczenie emulacji CO2. Z obsługą języka, automatyczne zmienne przechowywania widoczne dla coroutine zostanie umieszczony w pamięci wewnętrznej coroutine. Zwróć uwagę na mój nacisk na "visible to the coroutine", jeśli coroutine wywoła funkcję, która wewnętrznie używa automatycznych zmiennych magazynujących, wtedy te zmienne zostaną umieszczone na stosie runtime. Dokładniej mówiąc, coroutine bez stosu musi zachować tylko zmienne / tymczasowe, które mogą być używane po wznowieniu.

Aby było jasne, możesz również użyć zmiennych automatycznego przechowywania w ciele CO2:

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

As long ponieważ definicja nie poprzedza żadnego await.

  • czy są jakieś ograniczenia dotyczące funkcji, które mogę wywołać z coroutine bez stosu?
Nie.
  • jeśli nie ma zapisywania kontekstu stosu dla coroutine bez stosu, gdzie idą zmienne automatycznego przechowywania, gdy koroutine jest biegać?

Odpowiedź powyżej, koroutine bez stosu nie dba o automatyczne zmienne magazynujące używane w wywołanych funkcjach, zostaną one po prostu umieszczone na normalny stos runtime.

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

 41
Author: Jamboree,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-11 15:23:27

To, czego potrzebujesz to wątki/włókna-Zwykle chcesz zawiesić swój kod (uruchomiony w włóknach) w głębokim zagnieżdżonym stosie połączeń (na przykład parsowanie wiadomości z połączenia TCP). W tym przypadku nie można użyć bezstopniowego przełączania kontekstu (stos aplikacji jest współdzielony między bezstopniowymi coroutinami -> ramki stosu wywołanych podprogramów byłyby nadpisane).

Możesz użyć czegoś takiego jak boost.włókno, które implementuje nici/włókna użytkownika na bazie boost.kontekst.

 2
Author: olk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-12 10:25:37