Являются ли сопрограммы C ++ 20 без стека проблемой?

Исходя из следующего, похоже, что сопрограммы в C ++ 20 будут без стеков.

https://en.cppreference.com/w/cpp/language/coroutines

Я обеспокоен по многим причинам:

  1. Во встроенных системах распределение кучи часто недопустимо.
  2. Когда в низкоуровневом коде было бы полезно вложение co_await (я не верю, что совместные подпрограммы без стека позволяют это).

При использовании сопрограммы без стека может быть приостановлена ​​только процедура верхнего уровня. Любая процедура, вызываемая этой процедурой верхнего уровня, сама по себе не может быть приостановлена. Это запрещает предоставлять операции приостановки / возобновления в подпрограммах в библиотеке общего назначения.

https://www.boost.org/doc/libs/1_57_0/libs/coroutine/doc/html/coroutine/intro.html#coroutine.intro.stackfulness

  1. Более подробный код из-за необходимости пользовательских распределителей и пула памяти.

  2. Медленнее, если задача ожидает, пока операционная система выделит ей некоторую память (без объединения памяти).

Учитывая эти причины, я действительно надеюсь, что я очень ошибаюсь относительно того, каковы текущие сопрограммы.

Вопрос состоит из трех частей:

  1. Почему C ++ решил использовать сопрограммы без стека?
  2. Относительно распределений для сохранения состояния в подпрограммах без стека. Могу ли я использовать alloca (), чтобы избежать любых распределений кучи, которые обычно используются для создания сопрограммы.

Состояние сопрограммы распределяется в куче через оператор не-массива new. https://en.cppreference.com/w/cpp/language/coroutines

  1. Мои предположения о сопрограммах c ++ неверны, почему?

РЕДАКТИРОВАТЬ:

Сейчас я прохожу переговоры cppcon для сопрограмм, если я найду какие-либо ответы на свой вопрос, я опубликую их (пока ничего).

CppCon 2014: Гор Нишанов «ждите 2.0: бессменные восстанавливаемые функции»

https://www.youtube.com/watch?v=KUhSjfSbINE

CppCon 2016: Джеймс МакНеллис «Введение в сопрограммы C ++»

https://www.youtube.com/watch?v=ZTqHjjm86Bw

44
задан David Ledger 23 July 2019 в 15:39
поделиться

3 ответа

Передайте: Когда в этом сообщении говорятся просто "сопрограммы", я обращаюсь к понятие сопрограммы, не определенный C++ 20 функций. При разговоре об этой функции я назову его" co_await" или "co_await сопрограммы".

На динамическом выделении

Cppreference иногда использует более свободную терминологию, чем стандарт. co_await, поскольку функция "требует" динамического выделения; прибывает ли это выделение из "кучи" или из статического блока памяти или независимо от того, что вопрос для поставщика выделения. Такие выделения могут игнорироваться при произвольных обстоятельствах, но так как стандарт не обстоятельно объясняет их, все еще необходимо предположить, что любая co_await сопрограмма может динамично выделить память.

co_await сопрограммы действительно имеют механизмы для пользователей для обеспечения выделения для состояния сопрограммы. Таким образом, можно заменить помещать в "кучу"/освобождать выделением хранилища какой-то конкретный пул памяти, Вы предпочитаете.

co_await, поскольку функция хорошо разработана к [1 123], удаляют многословие из точки использования для любого co_await - способные объекты и функциональность. co_await оборудование является невероятно сложным и сложным с большим количеством взаимодействий между объектами нескольких типов. Но в приостановить/возобновить точке, это всегда похоже co_await <some expression>. Добавление поддержки средства выделения Вашим awaitable объектам и обещаниям требует некоторого многословия, но то многословие живет за пределами места, где те вещи привыкают.

Используя alloca для сопрограммы было бы... очень несоответствующим для [1 125] большинство использование co_await. В то время как дискуссия по поводу этой функции пытается скрыть его, факт вопроса - то, что co_await, поскольку функция разработана для асинхронного использования. Это - его намеченная цель: остановить выполнение функции и запланировать возобновление той функции на потенциально другой поток, затем пася любое в конечном счете сгенерированное значение к некоторому коду получения, который может быть несколько удален от кода, который вызвал сопрограмму.

alloca не подходит для того конкретного варианта использования, так как вызывающая сторона сопрограммы позволяется/поощряется для движения, делают что так, чтобы значение могло быть сгенерировано некоторым другим потоком. Место, выделенное [1 110], поэтому больше не существовало бы, и это довольно плохо для сопрограммы, которая живет в нем.

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

