boost::coroutine2 против CoroutineTS

Boost::Coroutine2 и CoroutineTS(C++20) являются популярными реализациями сопрограмм в C++. Оба делают приостановку и возобновление, но две реализации используют совершенно разные подходы.

CoroutineTS(C++20)

  • Stackless
  • Приостановить на возврат
  • Использует специальные ключевые слова
generator<int> Generate()
{
   co_yield;
});

повышение:: coroutine2

  • Stackful
  • Приостановить по вызову
  • Не используйте специальные ключевые слова
pull_type source([](push_type& sink)
{
   sink();
});

Существуют ли какие-либо конкретные случаи использования, когда я должен выбрать только один из них?

1 ответ

Решение

Основное техническое отличие заключается в том, хотите ли вы иметь возможность уступать из вложенного колла. Это невозможно сделать с помощью сопрограмм без стеков.

Еще одна вещь, которую следует учитывать, состоит в том, что стековые сопрограммы имеют свой собственный стек и контекст (например, маски сигналов, указатель стека, регистры ЦП и т. Д.), Поэтому они занимают больший объем памяти, чем сопрограммы без стеков. Это может быть проблемой, особенно если у вас система с ограниченными ресурсами или огромное количество сопрограмм, существующих одновременно.

Я понятия не имею, как они сравнивают производительность с точки зрения производительности в реальном мире, но в целом сопрограммы без стеков более эффективны, так как имеют меньше накладных расходов (переключатели задач без стеков не должны обмениваться стеками, хранить / загружать регистры и восстанавливать сигнал маска и т. д.).

Пример минимальной реализации сопрограммы без стеков см. В сопрограммах Саймона Тэтхэма, использующих устройство Даффа. Довольно интуитивно понятно, что они настолько эффективны, насколько это возможно.

Кроме того, на этот вопрос есть хорошие ответы, в которых более подробно рассказывается о различиях между стеками и без стеков.

Как получить из вложенного вызова в сопрограмм без стеков? Несмотря на то, что я сказал, что это невозможно, это не на 100% верно: вы можете использовать (по крайней мере два) трюка для достижения этой цели, каждый из которых имеет свои недостатки: во-первых, вы должны конвертировать каждый вызов, который должен быть в состоянии привести к вызову сопрограммы. в сопрограмму, а также. Теперь есть два пути:

  1. Батутный подход: вы просто вызываете дочернюю сопрограмму из родительской сопрограммы в цикле, пока она не вернется. Каждый раз, когда вы уведомляете детскую сопрограмму, если она не заканчивается, вы также выдаете вызывающую сопрограмму. Обратите внимание, что этот подход запрещает вызывать дочернюю сопрограмму напрямую, вы всегда должны вызывать внешнюю сопрограмму, которая затем должна повторно вводить весь стек вызовов. Это вызов и возврат сложности O(n) для глубины вложенности n. Если вы ждете события, событие просто должно уведомить внешнюю сопрограмму.

  2. Подход родительской ссылки: Вы передаете адрес родительской сопрограммы дочерней сопрограмме, получаете родительскую сопрограмму, и дочерняя сопрограмма вручную возобновляет родительскую сопрограмму после ее завершения. Обратите внимание, что этот подход запрещает прямой вызов любой сопрограммы, кроме самой внутренней. Этот подход имеет сложность вызова и возврата O (1), поэтому он, как правило, предпочтительнее. Недостаток заключается в том, что вы должны вручную зарегистрировать самую внутреннюю сопрограмму где-нибудь, чтобы следующее событие, которое хочет возобновить внешнюю сопрограмму, знало, на какую внутреннюю сопрограмму напрямую нацелиться.

Примечание. Под сложностью вызова и возврата я подразумеваю количество шагов, предпринятых при уведомлении сопрограммы о ее возобновлении, и число шагов, предпринятых после уведомления о возврате к вызывающему уведомителю снова.

Другие вопросы по тегам