2021-06-21

vak: (Default)
Судя по комментам к предыдущему посту, существует устойчивое подозрение, что сопрограммы имеют стек, и вообще кушают ресурсы. Хорошо, давайте померяем, это нетрудно. Создадим миллион сопрограмм и глянем расход памяти. Вот такой тестик.
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 байтов на одну сопрограмму. Вполне недорогое удовольствие получается. Конечно, никакого собственного стека у сопрограмм не имеется, просто негде.
vak: (Default)
Давайте изобразим на сопрограммах тривиальный симулятор. Пусть у нас есть класс simulator_t, его реализующий.

Добавим модельное время: целое число, монотонно увеличивающееся по мере выполнения. Пусть метод simulator_t::time() возвращает это время. Другой метод simulator_t::delay(N) пусть приостанавливает текущий процесс на указанное число тактов.

Моделируемые процессы будем представлять в виде отдельных независимых сопрограмм. В начале мы создаём эти процессы посредством метода simulator_t::make_process(), затем запускаем симуляцию методом simulator_t::run().

Вот так выглядит главная программа:
int main()
{
simulator_t sim;
sim.make_process("clock", clock);
sim.make_process("master", master);
sim.run();
}
Первый процесс - условный генератор синхросигнала. Через каждую единицу времени он изменяет значение сигнала Clock с 0 на 1 и наоборот. Сам сигнал добавим позже, в следующем посте, а пока просто печатаем строчку. Задержка реализуется оператором "co_await sim.delay(N)".
co_void_t clock(simulator_t &sim)
{
for (;;) {
std::cout << '(' << sim.time() << ") Clock set" << std::endl;
co_await sim.delay(1);
std::cout << '(' << sim.time() << ") Clock reset" << std::endl;
co_await sim.delay(1);
}
}
Второй процесс как бы наблюдает за выполнением и завершает симуляцию через 10 единиц времени:
co_void_t master(simulator_t &sim)
{
std::cout << '(' << sim.time() << ") Started" << std::endl;
co_await sim.delay(5);
std::cout << '(' << sim.time() << ") Half way through" << std::endl;
co_await sim.delay(5);
std::cout << '(' << sim.time() << ") Finished" << std::endl;
sim.finish();
}
Компилируем и запускаем:
$ make
g++-10 -std=c++20 -fcoroutines -c -o demo1.o demo1.cpp
g++-10 -std=c++20 -fcoroutines -c -o simulator.o simulator.cpp
g++-10 -std=c++20 -fcoroutines demo1.o simulator.o -o demo1
$ ./demo1
(0) Started
(0) Clock set
(1) Clock reset
(2) Clock set
(3) Clock reset
(4) Clock set
(5) Half way through
(5) Clock reset
(6) Clock set
(7) Clock reset
(8) Clock set
(9) Clock reset
(10) Finished
Как мы можем видеть, симулятор выполняет эти две сопрограммы как бы параллельно, переключаясь между ними при необходимости по мере продвижения модельного времени.

Исходники можно глянуть здесь: https://github.com/sergev/simulation-with-coroutines