Когда coroutine frame выделяется и освобождается, и может ли аллокация быть элиминирована?
Фрейм выделяется (обычно в куче) один раз при первом вызове корутины и освобождается при вызове destroy(). Компилятор может элиминировать heap-аллокацию (HALO), когда время жизни корутины ему полностью видимо.
- ✗Считать, что фрейм переаллоцируется на каждом resume, а не один раз при вызове
- ✗Думать, что фрейм освобождается сам при
co_return, а не приdestroy() - ✗Полагать, что HALO происходит всегда, а не только при полностью видимом времени жизни
- →При каких условиях компилятор может применить HALO и встроить фрейм?
- →Как задать кастомный аллокатор для coroutine frame?
Жизненный цикл фрейма
Task<int> compute() {
int local = 0; // живёт в coroutine frame
co_await something(); // фрейм НЕ переаллоцируется здесь
co_return local + 1;
}
void caller() {
Task<int> t = compute(); // <-- фрейм выделяется здесь (один раз)
int r = t.get();
} // <-- ~Task() → handle.destroy() → фрейм освобождён
Аллокация происходит один раз при вызове корутины — не на каждом resume(). Размер фрейма известен на этапе компиляции, поэтому это один блок фиксированного размера. Освобождение происходит при destroy(), который обычно вызывает RAII-обёртка (~Task).
Элизия аллокации (HALO)
Если компилятор видит, что время жизни корутины полностью вложено во время жизни вызывающей стороны (корутина создана, использована и уничтожена в одной области видимости, всё инлайнится), он может убрать heap-аллокацию и разместить фрейм на стеке вызывающей стороны. Это называется HALO — Heap Allocation eLision Optimization.
// Хорошо для HALO: корутина не «убегает», всё инлайнится
int sum() {
auto t = compute(); // компилятор может разместить фрейм на стеке sum()
return t.get();
}
HALO не гарантирована стандартом — это оптимизация качества реализации. Если хэндл сохраняется, передаётся наружу или время жизни неочевидно, аллокация остаётся в куче.