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
В результате выгода в том, что пока сопрограмма активно не выполняется, ей не надо _пустое_место_ на стеке. И синхронные функции выполняются с той же эффективностью как обычно. А вот асинхронные функции (сопрограммы) имеют дополнительные накладные расходы по динамическому распределению памяти для стековых фреймов.
no subject
no subject
А как она реализована: сплошной массив у Threads, или список фреймов в Heap у coroutines - частности.
В любом случае где-то хранится цепь continuations вместо цепочки return addresses.
no subject
no subject
no subject
Всё равно нет никакой цепи continuations. Стандартная цепочка вложенных вызовов на обычном стеке.
no subject
no subject
no subject
Еще один вариант их представить - это несколько лямбд с общим контекстом. Запускается первая лямбда, она работает-работает пока не доходит до слова await. На этом месте она присоединяет к ожидаемому фьючеру лямбду-продолжение и возвращается. Когда фьючер будет готов, шедулер вызовет лямбду-продолжение и все продолжится. Когда же лямбда встречает слово return, она не просто так возвращается, а сначала сигналит в привязанный к контексту промиз/фьючер и освобождает контекст (выдывая деструкторы для всех объектов в нем).
Тут есть интересный момент: что если мы вызываем другую функцию, которая может внутри себя сделать await? Тогда та функция вернется в нашу, когда ее значение еще не готово. Ответ такой, что так делать не разрешается. Нельзя напрямую вызвать другую функцию, которая будет делать await, потому что та функция в таком разе обязана быть асинхронной. То есть, ее вызов транслируется в создание контекста и промиза/фьючера вызываемой асинхронной функции, привязывание к этому фьючеру своей лямбды-продолжения, шедуленье первой лямбды от новой функции, и возвращение из текущей лямбды.