vak: (Default)
[personal profile] vak
Судя по комментам к предыдущему посту, существует устойчивое подозрение, что сопрограммы имеют стек, и вообще кушают ресурсы. Хорошо, давайте померяем, это нетрудно. Создадим миллион сопрограмм и глянем расход памяти. Вот такой тестик.
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;
}
Результат:
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.
Делим разницу на количество сопрограмм: (48077488 - 77488) / 1000000 = 48 байтов на одну сопрограмму. Вполне недорогое удовольствие получается. Конечно, никакого собственного стека у сопрограмм не имеется, просто негде.

Date: 2021-06-22 02:55 (UTC)
sab123: (Default)
From: [personal profile] sab123
Стек сопрограмм выражается в виде контекстов, которые они аллокируют(как это по-русски?) из кучи. Это, кстати, и к вопросу об отладке: чтобы понять стек вызова, надо раскручивать цепочку этих контекстов.

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

Date: 2021-06-22 03:50 (UTC)
ircicq: (Default)
From: [personal profile] ircicq
Стек - это прежде всего структура вида LIFO.
А как она реализована: сплошной массив у Threads, или список фреймов в Heap у coroutines - частности.

В любом случае где-то хранится цепь continuations вместо цепочки return addresses.


Date: 2021-06-22 05:13 (UTC)
sab123: (Default)
From: [personal profile] sab123
Не копируется. На стек (или скорее даже в регистр, как аргумент вызова) кладется указатель на контекст. Точно так же, как и в лямбде. На стеке резервируются только краткоживущие локальные переменные.

Date: 2021-06-22 04:31 (UTC)
sab123: (Default)
From: [personal profile] sab123
Для ограниченного определения слова "работает". Внутри, конечно, асинхронная функция делится на несколько синхронных, каждая из которых ответственна за кусок от одного ожидания до другого. И вот эти синхронные функции выполняются на стеке, там же выделяют краткоживущие колакльные переменные, и вызывают другие синхронные функции там же. Но аналог полноценного стекового фрейма - это контекст.

Date: 2021-06-22 05:27 (UTC)
sab123: (Default)
From: [personal profile] sab123
Это одно и то же. Сопрограммы, конечно, разные бывают (вот когда-то еще в детстве я читал про них на Форте, user-space threads - тоже они, и я как-то даже делал свою реализацию), но вот конкретно такие, которые имеются в виду в С++ и Расте - функции с интерфейсом async/await.

Еще один вариант их представить - это несколько лямбд с общим контекстом. Запускается первая лямбда, она работает-работает пока не доходит до слова await. На этом месте она присоединяет к ожидаемому фьючеру лямбду-продолжение и возвращается. Когда фьючер будет готов, шедулер вызовет лямбду-продолжение и все продолжится. Когда же лямбда встречает слово return, она не просто так возвращается, а сначала сигналит в привязанный к контексту промиз/фьючер и освобождает контекст (выдывая деструкторы для всех объектов в нем).

Тут есть интересный момент: что если мы вызываем другую функцию, которая может внутри себя сделать await? Тогда та функция вернется в нашу, когда ее значение еще не готово. Ответ такой, что так делать не разрешается. Нельзя напрямую вызвать другую функцию, которая будет делать await, потому что та функция в таком разе обязана быть асинхронной. То есть, ее вызов транслируется в создание контекста и промиза/фьючера вызываемой асинхронной функции, привязывание к этому фьючеру своей лямбды-продолжения, шедуленье первой лямбды от новой функции, и возвращение из текущей лямбды.

Date: 2021-06-22 05:05 (UTC)
spamsink: (Default)
From: [personal profile] spamsink
И что, если у сопрограммы есть локальные переменные, то они при каждом await/resume туда-сюда копируются? Если да, то это досадно.

Date: 2021-06-22 07:06 (UTC)
spamsink: (Default)
From: [personal profile] spamsink
Ну слава богу. Надо полагать, -fomit-frame-pointer для них игнорируется, так что всё работает правильно. А alloca() будет работать точно так же, как выделение места для фактических параметров вызываемых процедур - на нормальном стеке. Я, собственно, хотел попросить попробовать напечатать адрес локальной переменной сопрограммы при первом вызове и после resume - проверить, всё ли честно делается.

Date: 2021-06-22 08:06 (UTC)
ircicq: (Default)
From: [personal profile] ircicq
В типовой реализации функции соответствует сгенерированный класс машины состояний
Локальным переменным соответствуют поля этого класса.
А каждому await - номер состояния.

Date: 2021-06-28 17:39 (UTC)
From: [personal profile] qse

Цель такова, что переменные живущие между приостановками будут в стеке, а те, которые переживают остановки - в activation frame вне стека

The ‘coroutine frame’ holds part of the coroutine’s activation frame that persists while the coroutine is suspended and the ‘stack frame’ part only exists while the coroutine is executing and is freed when the coroutine suspends and transfers execution back to the caller/resumer.

По ссылке есть картинки со стеком и регистрами

Там же есть ссылка на Proposed Draft Technical Specification, кому интересно. Мне, лично, не особо интересно, достаточно знать что работает. За прошедшие годы MSVC подшаманили так, что отладчик неплохо показывает на каком ты свете когда шагаешь по coroutine-enabled функции, но все же, я бы сказал, пока это не еще идеально. Хоть и пользоваться можно.

Edited Date: 2021-06-28 17:43 (UTC)

Date: 2021-06-22 09:30 (UTC)
norian: (Default)
From: [personal profile] norian
стек должны были копировать чтобы эксепшны работали, но на них походу просто забили болт, типа лапгами как-нть извратитесь если хотите, и смысла копировать нет