Подтвердить что ты не робот

Как сопроводители реализуются в JVM-языках без поддержки JVM?

Этот вопрос появился после прочтения предложения Loom, в котором описывается подход реализации сопрограмм на языке программирования Java.

В частности, это предложение говорит о том, что для реализации этой функции на языке потребуется дополнительная поддержка JVM.

Как я понимаю, в JVM уже есть несколько языков, которые имеют сопрограммы как часть их набора функций, таких как Kotlin и Scala.

Итак, как эта функция реализована без дополнительной поддержки и может ли она быть реализована эффективно без нее?

4b9b3361

Ответ 1

tl; dr Резюме:

В частности, это предложение говорит, что для реализации этой функции на языке потребуется дополнительная поддержка JVM.

Когда они говорят "требуется", они означают "требуемые для того, чтобы быть реализованы таким образом, чтобы они были как совершенными, так и совместимыми между языками".

Итак, как эта функция реализована без дополнительной поддержки

Существует множество способов, наиболее легко понять, как это возможно (но не всегда проще реализовать) - реализовать свою собственную виртуальную машину с помощью вашей собственной семантики поверх JVM. (Обратите внимание, что это не так, как это делается на самом деле, это только интуиция относительно того, почему это можно сделать.)

и может ли он быть эффективно реализован без него?

Не совсем.

Немного более длинное объяснение:

Обратите внимание, что одна цель Project Loom - представить эту абстракцию исключительно как библиотеку. Это имеет три преимущества:

  • Намного проще представить новую библиотеку, чем изменить язык программирования Java.
  • Библиотеки могут быть немедленно использованы программами, написанными на каждом отдельном языке в JVM, тогда как функция языка Java может использоваться только Java-программами.
  • Может быть реализована библиотека с тем же API, которая не использует новые функции JVM, что позволит вам писать код, который работает на старых JVM с простой перекомпилировкой (хотя и с меньшей производительностью).

Однако реализация этого в виде библиотеки исключает умные трюки компилятора, превращающие совлокальные подпрограммы во что-то другое, потому что нет компилятора. Без умных трюков компилятора получение хорошей производительности намного сложнее, эрго, "требование" для поддержки JVM.

Более длинное объяснение:

В общем, все обычные "мощные" структуры управления эквивалентны в вычислительном смысле и могут быть реализованы друг с другом.

Наиболее известными из этих "мощных" универсальных структур управления потоком являются почтенный GOTO, другой - Continuations. Затем есть Threads и Coroutines, и тот, о котором люди не часто думают, но это также эквивалентно GOTO: Исключения.

Другая возможность - это повторный стек вызовов, так что стек вызовов доступен как объект для программиста и может быть изменен и переписан. (Многие диалекты Smalltalk делают это, например, и это также похоже на то, как это делается в C и сборке.)

Пока у вас есть один из них, вы можете иметь все это, просто используя один поверх другого.

JVM имеет два из них: Исключения и GOTO, но GOTO в JVM не универсален, он крайне ограничен: он работает только в одном методе. (Он по существу предназначен только для циклов.) Таким образом, это оставляет нас с Исключениями.

Итак, это один из возможных ответов на ваш вопрос: вы можете реализовать совлокальные подпрограммы поверх Исключения.

Другая возможность - не использовать весь поток управления JVM и реализовать собственный стек.

Однако это обычно не путь, который фактически выполняется при реализации совлокальных подпрограмм на JVM. Скорее всего, кто-то, кто реализует совлокальные подпрограммы, предпочтет использовать батуты и частично переопределить контекст выполнения в качестве объекта. То есть, например, как генераторы реализованы в C♯ в CLI (а не JVM, но проблемы схожи). Генераторы (которые в основном ограничены полукоординатами) в C♯ реализуются путем подъема локальных переменных метода в поля объекта контекста и разделения метода на несколько методов на этом объекте в каждом выражении yield, преобразование их в машину состояний и тщательно пронизывать все изменения состояния через поля объекта контекста. И до того, как async/await появился в качестве языковой функции, умный программист реализовал асинхронное программирование с использованием того же механизма.