Теперь, там обстоятельства, где выделение на месте было бы соответствующим. Варианты использования генератора для того, когда Вы хотите, по существу приостанавливают функцию и возвращают значение, затем берут, куда брошенная функция и потенциально возвращает новое значение. В этих сценариях стек для функции, которая вызывает сопрограмму, конечно, все еще будет вокруг.

co_await поддержки такие сценарии (хотя co_yield), но это делает так меньше оптимальным способом, по крайней мере, с точки зрения стандарта. Поскольку функция разработана для-и приостановки, превращение ее в сопрограмму приостанавливания вниз имеет эффект наличия этого динамического выделения, которое не должно быть динамично.

Поэтому стандарт не требует динамического выделения; если компилятор достаточно умен для обнаружения шаблона генератора использования, то это может удалить динамическое выделение и просто выделить место на локальном стеке. Но снова, это - то, что компилятор может делать, не должен сделать.

В этом случае, alloca - базирующееся выделение было бы соответствующим.

, Как это вошло в стандарт

, короткая версия - то, что это вошло в стандарт, потому что люди позади него вставили работу, и люди позади альтернатив не сделали.

Любая идея сопрограммы является сложной, и всегда будут вопросы о implementability относительно них. Например," resumeable функции " предложения выглядели большими, и я буду любить видеть его в стандарте. Но никто на самом деле не реализовал это в компиляторе. Таким образом, никто не мог доказать, что это была на самом деле вещь, Вы могли сделать. Ну конечно это звуки реализуемый, но это не означает, что это реализуемо.

Помнят , что произошло, прошлый раз "звуки, реализуемые", использовался в качестве основания для принятия функции.

Вы не хотите стандартизировать что-то, если Вы не знаете, что это может быть реализовано. И Вы не хотите к standadize что-то, если Вы не знаете, решает ли он на самом деле намеченную проблему.

Gor Nishanov и его команда в Microsoft вставляют работу для реализации co_await. Они сделали это для [1 131] годы , совершенствовав их реализацию и т.п.. Другие люди использовали свою реализацию в фактическом производственном коде и казались довольно удовлетворенными его функциональностью. Лязг даже реализовал его. Так же, как мне лично не нравится он, это бесспорно, что co_await зрелы функция.

, В отличие от этого, "базовые сопрограммы" альтернативы, которые были подняты год назад, поскольку конкурирующим идеям с [1 116] не удалось нарастить обороты частично, потому что их было трудно реализовать . Вот почему co_await был принят: потому что это был доказанный, старый, и звуковой инструмент, что люди хотели и имели продемонстрированную способность улучшить их код.

co_await не для всех. Лично, я не буду, вероятно, использовать его очень, поскольку волокна работают намного лучше на мои варианты использования. Но это очень хорошо для своего определенного варианта использования:-и приостановка.

36
ответ дан Nicol Bolas 4 August 2019 в 17:05
поделиться

сопрограммы без стека

  • сопрограммы без стека (C++ 20) делают преобразование кода (конечный автомат)
  • без стека в этом средстве случая, что стек приложений не используется для хранения локальных переменных (например, переменные в алгоритме)
  • иначе, локальные переменные сопрограммы без стека были бы перезаписаны вызовами обычных функций после приостановки сопрограммы
  • без стека сопрограмм, для без стека действительно нужна память для хранения локальных переменных также, особенно если сопрограмма приостановлена, локальные переменные должны быть сохранены
  • для этой цели сопрограммы, без стека выделяют и используют так называемую запись активации (эквивалентный стековому фрейму)
  • , запись активации не должна находиться на основная стопка потока
  • , приостановка от глубокого стека вызовов только возможна, если все промежуточные функции являются сопрограммами без стека также ( вирусный ; иначе Вы добрались бы поврежденный стек )
  • , сопрограмма без стека не может пережить свою вызывающую сторону/создателя
  • , некоторые разработчики лязга скептичны , что Выделение "кучи" eLision Оптимизация (Halo) может всегда применяться

stackfull сопрограммы

  • в его сущности stackful сопрограмма просто , указатель вершины стека переключателей и указатель команд
  • выделяют стек стороны, который работает как обычный стек (хранящий локальные переменные, совершенствуя указатель вершины стека для вызванных функций)
  • , стек стороны должен быть выделен только однажды (может также быть объединен), и все последующие вызовы функции быстры (потому что, только совершенствуя указатель вершины стека)
  • каждый, какие сопрограммы без стека требуют его собственной записи активации-> названный в глубокой цепочке вызовов, много записей активации должно быть создано/выделено
  • , stackful сопрограммы позволяют приостанавливать от глубокой цепочки вызовов, в то время как промежуточные функции могут быть обычными функциями ( не вирусный )
  • , stackfull сопрограмма может пережить свою вызывающую сторону/создателя
  • одна версия икры сравнительных тестов skynet 1 миллион stackful сопрограмм и показывает, что stackful сопрограммы очень эффективны (поражение версии с помощью потоков)
  • , версия сравнительного теста skynet с помощью сопрограмм без стека еще не была реализована
  • , boost.context представляет основная стопка потока как stackful сопрограмма/волокно - даже на поддержках ARM
  • boost.context по требованию растущие стеки (стопки разделения GCC)
