Entry tags:
48 байт на одну сопрограмму
Судя по комментам к предыдущему посту, существует устойчивое подозрение, что сопрограммы имеют стек, и вообще кушают ресурсы. Хорошо, давайте померяем, это нетрудно. Создадим миллион сопрограмм и глянем расход памяти. Вот такой тестик.
Результат:co_void_t print(int n)
{
for (;;) {
std::cout << n << std::endl;
co_await co_await_t{};
}
}
int main(int argc, char **argv)
{
std::coroutine_handle<> continuation[1000000];
std::cout << "Using " << mallinfo().uordblks << " bytes after start.\n";
for (int i = 0; i < 1000000; i++)
continuation[i] = print(i);
std::cout << "Using " << mallinfo().uordblks << " bytes after coroutine allocation.\n";
for (int i = 0; i < 1000000; i++)
continuation[i].resume();
std::cout << "Using " << mallinfo().uordblks << " bytes after coroutine run.\n";
for (int i = 0; i < 1000000; i++)
continuation[i].destroy();
std::cout << "Using " << mallinfo().uordblks << " bytes after coroutine destroyed.\n";
return 0;
}
Делим разницу на количество сопрограмм: (48077488 - 77488) / 1000000 = 48 байтов на одну сопрограмму. Вполне недорогое удовольствие получается. Конечно, никакого собственного стека у сопрограмм не имеется, просто негде.Using 77488 bytes after start.
Using 48077488 bytes after coroutine allocation.
0
...
999999
Using 48077488 bytes after coroutine run.
Using 77824 bytes after coroutine destroyed.
no subject
Еще один вариант их представить - это несколько лямбд с общим контекстом. Запускается первая лямбда, она работает-работает пока не доходит до слова await. На этом месте она присоединяет к ожидаемому фьючеру лямбду-продолжение и возвращается. Когда фьючер будет готов, шедулер вызовет лямбду-продолжение и все продолжится. Когда же лямбда встречает слово return, она не просто так возвращается, а сначала сигналит в привязанный к контексту промиз/фьючер и освобождает контекст (выдывая деструкторы для всех объектов в нем).
Тут есть интересный момент: что если мы вызываем другую функцию, которая может внутри себя сделать await? Тогда та функция вернется в нашу, когда ее значение еще не готово. Ответ такой, что так делать не разрешается. Нельзя напрямую вызвать другую функцию, которая будет делать await, потому что та функция в таком разе обязана быть асинхронной. То есть, ее вызов транслируется в создание контекста и промиза/фьючера вызываемой асинхронной функции, привязывание к этому фьючеру своей лямбды-продолжения, шедуленье первой лямбды от новой функции, и возвращение из текущей лямбды.