ОДНАКО, и именно в этой статье, на которую вы указали, скорее всего, говорилось: все эти машины стоят дорого. Если вы реализуете свой собственный стек или поднимите контекст выполнения на отдельный объект или скомпилируете все свои методы в один гигантский метод и повсеместно используйте GOTO (что даже невозможно из-за ограничения размера для методов) или используйте Исключения как поток управления, по крайней мере одна из этих двух вещей будет правдой:

  • Соглашения о вызове становятся несовместимыми с раскладкой стека JVM, которую ожидают другие языки, т.е. вы теряете функциональную совместимость.
  • Компилятор JIT не имеет представления о том, что, черт возьми, делает ваш код, и представлен шаблонами байтового кода, шаблонами потока выполнения и шаблонами использования (например, бросать и ловить большие суммы исключений), он не ожидает и не делает " t знать, как оптимизировать, то есть вы теряете производительность.

Богатый Хикки (разработчик Clojure) однажды сказал в разговоре: "Хвост звонков, производительность, вмешательство. Выберите два". Я обобщил это на то, что я называю Хикки Максимом: "Advanced Control-Flow, Performance, Interop. Pick Two".

На самом деле, как правило, трудно достичь даже одного из взаимодействий или производительности.

Кроме того, ваш компилятор станет более сложным.

Все это исчезает, когда конструкция доступна изначально в JVM. Представьте себе, например, если в JVM не было потоков. Затем всякая реализация языка создавала бы собственную библиотеку Threading, которая была бы сложной, сложной, медленной и не взаимодействовала бы с какой-либо другой реализацией на языке Threading.

Недавний и реальный пример - lambdas: во многих языковых реализациях JVM были lambdas, например. Scala. Затем Java добавила lambdas, но поскольку JVM не поддерживает lambdas, они должны быть каким-то образом закодированы, а кодировка, выбранная Oracle, отличается от той, которую выбрал ранее Scala, а это означает, что вы не могли пройти Java lambda для метода Scala, ожидающего Scala Function. Решение в этом случае состояло в том, что разработчики Scala полностью переписали свою кодировку lambdas для совместимости с кодировкой, выбранной Oracle. В некоторых местах это фактически нарушило совместимость.

Ответ 2

Из Документация Котлина на Coroutines (выделено мной):

Coroutines упрощают асинхронное программирование, помещая сложности в библиотеки. Логика программы может быть выражена последовательно в сопрограмме, а основная библиотека будет определять асинхронность для нас. Библиотека может переносить соответствующие части кода пользователя в обратные вызовы, подписываться на соответствующие события, выполнять расписание на разных потоках (или даже на разных машинах!), а код остается таким же простым, как если бы он выполнялся последовательно.

Короче говоря, они скомпилированы до кода, который использует обратные вызовы и конечный автомат для обработки приостановки и возобновления.

Роман Элизаров, руководитель проекта, дал две фантастические беседы в KotlinConf 2017 по этому вопросу. Один из них - Введение в Coroutines, второй - Deep Dive on Coroutines.

Ответ 3

Coroutines не полагаются на функции операционной системы или JVM. Вместо этого сопрограммы и функции suspend преобразуются компилятором, производящим машину состояний, способную вообще обрабатывать приостановки и пропускать приостанавливающие сопрограммы, сохраняя их состояние. Это включено Continuations, которые добавляются как параметр для каждой приостанавливающей функции компилятором; этот метод называется " Стиль пропуски продолжения". (CPS).

Один пример можно наблюдать при преобразовании функций suspend:

suspend fun <T> CompletableFuture<T>.await(): T

Ниже показано его подпись после преобразования CPS:

fun <T> CompletableFuture<T>.await(continuation: Continuation<T>): Any?

Если вы хотите узнать жесткие детали, вам нужно прочитать это объяснение.