2
ответ дан xlrg 4 August 2019 в 17:05
поделиться
  • 1
    Я подвергаю сомнению это. Имеет смысл помещать KnownType (typeof (SomeData)) на базовом классе (ConsoleData), позволяя Вам передать дочерний экземпляр методу, ожидая основной экземпляр. Но я don' t думают, что необходимо поместить его на дочерний экземпляр. – Andrew Shepherd 18 December 2011 в 22:02

Я использую сопрограммы без стека на маленьких целях Коры-M0 ARM жесткого реального времени с 32 КБ RAM, где нет никакого подарка средства выделения "кучи" вообще: вся память статически предварительно выделяется. Сопрограммы без стека являются судьбоносными, и stackful сопрограммами, которые я ранее использовал, были боль для разбираний и был по существу взлом полностью на основе определенного для реализации поведения. При движении от той путаницы до совместимого стандартами, портативного C++, было замечательно. Я дрожу, чтобы думать, что кто-то мог бы предложить возвратиться.

  • сопрограммы Stackless не подразумевают использование "кучи": Вы имеете полный контроль по тому, как кадр сопрограммы выделяется (через void * operator new(size_t) участник в типе обещания).

  • co_await может быть вложен очень хорошо , на самом деле это - случай общего использования.

  • сопрограммы Stackful должны выделить те стеки где-нибудь также, и, возможно, нелепо, что они не могут использовать основную стопку потока для этого . Эти стеки выделяются на "куче", возможно, через средство выделения пула, которое получает блок от "кучи" и затем подразделяет ее.

  • реализации сопрограммы Stackless могут игнорировать выделение кадра, такое, что обещание operator new не называют вообще, тогда как stackful сопрограммы всегда выделяют стек для сопрограммы, или необходимый или нет, потому что компилятор не может помочь времени выполнения сопрограммы с игнорированием его (по крайней мере, не в C/C++).

  • выделения могут игнорироваться точно при помощи стека, где компилятор может доказать, что жизнь сопрограммы не оставляет объем вызывающей стороны. И это - единственный способ, которым можно использовать alloca. Так, компилятор уже заботится о нем для Вас. Насколько прохладный это!

    Теперь, нет никакого требования, чтобы компиляторы на самом деле сделали этот elision, но AFAIK, все реализации там делают это с некоторыми нормальными пределами на то, насколько сложный, что "доказательство" может быть - в некоторых случаях, это не разрешимая проблема (IIRC). Плюс, легко проверить, сделал ли компилятор, как Вы ожидали: если Вы знаете, что все сопрограммы с конкретным типом обещания только вкладываются (разумный в маленьких встроенных проектах, но не только!), можно объявить operator new в типе обещания, но не определить его, и затем код не свяжется, если компилятор "провалил".

    прагма А могла быть добавлена к конкретной реализации компилятора, чтобы объявить, что конкретный кадр сопрограммы не выходит, даже если компилятор не достаточно умен для доказательства его - я не проверял, потрудился ли кто-либо писать их уже, потому что мои варианты использования достаточно разумны, что компилятор всегда делает правильную вещь.

    Память, выделенная с alloca, не может использоваться после возврата от вызывающей стороны. вариант использования для alloca, на практике, должен быть немного большим количеством портативного способа выразить переменный размер gcc автоматическое расширение массива.

В по существу всех реализациях stackful сопрограмм в подобном C lanaguages, одно и только [1 111] предполагаемых "преимуществ" stackfull-мыса - то, что к кадру получают доступ с помощью обычного относительного указателем базы обращения, и push и pop в соответствующих случаях, таким образом, "простой" код C может работать на этом искусственном стеке без изменений в генераторе кода. Никакие сравнительные тесты не поддерживают этот способ мышления, тем не менее, если у Вас есть много активных сопрограмм - это - прекрасная стратегия, если существует ограниченное количество их, и у Вас есть память для траты для запуска с.

Стек должен быть сверхвыделен, уменьшив местность ссылки: типичная stackful сопрограмма использует полную страницу для стека в минимуме, и стоимость предоставления доступа к этой странице не совместно используется ни с чем больше: единственная сопрограмма должна перенести все это. Вот почему стоило разработать Python без стека для многопользовательских игровых серверов.

