Достучаться до небес — Корутины, Горутины и прочие Рутины

Смотрели Достучаться до небес? Ахеренный фильм, всем советую. У меня всё. Или нет?

Итак, шёл вторник, 17 октября, обычный день. На хабре появилась крутая статейка — https://habrahabr.ru/post/339618/ — Послевкусие от Kotlin, часть 3. Корутины — делим процессорное время. Эта серия статей от независимого разработчика (он не из Jetbrains, пока), в которых рассматривается опыт использования Котлина в продакшене. Эта статья была конкретно посвещена Корутинам и их перфомансу.

В комментариях к статье я высказал своё мнение, что:

  1. Корутины от Котлина медленные (по сравнению с Горутинами).
  2. Котлиновские Корутины можно использовать лишь для натуральных асинхронных задач (например, загрузка превьюшек статей на мобильном приложении в бэграунде), а не для выполнения CPU-bound вычислений.

Меня в комментариях несколько опустили, сказав, что мои рассуждения неверны, и вообще я дурачок. Пришлось пойти в интернеты и почитать, как всё на самом деле.

В расследование вопроса производительности Котлиновских Корутин и Гошных Горутин я использовал 3 источника:

  1. https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md#implementation-details — официальное описание реалиции Котлиновских Корутин от создателей языка.
  2. https://groups.google.com/forum/#!msg/golang-nuts/j51G7ieoKh4/wxNaKkFEfvcJ — Ответ разработчиков языка Go, о том, как работают Горутины, и на сколько и почему они быстрые.
  3. https://stackoverflow.com/q/46864623/4167563 — ответ Романа Елизарова — одного из ключевых разработчиков Котлина, а также создателя Корутин в этом языке. Достучаться до небес, дотянуться до звёзд…

Благодаря этим ссылкам я получил конечную картину видения Котлиновских Корутин и Гошных Горутин.

Котлиновские Корутины:

  • Это stackless корутины. Корутины реализованы с помощью компиляции вашего кода в Конечный автомат. Пример финального кода вы можете найти по ссылке на гитхаб. Всё состояние корутины хранится не на стэке (поэтому и stackless), а в Хипе. Для сохранения состояния используется специальный объект — Контекст. Пустая корутина занимает всего несколько байтов в Хипе.
  • В Котлиновских корутинах возможна любая вложенность suspended functions — замореженных функций. То есть, из каждой новой корутины вы можете вызывать опять корутину, и так бесконечно. Переполнение стека не возможно. Причина — как раз таки Stackless. Состояние хранится в специальном объекте, а набор вызовов Замороженных функций сконвертируется в linked list этих самых объектов. Это сказал Роман, прочитать его слова можете по ссылке на StackOverflow.
  • В котлиновских корутинах вы полностью сами управляете Пулом потоков, на которых раннаются таски. Как следствие, это даёт больше гибкости, но и можно написать херовый код, который забьёт всё CPU, и программа будет тратить всё время на thread context switch.
  • В котлиновских корутинах вы сами решаете, где функция должна заморозиться. Это даёт больше гибкости опять-таки.
  • Котлиновская Корутина — это Тупо набор независимых Таксов, отсортированный по порядку выполнения. Таски выполняются одна за одной. Каждая таска может выполниться на любом доступном Треде.

Гошные Горутины:

  • Это stackful coroutines. Всё состояние хранится на стеке. Поэтому, если очень постараться, сделать милилард вложенных вызовов, то можно сделать себе переполнение стека.
  • Нет возможности задать определенный пул потоков. Все горутины бегут в едином пуле, который определяетя самим языком. Пользователь лишь только может задать количество потоков этого пула. По умолчанию, число потоков совпадает с числом процессеров вашей машины.
  • В горутинах нет возможности определять точки заморозки в функциях. Рантайм Го сам решает, где нужно передать выполнение от одной горутины к другой. Эти точки передачи вызовов определены в спеке языка. В частности, это места IO, а также блокировки на примитивах синхронизации.
  • Переключение между двумя Горутинами — супер дешевое, O(1), то есть, не зависит от количества созданных горутин в системе. Всё, что нужно сделать для переключения — это поменять 3 регистра — Program counter, Stack Pointer и DX.

Что же лучше? Зависит, очень сильно. Нужно брать решения на обоих языках и мерить, по другому никак. В котлине пустые корутины более дешевые, чем в Го. Зато переключение контекста в Го — дешевле не порядок. Поэтому, если у вас будет часто выбираться новая Корутина для выполнения действия, то Го — ваш выбор. Если у вас возможен миллион вложенных вызовов — стоит посмотреть на Котлин, где гарантированно нет переполнения стэка.

Также стоит чётко подумать, IO-bound у вас задачи в Корутинах, или CPU-bound. Если первое, возможно, удивительно, но самое быстрое для вас будет — это исопльзование одного потока. И тут есть one thing. Котлин позволит создать набор корутин поверх Тред Пула из 1 потока, а в Го — вы так не сможете. Поэтому ещё раз, мерьте, не стесняйтесь.

Категории: О жизни