, Если существует несколько сопрограмм только - без проблем. Если у Вас будут тысячи сетевых запросов все обработанные stackful сопрограммами с легким сетевым стеком, который не налагает наверху, который монополизирует производительность, то счетчики производительности для неудачных обращений в кэш заставят Вас кричать. Как Nicol заявил в другом ответе, это становится несколько менее релевантным больше слоев, там между сопрограммой и безотносительно асинхронной операции, которую это обрабатывает.

Это были давно любые 32 +-bit, ЦП имел выигрыши в производительности, свойственные к доступу к памяти через какой-то конкретный способ адресации. То, что имеет значение, является благоприятными для кэша схемами доступа и усиливающий упреждающую выборку, предсказание ветвлений и спекулятивное выполнение. Страничная память и ее запоминающее устройство являются всего двумя дальнейшими уровнями кэша (L4 и L5 на настольных центральных процессорах).

  1. , Почему C++ принял бы решение использовать сопрограммы без стека? , поскольку они работают лучше, и не хуже. На стороне производительности могут быть только преимущества для них. Таким образом, это - легкая задача, мудрая производительностью, чтобы просто использовать их.

  2. Может я использовать alloca () для предотвращения любых выделений "кучи", которые обычно использовались бы для создания сопрограммы. номер. Это было бы решение несуществующей проблемы. Сопрограммы Stackful на самом деле не выделяют на существующем стеке: они создают новые стеки, и они выделяются на "куче" по умолчанию, как кадры сопрограммы C++ были бы (по умолчанию).

  3. мои предположения о сопрограммах C++ неправильно, почему? Посмотрите выше.

  4. [еще 1115] подробный код из-за потребности в пользовательских средствах выделения и объединении памяти. , Если Вы хотите, чтобы stackful сопрограммы работали хорошо, Вы будете делать то же самое для управления областями памяти для стеков, и оказывается, что это еще более твердо. Необходимо минимизировать отходы памяти, и таким образом необходимо минимально сверхвыделить стек для варианта использования на 99,9% и соглашение так или иначе с сопрограммами, которые исчерпывают этот стек.

    Один способ, которым я имел дело с ним в C++, был путем выполнения стека, регистрируется в точках разветвления, где анализ кода указывает, что больше стека может быть необходимо, тогда если стек переполнился бы, исключение было выдано, отмененная работа сопрограммы (дизайн системы должен был поддерживать его!), и затем работа перезапущена с большим количеством стека. Это - простой способ быстро потерять преимущества плотно упакованного стека-fuls. О, и я должен был обеспечить свое собственное __cxa_allocate_exception, чтобы это работал. Забава, а?

Еще одна история: я играю с использованием сопрограмм в драйверах привилегированного режима Windows, и там stacklessness действительно имеет значение - до такой степени, что, если аппаратные средства позволяют, можно выделить буфер пакетов и кадр сопрограммы вместе, и эти страницы прикрепляются, когда они отправлены сетевому оборудованию для выполнения. Когда обработчик прерываний возобновляет сопрограмму, страница там, и если сетевая плата позволяет, это могло бы даже выбрать его с упреждением для Вас так, это будет в кэше. Таким образом, это работает хорошо - это - всего один вариант использования, но так как Вы хотели встроенный - я встроил:).

, возможно, не распространено думать о драйверах на настольных платформах как "встроенный" код, но я вижу много общих черт, и необходимо встроенное мышление. Последней вещью, которую Вы хотите, является код ядра, который выделяет слишком много, особенно если это добавило бы на поток наверху. Типичный настольный ПК имеет несколько тысяч существующих потоков, и многие из них там для обработки ввода-вывода. Теперь вообразите бездисковую систему, которая использует устройство хранения данных iSCSI. В такой системе что-либо, что связал ввод-вывод, это не связывается с USB, или GPU будет связан с сетевым оборудованием и сетевым стеком.

Наконец: Доверительные сравнительные тесты, не я, и читают ответ Nicol также! . Моя перспектива формируется моими вариантами использования - я могу сделать вывод, но я не требую никакого собственного опыта с сопрограммами в коде "универсала", где производительность вызывает меньше беспокойства. Выделения "кучи" для сопрограмм без стека очень часто едва примечательны в трассировках производительности. В коде приложения общего назначения это редко будет проблемой. Это действительно становится "интересным" в коде библиотеки, и некоторые шаблоны должны быть разработаны, чтобы позволить пользователю библиотеки настраивать это поведение. Эти шаблоны будут найдены и популяризированы, поскольку больше библиотек использует сопрограммы C++.

54
ответ дан 26 November 2019 в 17:22
поделиться
Другие вопросы по тегам:

Похожие вопросы: