Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Thread Wars: Project Loom Strikes Back

Thread Wars: Project Loom Strikes Back

На фоне приближающегося к релизу проекта Loom в Java-мире только и разговоров, что о корутинах да о легковесной многопоточности!

Но ведь Java на этом поле далеко не первая, скорее наоборот: это один из последних современных языков, где добавляют корутины. Так что же, мы просто получаем в точности то, что уже есть у соседей? Отличаются ли чем-нибудь корутины в Java от корутин в Kotlin? А от горутин в Go? Наши корутины будут лучше или хуже? И почему их так долго делают, раз в других языках все уже давно есть?

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

Ivan Ugliansky

July 20, 2022
Tweet

More Decks by Ivan Ugliansky

Other Decks in Programming

Transcript

  1. questions • Java повторяет за Котлином? • Котлин теперь перейдет

    на Loom? • Почему так долго делали? Вон в Котлине давно уже…
  2. • А вообще уже работает? Пора переходить? • У меня

    теперь все ускорится? • У меня теперь все замедлится? questions • Java повторяет за Котлином? • Котлин теперь перейдет на Loom? • Почему так долго делали? Вон в Котлине давно уже…
  3. agenda 1. Разбираемся с проблемой и терминологией 2. Сравниваем Loom

    c другими реализациями 3. Loom: смотрим на подводные камни
  4. Хотим писать код параллельный: Thread t = new Thread(() ->

    { int v = foo(); v += 2; bar(v); }); t.start(); ... t.join(); the problem
  5. А как реализовать java.lang.Thread? Очевидное решение – встать на плечи

    гигантов! java.lang.Thread <=> OS-thread (1:1 схема, один к одному) the problem
  6. java.lang.Thread <=> OS-thread + в JVM ничего делать не нужно

    + scheduler, над которым работают 50 лет the problem
  7. java.lang.Thread <=> OS-thread + в JVM ничего делать не нужно

    + scheduler, над которым работают 50 лет − ограничение по количеству − долгое создание − ограниченный стек the problem
  8. final int count = 100_000; final Thread[] threads = new

    Thread[count]; for (int i = 0; i < count; i++) { System.out.println("Starting thread #" + i); final int n = i; threads[i] = new Thread(() -> { Thread.sleep(Duration.ofSeconds(10)); System.out.println("Thread #" + n + " finished"); }); threads[i].start(); } for (int i = 0; i < count; i++) { threads[i].join(); } the problem
  9. final int count = 100_000; final Thread[] threads = new

    Thread[count]; for (int i = 0; i < count; i++) { System.out.println("Starting thread #" + i); final int n = i; threads[i] = new Thread(() -> { Thread.sleep(Duration.ofSeconds(10)); System.out.println("Thread #" + n + " finished"); }); threads[i].start(); } for (int i = 0; i < count; i++) { threads[i].join(); } the problem
  10. final int count = 100_000; final Thread[] threads = new

    Thread[count]; for (int i = 0; i < count; i++) { System.out.println("Starting thread #" + i); final int n = i; threads[i] = new Thread(() -> { Thread.sleep(Duration.ofSeconds(10)); System.out.println("Thread #" + n + " finished"); }); threads[i].start(); } for (int i = 0; i < count; i++) { threads[i].join(); } Starting thread #10775 Starting thread #10776 Starting thread #10777 Starting thread #10778 Starting thread #10779 Starting thread #10780 [1.232s][warning][os,thread] Failed to start thread "Unknown thread" – pthread_create failed (EAGAIN) for attributes: stacksize: 1024k, guardsize: 0k, detached. [1.232s][warning][os,thread] Failed to start the native thread for java.lang.Thread "Thread-10780" the problem
  11. final int count = 100_000; final Thread[] threads = new

    Thread[count]; for (int i = 0; i < count; i++) { System.out.println("Starting thread #" + i); final int n = i; threads[i] = new Thread(() -> { Thread.sleep(Duration.ofSeconds(10)); System.out.println("Thread #" + n + " finished"); }); threads[i].start(); } for (int i = 0; i < count; i++) { threads[i].join(); } Starting thread #10775 Starting thread #10776 Starting thread #10777 Starting thread #10778 Starting thread #10779 Starting thread #10780 [1.232s][warning][os,thread] Failed to start thread "Unknown thread" – pthread_create failed (EAGAIN) for attributes: stacksize: 1024k, guardsize: 0k, detached. [1.232s][warning][os,thread] Failed to start the native thread for java.lang.Thread "Thread-10780" the problem
  12. java.lang.Thread <=> OS-thread Следствие реализации: o Треды – дорогие! Лишний

    раз их создавать не стоит (а хочется) o Значит треды будем пулить the problem
  13. final int count = 100_000; final Thread[] threads = new

    Thread[count]; for (int i = 0; i < count; i++) { System.out.println("Starting thread #" + i); final int n = i; threads[i] = new Thread(() -> { Thread.sleep(Duration.ofSeconds(10)); System.out.println("Thread #" + n + " finished"); }); threads[i].start(); } for (int i = 0; i < count; i++) { threads[i].join(); } thread pool to the rescue
  14. ExecutorService es = Executors.newFixedThreadPool(4); for (int i = 0; i

    < 100_000; i++) { System.out.println("Submitting task #" + i); es.execute(() -> { Thread.sleep(Duration.ofSeconds(10)); }); } thread pool to the rescue
  15. ExecutorService es = Executors.newFixedThreadPool(4); for (int i = 0; i

    < 100_000; i++) { System.out.println("Submitting task #" + i); es.execute(() -> { Thread.sleep(Duration.ofSeconds(10)); }); } thread pool to the rescue
  16. Вместо тредов таски в тредпуле: + Больше не боимся ограничений

    OS! + Специализированные scheduler-ы thread pool to the rescue
  17. Вместо тредов таски в тредпуле: + Больше не боимся ограничений

    OS! + Специализированные scheduler-ы − Долгий (бесконечный) таск забирает OS-thread thread pool to the rescue
  18. Блокирующие вызовы: o Ожидание ответа по сети o Ожидание окончания

    файловой операции o Ожидание монитора o Ожидание по таймауту (Thread.sleep) o … thread pool to the rescue
  19. j.l.Thread j.l.Thread j.l.Thread j.l.Thread OS-thread OS-thread OS-thread OS-thread Tasks Долгий

    таск Thread pool socketChannel.read(buffer); thread pool to the rescue
  20. j.l.Thread j.l.Thread j.l.Thread j.l.Thread OS-thread OS-thread OS-thread OS-thread Tasks Долгий

    таск Thread pool Блокирующий вызов Блокирующий вызов Блокирующий вызов thread pool to the rescue
  21. Вместо тредов таски в тредпуле: + Больше не боимся ограничений

    OS! + Специализированные scheduler-ы − Долгий (бесконечный) таск забирает OS-thread − Блокирующие вызовы, как крайний случай thread pool to the rescue
  22. to block or not to block Блокирующие вызовы: o Ожидание

    ответа по сети o Ожидание окончания файловой операции o Ожидание монитора o Ожидание по таймауту (Thread.sleep) o …
  23. Блокирующие вызовы: o Ожидание ответа по сети o Ожидание окончания

    файловой операции o Ожидание монитора o Ожидание по таймауту (Thread.sleep) o … Ожидание реакции OS. to block or not to block
  24. Блокирующие вызовы: o Ожидание ответа по сети o Ожидание окончания

    файловой операции o Ожидание монитора o Ожидание по таймауту (Thread.sleep) o … Ожидание реакции OS. Но можно и не ждать! to block or not to block
  25. OS предаставляет асинхронный API (EPOLL) Вместо занимающего тред ожидания ответа

    подписываемся на событие. Когда оно случится, вызывается колбек. to block or not to block
  26. Через EPOLL реализованы асинхронные API для работы с сетью и

    файловой системой в managed языках. AsynchronousSocketChannel AsynchronousFileChannel ... to block or not to block
  27. Через EPOLL реализованы асинхронные API для работы с сетью и

    файловой системой в managed языках. <A> void read(ByteBuffer dst, long position, A attachment, CompletionHandler<Integer,? super A> handler); callback to block or not to block
  28. j.l.Thread j.l.Thread j.l.Thread j.l.Thread OS-thread OS-thread OS-thread OS-thread Tasks Thread

    pool asyncSocket.read(buffer, ..., callback); thread pool to the rescue
  29. Проблема подхода с callback – уродование кода и сложность отладки

    int v = foo(); validate(v); int k = bar(v); validate(k); baz(k); thread pool to the rescue
  30. Проблема подхода с callback – уродование кода и сложность отладки

    main callback int v = foo(); validate(v); int k = bar(v); validate(k); baz(k); foo(v -> { validate(v); bar(v, k -> { validate(k); baz(k); }); }); callback thread pool to the rescue
  31. Альтернатива колбекам - Future Future<Integer> read(ByteBuffer dst, long position); o

    Вызов read неблокирующий, сразу возвращает результат o Вызов Future.get – блокирующий to block or not to block
  32. Альтернатива колбекам - Future Future<Integer> read(ByteBuffer dst, long position); o

    Вызов read неблокирующий, сразу возвращает результат o Вызов Future.get – блокирующий o Само по себе это не поможет, но помогут комбинаторы o Вместо вызова get описываем следующие шаги to block or not to block
  33. Проблема подхода с callback – уродование кода и сложность отладки

    main callback int v = foo(); validate(v); int k = bar(v); validate(k); baz(k); foo(v -> { validate(v); bar(v, k -> { validate(k); baz(k); }); }); callback to block or not to block
  34. С комбинаторами – лучше. Callback hell больше нет, но код

    все еще другой и непривычный. CompletableFuture.supplyAsync(this::foo). thenApply(v -> { validate(v); return v; }). thenApplyAsync(this::bar). thenApply(k -> { validate(k); return k; }). thenAcceptAsync(this::baz); to block or not to block
  35. Итого: первый путь решения проблемы тредов – изменение исходного кода

    o Используем асинхронные API o Декомпозируем код с помощью Future и комбинаторов o Избегаем кода с блокировками на мониторах to block or not to block
  36. Итого: первый путь решения проблемы тредов – изменение исходного кода

    o Используем асинхронные API o Декомпозируем код с помощью Future и комбинаторов o Избегаем кода с блокировками на мониторах После этого начинает работать подход с тредпулом. to block or not to block
  37. j.l.Thread j.l.Thread j.l.Thread j.l.Thread OS-thread OS-thread OS-thread OS-thread Tasks Долгий

    таск Thread pool Блокирующий вызов to block or not to block
  38. j.l.Thread j.l.Thread j.l.Thread j.l.Thread OS-thread OS-thread OS-thread OS-thread Tasks Долгий

    таск Thread pool Блокирующий вызов to block or not to block
  39. Основанные на ThreadPool и изменении подхода к написанию кода: +

    Реализация на уровне библиотеки ± Сильное изменение кода
  40. Основанные на ThreadPool и изменении подхода к написанию кода: +

    Реализация на уровне библиотеки ± Сильное изменение кода
  41. Основанные на ThreadPool и изменении подхода к написанию кода: +

    Реализация на уровне библиотеки ± Сильное изменение кода – Хрупкое решение (в случае Java)
  42. Аналогичный подход в функциональных языках: o Scala: ZIO, Cats Effect

    o Pure functional Реактивные фреймворки: youtube.com/watch?v=YwG04UZP2a0
  43. to hide a thread pool И все же писать и

    отлаживать императивный код хочется
  44. И все же писать и отлаживать императивный код хочется А

    что если преобразовывать код для работы с тредпулом… автоматически? to hide a thread pool
  45. И все же писать и отлаживать императивный код хочется А

    что если преобразовывать код для работы с тредпулом… автоматически? Так делают в C#/JS/С++/Rust/Kotlin to hide a thread pool
  46. main callback int v = foo(); validate(v); int k =

    bar(v); validate(k); baz(k); foo(v -> { validate(v); bar(v, k -> { validate(k); baz(k); }); }); callback to hide a thread pool
  47. fun test(input: Int): Int { val seed = Random.nextInt() delay(100)

    print("$input -> $seed") delay(100) return input + seed } to hide a thread pool
  48. public suspend fun delay(timeMillis: Long) to hide a thread pool

    fun test(input: Int): Int { val seed = Random.nextInt() delay(100) print("$input -> $seed") delay(100) return input + seed }
  49. public suspend fun delay(timeMillis: Long) to hide a thread pool

    fun test(input: Int): Int { val seed = Random.nextInt() delay(100) print("$input -> $seed") delay(100) return input + seed }
  50. public suspend fun delay(timeMillis: Long) to hide a thread pool

    suspend fun test(input: Int): Int { val seed = Random.nextInt() delay(100) print("$input -> $seed") delay(100) return input + seed }
  51. fun main() = runBlocking { for (i in 1..100_000) {

    launch { val result = test(i) println("Coroutine $i returned $result") } } } suspend fun test(input: Int): Int { ... } to hide a thread pool
  52. fun main() = runBlocking { for (i in 1..100_000) {

    launch { val result = test(i) println("Coroutine $i returned $result") } } } suspend fun test(input: Int): Int { ... } to hide a thread pool
  53. suspend fun test(input: Int): Int { val seed = Random.nextInt()

    delay(100) print("$input -> $seed") delay(100) return input + seed } А как это работает то? to hide a thread pool
  54. fun main() = runBlocking { for (i in 1..100_000) {

    launch { val result = test(i) println("Coroutine $i returned $result") } } } suspend fun test(input: Int): Int { ... } Запуск таска на тредпуле to hide a thread pool
  55. suspend fun test(input: Int): Int { val seed = Random.nextInt()

    delay(100) print("$input -> $seed") delay(100) return input + seed } to hide a thread pool
  56. suspend fun test(input: Int): Int { val seed = Random.nextInt()

    delay(100) print("$input -> $seed") delay(100) return input + seed } callback to hide a thread pool
  57. suspend fun test(input: Int): Int { val seed = Random.nextInt()

    delay(100) print("$input -> $seed") delay(100) return input + seed } callback to hide a thread pool
  58. suspend fun test(input: Int): Int { val seed = Random.nextInt()

    delay(100) print("$input -> $seed") delay(100) return input + seed } Object test(int input, Continuation<?> cont) { ... } to hide a thread pool
  59. suspend fun test(input: Int): Int { val seed = Random.nextInt()

    delay(100) print("$input -> $seed") delay(100) return input + seed } Object test(int input, Continuation<?> cont) { ... switch (cont.label) { } ... } 0 to hide a thread pool
  60. suspend fun test(input: Int): Int { val seed = Random.nextInt()

    delay(100) print("$input -> $seed") delay(100) return input + seed } Object test(int input, Continuation<?> cont) { ... switch (cont.label) { case 0: cont.input = input; cont.seed = Random.nextInt(); cont.label = 1; delay(100, cont); break; ... } ... } 1 0 to hide a thread pool
  61. suspend fun test(input: Int): Int { val seed = Random.nextInt()

    delay(100) print("$input -> $seed") delay(100) return input + seed } Object test(int input, Continuation<?> cont) { ... switch (cont.label) { ... case 1: print("$cont.input -> $cont.seed") cont.label = 2; delay(100, cont); break; ... } ... } 2 1 0 to hide a thread pool
  62. suspend fun test(input: Int): Int { val seed = Random.nextInt()

    delay(100) print("$input -> $seed") delay(100) return input + seed } Object test(int input, Continuation<?> cont) { ... switch (cont.label) { ... case 2: cont.caller.resumeWith( cont.input + cont.seed); } ... } 2 1 0 to hide a thread pool
  63. suspend fun test(input: Int): Int { val seed = Random.nextInt()

    delay(100) print("$input -> $seed") delay(100) return input + seed } to hide a thread pool
  64. suspend fun test(input: Int): Int { val seed = Random.nextInt()

    delay(100) print("$input -> $seed") delay(100) return input + seed } callback callback to hide a thread pool
  65. Suspend функции: o Преобразуются компилятором в switch и работу с

    Continuation o Изначально появляются в библиотеке или как обертки над async API (см. suspendCoroutine) to hide a thread pool
  66. Suspend функции: o Преобразуются компилятором в switch и работу с

    Continuation o Изначально появляются в библиотеке или как обертки над async API (см. suspendCoroutine) o Caller suspend функции тоже suspend to hide a thread pool
  67. fun test(): Int { val r = foo() validate(r) val

    l = bar(r + 42) validate(l) return baz(r + l) } to hide a thread pool
  68. fun test(): Int { val r = foo() validate(r) val

    l = bar(r + 42) validate(l) return baz(r + l) } fun validate() { ... } fun foo() { validate(...) ... } fun bar(i: Int) { foo() ... } fun baz(i: Int) { validate(i) ... } to hide a thread pool
  69. fun test(): Int { val r = foo() validate(r) val

    l = bar(r + 42) validate(l) return baz(r + l) } fun validate() { ... } @Benchmark fun baseline() = runBlocking { test() } fun foo() { validate(...) ... } fun bar(i: Int) { foo() ... } fun baz(i: Int) { validate(i) ... } to hide a thread pool
  70. fun test(): Int { val r = foo() validate(r) val

    l = bar(r + 42) validate(l) return baz(r + l) } fun validate() { ... } @Benchmark fun baseline() = runBlocking { test() } fun foo() { validate(...) ... } fun bar(i: Int) { foo() ... } fun baz(i: Int) { validate(i) ... } Benchmark Score Error Units baseline 0,337 ± 0,027 us/op to hide a thread pool
  71. fun test(): Int { val r = foo() validate(r) val

    l = bar(r + 42) validate(l) return baz(r + l) } suspend fun validate() {…} @Benchmark fun baseline() = runBlocking { test() } fun foo() { validate(...) ... } fun bar(i: Int) { foo() ... } fun baz(i: Int) { validate(i) ... } Benchmark Score Error Units baseline 0,337 ± 0,027 us/op to hide a thread pool
  72. suspend fun test(): Int { val r = foo() validate(r)

    val l = bar(r + 42) validate(l) return baz(r + l) } suspend fun validate() {…} @Benchmark fun testSuspend() = runBlocking { test() } suspend fun foo() { validate(...) ... } suspend fun bar(i: Int) { foo() ... } suspend fun baz(i: Int) { validate(i) ... } Benchmark Score Error Units baseline 0,337 ± 0,027 us/op to hide a thread pool
  73. suspend fun test(): Int { val r = foo() validate(r)

    val l = bar(r + 42) validate(l) return baz(r + l) } suspend fun validate() {…} @Benchmark fun testSuspend() = runBlocking { test() } suspend fun foo() { validate(...) ... } suspend fun bar(i: Int) { foo() ... } suspend fun baz(i: Int) { validate(i) ... } Benchmark Score Error Units baseline 0,337 ± 0,027 us/op testSuspend 3,843 ± 0,278 us/op to hide a thread pool
  74. Benchmark Score Error Units baseline 0,337 ± 0,027 us/op testSuspend

    3,843 ± 0,278 us/op fun test(): Int { val r = foo() validate(r) val l = bar(r + 42) validate(l) return baz(r + l) } fun validate() { ... } @Benchmark fun baseline() = runBlocking { test() } fun foo() { validate(...) ... } fun bar(i: Int) { foo() ... } fun baz(i: Int) { validate(i) ... } to hide a thread pool
  75. Benchmark Score Error Units baseline 0,337 ± 0,027 us/op testSuspend

    3,843 ± 0,278 us/op fun test(): Int { val r = foo() validate(r) val l = bar(r + 42) validate(l) return baz(r + l) } fun validate() { ... } @Benchmark fun testNoInline() = runBlocking { test() } fun foo() { validate(...) ... } fun bar(i: Int) { foo() ... } fun baz(i: Int) { validate(i) ... } @CompilerControl(CompilerControl.Mode.DONT_INLINE) to hide a thread pool
  76. Benchmark Score Error Units baseline 0,337 ± 0,027 us/op testSuspend

    3,843 ± 0,278 us/op testNoInline 2,677 ± 0,075 us/op fun test(): Int { val r = foo() validate(r) val l = bar(r + 42) validate(l) return baz(r + l) } fun validate() { ... } @Benchmark fun testNoInline() = runBlocking { test() } fun foo() { validate(...) ... } fun bar(i: Int) { foo() ... } fun baz(i: Int) { validate(i) ... } @CompilerControl(CompilerControl.Mode.DONT_INLINE) to hide a thread pool
  77. Benchmark Score Error Units baseline 0,337 ± 0,027 us/op testSuspend

    3,843 ± 0,278 us/op testNoInline 2,677 ± 0,075 us/op fun test(): Int { val r = foo() validate(r) val l = bar(r + 42) validate(l) return baz(r + l) } fun validate() { ... } @Benchmark fun testNoInline() = runBlocking { test() } fun foo() { validate(...) ... } fun bar(i: Int) { foo() ... } fun baz(i: Int) { validate(i) ... } @CompilerControl(CompilerControl.Mode.DONT_INLINE) to hide a thread pool
  78. А чем за это все заплатим? o Производительностью преобразованного кода

    o Памятью (из-за создания Continuation-ов) to hide a thread pool
  79. Автоматическое преобразование кода в CPS o Используется в C#, JS,

    C++, Kotlin o Реализация на уровне компилятора и библиотеки + Минимальное изменение исходного кода − Издержки по производительности и по памяти
  80. Автоматическое преобразование кода в CPS o Используется в C#, JS,

    C++, Kotlin o Реализация на уровне компилятора и библиотеки + Минимальное изменение исходного кода − Издержки по производительности и по памяти ± Раскраска кода (suspend функциями) – Все еще хрупкое решение!
  81. больше меньше ThreadPool + Callbacks/Future/ Combinators CompletableFuture ReactiveFrameworks Влияние на

    код и язык Автоматическое преобразование кода в CPS a.k.a stackless корутины
  82. больше меньше ThreadPool + Callbacks/Future/ Combinators CompletableFuture ReactiveFrameworks Влияние на

    код и язык Автоматическое преобразование кода в CPS a.k.a stackless корутины С#/JS/C++ Kotlin
  83. больше меньше ThreadPool + Callbacks/Future/ Combinators CompletableFuture ReactiveFrameworks Влияние на

    код и язык Автоматическое преобразование кода в CPS a.k.a stackless корутины С#/JS/C++ Kotlin
  84. final int count = 100_000; final Thread[] threads = new

    Thread[count]; for (int i = 0; i < count; i++) { System.out.println("Starting thread #" + i); final int n = i; threads[i] = new Thread(() -> { Thread.sleep(Duration.ofSeconds(10)); System.out.println("Thread #" + n + " finished"); }); threads[i].start(); } for (int i = 0; i < count; i++) { threads[i].join(); } loom
  85. final int count = 100_000; final Thread[] threads = new

    Thread[count]; for (int i = 0; i < count; i++) { System.out.println("Starting thread #" + i); final int n = i; threads[i] = new Thread(() -> { Thread.sleep(Duration.ofSeconds(10)); System.out.println("Thread #" + n + " finished"); }); threads[i].start(); } for (int i = 0; i < count; i++) { threads[i].join(); } loom
  86. final int count = 100_000; final Thread[] threads = new

    Thread[count]; for (int i = 0; i < count; i++) { System.out.println("Starting thread #" + i); final int n = i; threads[i] = new Thread(() -> { Thread.sleep(Duration.ofSeconds(10)); System.out.println("Thread #" + n + " finished"); }); threads[i].start(); } for (int i = 0; i < count; i++) { threads[i].join(); } Starting thread #10775 Starting thread #10776 Starting thread #10777 Starting thread #10778 Starting thread #10779 Starting thread #10780 [1.232s][warning][os,thread] Failed to start thread "Unknown thread" – pthread_create failed (EAGAIN) for attributes: stacksize: 1024k, guardsize: 0k, detached. [1.232s][warning][os,thread] Failed to start the native thread for java.lang.Thread "Thread-10780" loom
  87. final int count = 100_000; final Thread[] threads = new

    Thread[count]; for (int i = 0; i < count; i++) { System.out.println("Starting thread #" + i); final int n = i; threads[i] = new Thread(() -> { Thread.sleep(Duration.ofSeconds(10)); System.out.println("Thread #" + n + " finished"); }); threads[i].start(); } for (int i = 0; i < count; i++) { threads[i].join(); } loom
  88. final int count = 100_000; final Thread[] threads = new

    Thread[count]; for (int i = 0; i < count; i++) { System.out.println("Starting thread #" + i); final int n = i; threads[i] = Thread.ofVirtual().start(() -> { Thread.sleep(Duration.ofSeconds(10)); System.out.println("Thread #" + n + " finished"); }); } for (int i = 0; i < count; i++) { threads[i].join(); } loom
  89. final int count = 100_000; final Thread[] threads = new

    Thread[count]; for (int i = 0; i < count; i++) { System.out.println("Starting thread #" + i); final int n = i; threads[i] = Thread.ofVirtual().start(() -> { Thread.sleep(Duration.ofSeconds(10)); System.out.println("Thread #" + n + " finished"); }); } for (int i = 0; i < count; i++) { threads[i].join(); } $ javac --release 20 --enable-preview Test.java Note: Test.java uses preview features of Java SE 20. $ java --enable-preview Test loom
  90. final int count = 100_000; final Thread[] threads = new

    Thread[count]; for (int i = 0; i < count; i++) { System.out.println("Starting thread #" + i); final int n = i; threads[i] = Thread.ofVirtual().start(() -> { Thread.sleep(Duration.ofSeconds(10)); System.out.println("Thread #" + n + " finished"); }); } for (int i = 0; i < count; i++) { threads[i].join(); } $ javac --release 20 --enable-preview Test.java Note: Test.java uses preview features of Java SE 20. $ java --enable-preview Test Thread #99986 finished Thread #99982 finished Thread #99990 finished Thread #99993 finished Thread #99996 finished Thread #99994 finished Thread #99997 finished Thread #99999 finished Thread #99988 finished loom
  91. try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 100_000).forEach(i -> {

    executor.submit(() -> { Thread.sleep(Duration.ofSeconds(1)); System.out.println("worker " + i + " finished"); return i; }); }); } loom
  92. try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 100_000).forEach(i -> {

    executor.submit(() -> { Thread.sleep(Duration.ofSeconds(1)); System.out.println("worker " + i + " finished"); return i; }); }); } worker #99989 finished worker #99982 finished worker #99991 finished worker #99994 finished worker #99993 finished worker #99999 finished worker #99995 finished worker #99992 finished worker #99998 finished loom
  93. V N M P P P P виртуальные потоки OS-потоки

    V V V V V V V V V V V V V o N:M threading loom: under the hood
  94. V N M P P P P виртуальные потоки OS-потоки

    V V V V V V V V V V V V V o N:M threading o Виртуальный поток: o исполняется (на OS- потоке) o ожидает loom: under the hood
  95. V V N M P P P P виртуальные потоки

    OS-потоки V V V V V V V V V V V V o N:M threading o Виртуальный поток: o исполняется (на OS- потоке) o ожидает o Снятие с OS-потока кооперативное loom: under the hood
  96. V V N M o N:M threading o Виртуальный поток:

    o исполняется (на OS- потоке) o ожидает o Снятие с OS-потока кооперативное o Планировщик решает, кто следующий P P P P виртуальные потоки OS-потоки V V V V V V V V V V V V loom: under the hood
  97. Когда виртуальный поток снимается с OS потока? o Thread.yield() public

    static void yield() { if (currentThread() instanceof VirtualThread vthread) { vthread.tryYield(); } else { yield0(); } } loom: under the hood
  98. Когда виртуальный поток снимается с OS потока? o Thread.yield(), Thread.sleep(…)

    o LockSupport.park(…) public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); try { if (t.isVirtual()) { VirtualThreads.park(); } else { U.park(false, 0L); } } finally { setBlocker(t, null); } } loom: under the hood
  99. Когда виртуальный поток снимается с OS потока? o Thread.yield(), Thread.sleep(…)

    o LockSupport.park(…) o ReentrantLock, BlockingQueue, Mutex loom: under the hood
  100. Когда виртуальный поток снимается с OS потока? o Thread.yield(), Thread.sleep(…)

    o LockSupport.park(…) o ReentrantLock, BlockingQueue, Mutex o java.net/java.nio.channels loom: under the hood
  101. Когда виртуальный поток снимается с OS потока? o Thread.yield(), Thread.sleep(…)

    o LockSupport.park(…) o ReentrantLock, BlockingQueue, Mutex o java.net/java.nio.channels "Блокирующие" чтение и запись по сети вместо блокировки OS-потока снимают виртуальный поток loom: under the hood
  102. public int read(ByteBuffer buf) throws IOException { ... configureSocketNonBlockingIfVirtualThread(); n

    = IOUtil.read(fd, buf, -1, nd); if (blocking) { while (IOStatus.okayToRetry(n) && isOpen()) { park(Net.POLLIN); n = IOUtil.read(fd, buf, -1, nd); } } ... sun.nio.ch.SocketChannelImpl.java loom: under the hood
  103. V А что, если поток заблокируется на IO? V N

    M P P P P V V V V V V V V V V V V FileInputStream.read() loom: under the hood
  104. V А что, если поток заблокируется на IO? V N

    M P P P P V V V V V V V V V V V V FileInputStream.read() public int read() throws IOException { long comp = Blocker.begin(); try { return read0(); } finally { Blocker.end(comp); } } loom: under the hood
  105. V А что, если поток заблокируется на IO? V N

    M P P P P V V V V V V V V V V V V FileInputStream.read() public int read() throws IOException { long comp = Blocker.begin(); try { return read0(); } finally { Blocker.end(comp); } } P loom: under the hood
  106. V А что, если поток заблокируется на IO? V N

    M P P P P V V V V V V V V V V V V FileInputStream.read() public int read() throws IOException { long comp = Blocker.begin(); try { return read0(); } finally { Blocker.end(comp); } } P loom: under the hood
  107. V А что, если поток заблокируется на IO? V N

    M P P P V V V V V V V V V V V V public int read() throws IOException { long comp = Blocker.begin(); try { return read0(); } finally { Blocker.end(comp); } } P loom: under the hood
  108. При блокирующих операциях: o Либо виртуальные потоки снимаются с OS-потоков

    o Либо временно добавляются новые OS-потоки loom: under the hood
  109. При блокирующих операциях: o Либо виртуальные потоки снимаются с OS-потоков

    o Либо временно добавляются новые OS-потоки Но есть два исключения! loom: under the hood
  110. try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 100_000).forEach(i -> {

    executor.submit(() -> { Thread.sleep(Duration.ofSeconds(1)); System.out.println("worker " + i + " finished"); return i; }); }); } $ time java --enable-preview Test >log real 0m2.526s user 0m6.683s sys 0m0.865s loom: under the hood
  111. Object[] monitors = new Object[100_000]; ... try (var executor =

    Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 100_000).forEach(i -> { executor.submit(() -> { synchronized (monitors[i]) { Thread.sleep(Duration.ofSeconds(1)); System.out.println("worker " + i + " finished"); return i; } }); }); } loom: under the hood
  112. Object[] monitors = new Object[100_000]; ... try (var executor =

    Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 100_000).forEach(i -> { executor.submit(() -> { synchronized (monitors[i]) { Thread.sleep(Duration.ofSeconds(1)); System.out.println("worker " + i + " finished"); return i; } }); }); } $ time java --enable-preview Test >log real 208m25.003s user 0m20.695s sys 0m13.830s loom: under the hood
  113. Object[] monitors = new Object[100_000]; ... try (var executor =

    Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 100_000).forEach(i -> { executor.submit(() -> { Thread.sleep(Duration.ofSeconds(1)); synchronized (monitors[i]) { System.out.println("worker " + i + " finished"); return i; } }); }); } loom: under the hood
  114. Object[] monitors = new Object[100_000]; ... try (var executor =

    Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 100_000).forEach(i -> { executor.submit(() -> { Thread.sleep(Duration.ofSeconds(1)); synchronized (monitors[i]) { System.out.println("worker " + i + " finished"); return i; } }); }); } $ time java --enable-preview Test >log real 0m2.532s user 0m5.044s sys 0m0.601s loom: under the hood
  115. Но есть два исключения! o synchronized. Внутри виртуальные потоки не

    снимаются o Причина: сложность реализации o Диагностика: -Djdk.tracePinnedThreads=full loom: under the hood
  116. Object[] monitors = new Object[100_000]; ... try (var executor =

    Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 100_000).forEach(i -> { executor.submit(() -> { synchronized(monitors[i]) { Thread.sleep(Duration.ofSeconds(1)); System.out.println("worker " + i + " finished"); return i; } }); }); } $ java -Djdk.tracePinnedThreads=full --enable-preview Test loom: under the hood
  117. Object[] monitors = new Object[100_000]; ... try (var executor =

    Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 100_000).forEach(i -> { executor.submit(() -> { synchronized(monitors[i]) { Thread.sleep(Duration.ofSeconds(1)); System.out.println("worker " + i + " finished"); return i; } }); }); } $ java -Djdk.tracePinnedThreads=full --enable-preview Test Thread[#22,ForkJoinPool-1-worker-1,5,CarrierThreads] java.base/java.lang.VirtualThread$VThreadContinuation.onPinned(VirtualThread.java:180) java.base/jdk.internal.vm.Continuation.onPinned0(Continuation.java:398) java.base/jdk.internal.vm.Continuation.yield0(Continuation.java:390) java.base/jdk.internal.vm.Continuation.yield(Continuation.java:357) java.base/java.lang.VirtualThread.yieldContinuation(VirtualThread.java:370) java.base/java.lang.VirtualThread.parkNanos(VirtualThread.java:532) java.base/java.lang.VirtualThread.doSleepNanos(VirtualThread.java:713) java.base/java.lang.VirtualThread.sleepNanos(VirtualThread.java:686) java.base/java.lang.Thread.sleep(Thread.java:538) Test.lambda$main$0(Test.java:78) <== monitors:1 ... loom: under the hood
  118. Но есть два исключения! o synchronized. Внутри виртуальные потоки не

    снимаются o Причина: сложность реализации o Диагностика: -Djdk.tracePinnedThreads=full o Лечение: замена на ReentrantLock loom: under the hood
  119. Но есть два исключения! o synchronized. Внутри виртуальные потоки не

    снимаются … o Если хоть один фрейм нативный Java => C++ => JNI => Java => … => Thread.yield() loom: under the hood
  120. Но есть два исключения! o synchronized. Внутри виртуальные потоки не

    снимаются … o Если хоть один фрейм нативный o Диагностика: -Djdk.tracePinnedThreads=full o Лечение: нет, избегайте сквозных нативов loom: under the hood
  121. А что, если в виртуальном потоке исполняется просто долгий CPU

    intensive код? for (int i = 0; i < 100_000; i++) { for (int j = 0; j < 100_000; j++) { while (z > 0) { // CPU intensive делишки } } } loom: under the hood
  122. А что, если в виртуальном потоке исполняется просто долгий CPU

    intensive код? V V N M P P P P V V V V V V V V V V V V FileInputStream.read() P CPU intensive loom: under the hood
  123. try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 100_000).forEach(i -> {

    executor.submit(() -> { Thread.sleep(Duration.ofSeconds(1)); System.out.println("worker " + i + " finished"); return i; }); }); } loom: under the hood
  124. try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 100_000).forEach(i -> {

    executor.submit(() -> { if (i < 8) { long result = getNthPrimeNumber(1_000_000 + i); System.out.println("Heavy worker " + i + " finished"); return (int) result; } else { Thread.sleep(Duration.ofSeconds(1)); System.out.println("worker " + i + " finished"); return i; }); }); } loom: under the hood
  125. try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 100_000).forEach(i -> {

    executor.submit(() -> { if (i < 8) { long result = getNthPrimeNumber(1_000_000 + i); System.out.println("Heavy worker " + i + " finished"); return (int) result; } else { Thread.sleep(Duration.ofSeconds(1)); System.out.println("worker " + i + " finished"); return i; }); }); } Heavy worker 6 finished Heavy worker 4 finished Heavy worker 0 finished Heavy worker 1 finished Heavy worker 5 finished Heavy worker 3 finished Heavy worker 2 finished Heavy worker 7 finished worker 14 finished worker 15 finished worker 51 finished worker 53 finished worker 55 finished ... loom: under the hood
  126. А что, если в виртуальном потоке исполняется просто долгий CPU

    intensive код? o Это может быть проблемой (несколько CPU intensive потоков забьют весь пул) loom: under the hood
  127. А что, если в виртуальном потоке исполняется просто долгий CPU

    intensive код? o Это может быть проблемой (несколько CPU intensive потоков забьют весь пул) o Лечение: • Ручной Thread.yield() • Использование старых тредов loom: under the hood
  128. Практические советы: 1. Избегайте synchronized, заменяйте на ReentrantLock 2. Избегайте

    сквозных нативов 3. Избегайте CPU-intensive кода, вставляйте ручной yield -Djdk.tracePinnedThreads для обнаружения проблем 1 и 2. loom: under the hood
  129. Практические советы: 1. Избегайте synchronized, заменяйте на ReentrantLock 2. Избегайте

    сквозных нативов 3. Избегайте CPU-intensive кода, вставляйте ручной yield -Djdk.tracePinnedThreads для обнаружения проблем 1 и 2. Пункты 1 и 3 можно реализовать на стороне рантайма! loom: under the hood
  130. Практические советы: 1. Избегайте synchronized, заменяйте на ReentrantLock 2. Избегайте

    сквозных нативов 3. Избегайте CPU-intensive кода, вставляйте ручной yield -Djdk.tracePinnedThreads для обнаружения проблем 1 и 2. Пункты 1 и 3 можно реализовать на стороне рантайма! loom: under the hood they are billions
  131. Стек Классические стеки: o 1MB зарезервировано o Большая часть страниц

    не подключена o Страницы по 4KB или больше 4KB better threads
  132. Стек Классические стеки: o 1MB зарезервировано o Большая часть страниц

    не подключена o Страницы по 4KB или больше o Подключаем лениво main() main better threads
  133. Стек Классические стеки: o 1MB зарезервировано o Большая часть страниц

    не подключена o Страницы по 4KB или больше o Подключаем лениво main() main foo() foo better threads
  134. Стек Классические стеки: o 1MB зарезервировано o Большая часть страниц

    не подключена o Страницы по 4KB или больше o Подключаем лениво main() main foo() bar() foo bar better threads
  135. Стек Классические стеки: o 1MB зарезервировано o Большая часть страниц

    не подключена o Страницы по 4KB или больше o Подключаем лениво main() main foo() bar() foo bar baz() baz better threads
  136. Стек Классические стеки: o 1MB зарезервировано o Большая часть страниц

    не подключена o Страницы по 4KB или больше o Подключаем лениво o В конце guard page main() main foo() bar() foo bar baz() baz better threads
  137. Почему не сопоставить такой стек каждому виртуальному потоку? main() foo()

    bar() baz() o Дорогое создание better threads Стек main foo bar baz
  138. Почему не сопоставить такой стек каждому виртуальному потоку? main() foo()

    bar() baz() o Дорогое создание o Потребление памяти (страницы бывают и по 64к) better threads Стек main foo bar baz
  139. Почему не сопоставить такой стек каждому виртуальному потоку? main() foo()

    bar() baz() o Дорогое создание o Потребление памяти (страницы бывают и по 64к) o Адресное пространство конечно better threads Стек main foo bar baz
  140. o Дорогое создание o Потребление памяти (страницы бывают и по

    64к) o Адресное пространство конечно o Ограничения OS (vm.max_map_count) Почему не сопоставить такой стек каждому виртуальному потоку? main() foo() bar() baz() better threads Стек main foo bar baz
  141. final class VirtualThread extends Thread { ... // scheduler and

    continuation private final Executor scheduler; private final Continuation cont; private final Runnable runContinuation; ... } better threads
  142. final class VirtualThread extends Thread { ... // scheduler and

    continuation private final Executor scheduler; private final Continuation cont; private final Runnable runContinuation; ... } public class Continuation { ... private StackChunk tail; ... } better threads
  143. final class VirtualThread extends Thread { ... // scheduler and

    continuation private final Executor scheduler; private final Continuation cont; private final Runnable runContinuation; ... } public class Continuation { ... private StackChunk tail; ... } public final class StackChunk { public static void init() {} private StackChunk parent; private int size; // in words private int sp; // in words private int argsize; // bottom stack-passed arguments, in words // The stack itself is appended here by the VM, as well as some injected fields } better threads
  144. V V N M P P P P виртуальные потоки

    OS-потоки V V V V V V V V better threads
  145. V V N M P P P P виртуальные потоки

    OS-потоки V V V V V V V V o Классические стеки у потоков носителей better threads
  146. N M P P P P виртуальные потоки OS-потоки o

    Классические стеки у потоков носителей o Собственные маленькие стеки в хипе у виртуальных потоков V V V V V V V V V V better threads
  147. V V N M P P P P виртуальные потоки

    OS-потоки V V V V V V V V o Классические стеки у потоков носителей o Собственные маленькие стеки в хипе у виртуальных потоков o Копирование стека при возобновлении better threads
  148. V V N M P P P P виртуальные потоки

    OS-потоки V V V V V V V V o Классические стеки у потоков носителей o Собственные маленькие стеки в хипе у виртуальных потоков o Копирование стека при возобновлении better threads
  149. V V N M P P P P виртуальные потоки

    OS-потоки V V V V V V V V o Классические стеки у потоков носителей o Собственные маленькие стеки в хипе у виртуальных потоков o Копирование стека при возобновлении o Копирование обратно в хип при приостановке better threads
  150. V V N M P P P P виртуальные потоки

    OS-потоки V V V V V V V V o Классические стеки у потоков носителей o Собственные маленькие стеки в хипе у виртуальных потоков o Копирование стека при возобновлении o Копирование обратно в хип при приостановке better threads
  151. V V N M P P P P виртуальные потоки

    OS-потоки V V V V V V V V o Классические стеки у потоков носителей o Собственные маленькие стеки в хипе у виртуальных потоков o Копирование стека при возобновлении o Копирование обратно в хип при приостановке better threads
  152. Теперь понятно, почему не работают нативы! Там ведь могли быть

    указатели на стек! struct test a; struct test* pa = &a; better threads
  153. Теперь понятно, почему не работают нативы! Там ведь могли быть

    указатели на стек! struct test a; struct test* pa = &a; Копирование стека сделает такие указатели невалидными. better threads
  154. run(depth) run(depth - 1) ... run(0) for (; ;) {

    Thread.yield() } better threads
  155. run(depth) run(depth - 1) ... run(0) for (; ;) {

    Thread.yield() } Стек run run run … … run Копируется каждый раз при context-switch? better threads
  156. import jdk.internal.vm.Continuation; import jdk.internal.vm.ContinuationScope; static class YieldAtLevel implements Runnable {

    private void run(int depth) { if (depth > 0) { run(depth - 1); } else if (depth == 0) { for (;;) { Continuation.yield(SCOPE); } } } ... } better threads
  157. import jdk.internal.vm.Continuation; import jdk.internal.vm.ContinuationScope; static class YieldAtLevel implements Runnable {

    private void run(int depth) { if (depth > 0) { run(depth - 1); } else if (depth == 0) { for (;;) { Continuation.yield(SCOPE); } } } ... } @Setup(Level.Trial) public void setup() { cont = YieldAtLevel.cont(stackDepth); cont.run(); } @Benchmark public void yieldAtTheBottom() { cont.run(); } better threads
  158. Benchmark (stackDepth) Score Error Units yieldAtTheBottom 5 115.436 ± 0.674

    ns/op yieldAtTheBottom 10 114.703 ± 2.482 ns/op yieldAtTheBottom 20 116.356 ± 1.025 ns/op yieldAtTheBottom 100 115.887 ± 1.295 ns/op yieldAtTheBottom 200 115.369 ± 0.520 ns/op yieldAtTheBottom 500 113.776 ± 0.384 ns/op run(depth) run(depth - 1) ... run(0) for (; ;) { Thread.yield() } better threads
  159. Да, но это оптимизируется ленивым копированием: o Копируется N фреймов

    o Затем return барьер P V Получается, что context-switch – работает за O(N) от размера стека?! foo bar baz better threads
  160. Да, но это оптимизируется ленивым копированием: o Копируется N фреймов

    o Затем return барьер P V Получается, что context-switch – работает за O(N) от размера стека?! foo bar baz better threads
  161. Да, но это оптимизируется ленивым копированием: o Копируется N фреймов

    o Затем return барьер P V Получается, что context-switch – работает за O(N) от размера стека?! foo bar baz better threads
  162. Да, но это оптимизируется ленивым копированием: o Копируется N фреймов

    o Затем return барьер P V Получается, что context-switch – работает за O(N) от размера стека?! foo bar better threads
  163. Да, но это оптимизируется ленивым копированием: o Копируется N фреймов

    o Затем return барьер Но есть и цена! P V Получается, что context-switch – работает за O(N) от размера стека?! foo better threads
  164. run(depth) run(depth - 1) ... run(0) for (; ;) {

    Thread.yield() } better threads
  165. 0 5000 10000 15000 20000 25000 30000 5 100 200

    300 400 500 No yielding Yield at the bottom and return ns/op stack depth
  166. Выводы по стекам: o Context-switch не бесплатный, но оптимизации работают

    o Контрпример: обратный ход глубокой рекурсии better threads
  167. V V N M P P P P виртуальные потоки

    OS-потоки V V V V V V V V V V V V Как понять, кто следующий исполняется? mighty scheduler
  168. V N M P P P P виртуальные потоки OS-потоки

    V V V V V V V V V V V V Как понять, кто следующий исполняется? o Очередь? mighty scheduler
  169. V N M P P P P виртуальные потоки OS-потоки

    V V V V V V V V V V V V Как понять, кто следующий исполняется? o Очередь? mighty scheduler
  170. V N M P P P P виртуальные потоки OS-потоки

    V V V V V V V V V V V Как понять, кто следующий исполняется? o Очередь? V mighty scheduler
  171. V N M P P P P виртуальные потоки OS-потоки

    V V V V V V V V V V V Как понять, кто следующий исполняется? o Очередь? V mighty scheduler
  172. V N M P P P P виртуальные потоки OS-потоки

    V V V V V V V V V V V Как понять, кто следующий исполняется? o Очередь? o Много очередей! V mighty scheduler
  173. V N M P P P P виртуальные потоки OS-потоки

    V V V V V V V V V V V Как понять, кто следующий исполняется? o Очередь? o Много очередей! V mighty scheduler
  174. V N M P P P P виртуальные потоки OS-потоки

    V V V V V V V V V V V Как понять, кто следующий исполняется? o Очередь? o Много очередей! o Work stealing V mighty scheduler
  175. V N M P P P P виртуальные потоки OS-потоки

    V V V V V V V V V V V Как понять, кто следующий исполняется? o Очередь? o Много очередей! o Work stealing Такая функциональность есть в ForkJoinPool! V mighty scheduler
  176. В качестве планировщика используется ForkJoinPool o Написан Дагом Ли! o

    Мощно оптимизирован youtube.com/watch?v=t0dGLFtRR9c mighty scheduler
  177. В качестве планировщика используется ForkJoinPool o Написан Дагом Ли! o

    Мощно оптимизирован Но так ли он подходит именно для виртуальных потоков? mighty scheduler
  178. Benchmark (dispatcher) Score Error Units PingPongActorBenchmark.coresCountPingPongs scheduler 58,211 ± 2,074

    ms/op PingPongActorBenchmark.coresCountPingPongs fjp 58,506 ± 10,942 ms/op PingPongActorBenchmark.coresCountPingPongs ftp_1 548,916 ± 37,671 ms/op ------------------------------------------------------------------------------- PingPongActorBenchmark.singlePingPong scheduler 23,855 ± 1,436 ms/op PingPongActorBenchmark.singlePingPong fjp 81,871 ± 1,816 ms/op PingPongActorBenchmark.singlePingPong ftp_1 29,627 ± 0,290 ms/op PingPongActorBenchmark.kt mighty scheduler
  179. PingPongActorBenchmark.kt Benchmark (dispatcher) Score Error Units PingPongActorBenchmark.coresCountPingPongs scheduler 58,211 ±

    2,074 ms/op PingPongActorBenchmark.coresCountPingPongs fjp 58,506 ± 10,942 ms/op PingPongActorBenchmark.coresCountPingPongs ftp_1 548,916 ± 37,671 ms/op ------------------------------------------------------------------------------- PingPongActorBenchmark.singlePingPong scheduler 23,855 ± 1,436 ms/op PingPongActorBenchmark.singlePingPong fjp 81,871 ± 1,816 ms/op PingPongActorBenchmark.singlePingPong ftp_1 29,627 ± 0,290 ms/op mighty scheduler
  180. Эмпирические наблюдения: o Зачастую приостановленная корутина скоро снова будет готова

    работать o Хорошо бы, чтобы она осталась на том же треде и на том же CPU mighty scheduler
  181. ForkJoinPool: o Написан Дагом Ли! o Мощно оптимизирован o Нужна

    более тонкая настройка очередности o Слишком агрессивно ворует mighty scheduler
  182. ForkJoinPool: o Написан Дагом Ли! o Мощно оптимизирован o Нужна

    более тонкая настройка очередности o Слишком агрессивно ворует У большинства JVM-based решений свой планировщик, который эти проблемы старается решать. mighty scheduler
  183. ... Thus, full support for NUMA requires the ability to

    create several different pools of carrier threads with the ability to configure them. This is not possible in the current version of the API. ... Vladimir Ogorodnikov [email protected] Нужна возможность писать и использовать с виртуальными потоками свои планировщики! mighty scheduler
  184. ... Thus, full support for NUMA requires the ability to

    create several different pools of carrier threads with the ability to configure them. This is not possible in the current version of the API. ... Vladimir Ogorodnikov [email protected] Yes, the plan is to ultimately allow custom schedulers, but we want to first let the ecosystem learn about virtual threads and their uses with the default scheduler. ... Ron Pressler Нужна возможность писать и использовать с виртуальными потоками свои планировщики! mighty scheduler
  185. Нужна возможность писать и использовать с виртуальными потоками свои планировщики!

    ... Thus, full support for NUMA requires the ability to create several different pools of carrier threads with the ability to configure them. This is not possible in the current version of the API. ... Vladimir Ogorodnikov [email protected] Yes, the plan is to ultimately allow custom schedulers, but we want to first let the ecosystem learn about virtual threads and their uses with the default scheduler. ... Ron Pressler mighty scheduler
  186. Из хорошего: + Низкоуровневые и оптимизированные примитивы: Continuation/Stack chunk +

    Переработана стандартная библиотека под виртуальные потоки is loom a hero?
  187. Из хорошего: + Низкоуровневые и оптимизированные примитивы: Continuation/Stack chunk +

    Переработана стандартная библиотека под виртуальные потоки + Влияние на код минимальное! Никакого coloring is loom a hero?
  188. Что нужно доделать: ? Synchronized ? Принудительное переключение потоков ?

    Humongous stack chunks (SOE при стеке >256КБ) ? Возможность делать свои планировщики is loom a hero?
  189. Из плохого: − Реализация сложна (кишки JVM + переделки в

    стандартной библиотеке) − Сквозные нативы пи́нают виртуальный тред − Обратный ход рекурсии может быть дорог − Context-switch не бесплатен is loom a hero?
  190. больше меньше ThreadPool + Callbacks/Future/ Combinators CompletableFuture ReactiveFrameworks Влияние на

    код и язык Автоматическое преобразование кода в CPS a.k.a stackless корутины С#/JS/C++ Kotlin
  191. больше меньше ThreadPool + Callbacks/Future/ Combinators CompletableFuture ReactiveFrameworks Влияние на

    код и язык Автоматическое преобразование кода в CPS a.k.a stackless корутины С#/JS/C++ Kotlin Виртуальные потоки в Loom a.k.a stackful корутины
  192. больше меньше ThreadPool + Callbacks/Future/ Combinators CompletableFuture ReactiveFrameworks Влияние на

    код и язык Автоматическое преобразование кода в CPS a.k.a stackless корутины С#/JS/C++ Kotlin Виртуальные потоки в Loom a.k.a stackful корутины Loom идеальный
  193. больше меньше ThreadPool + Callbacks/Future/ Combinators CompletableFuture ReactiveFrameworks Влияние на

    код и язык Автоматическое преобразование кода в CPS a.k.a stackless корутины С#/JS/C++ Kotlin Виртуальные потоки в Loom a.k.a stackful корутины Loom идеальный Loom сейчас
  194. brave new world func worker(id int) { fmt.Printf("Worker %d starting\n",

    id) time.Sleep(time.Second) fmt.Printf("Worker %d done\n", id) } func main() { for i := 1; i <= 10; i++ { go worker(i) } }
  195. func main() { var wg sync.WaitGroup for i := 1;

    i <= 100000; i++ { wg.Add(1) i := i go func() { defer wg.Done() worker(i) }() } wg.Wait() } brave new world
  196. … Worker 99833 done Worker 76674 done Worker 99825 done

    Worker 99821 done Worker 99799 done Worker 67867 done Worker 99815 done Worker 99804 done Worker 99816 done Worker 99863 done Worker 87131 done Worker 84239 done Worker 99808 done func main() { var wg sync.WaitGroup for i := 1; i <= 100000; i++ { wg.Add(1) i := i go func() { defer wg.Done() worker(i) }() } wg.Wait() } brave new world
  197. Горутины в Go: o Любую функцию можно запустить параллельно o

    Специальных модификаторов/раскраски – нет o Ограничений на код – нет brave new world
  198. Горутины в Go: o Любую функцию можно запустить параллельно o

    Специальных модификаторов/раскраски – нет o Ограничений на код – нет o Миллион горутин создается и работает brave new world
  199. Реализация горутин в Go: o N:M:P схема o Бесконечные стеки

    o Context-switch без копирования стеков brave new world
  200. Реализация горутин в Go: o N:M:P схема o Бесконечные стеки

    o Context-switch без копирования стеков + Бесплатный! За O(1) + Нативы работают − Платим скоростью каждой функции brave new world
  201. Реализация горутин в Go: o N:M:P схема o Бесконечные стеки

    o Context-switch без копирования стеков o Preemption! o Честность планирования горутин brave new world
  202. больше меньше ThreadPool + Callbacks/Future/ Combinators CompletableFuture ReactiveFrameworks Влияние на

    код и язык Автоматическое преобразование кода в CPS a.k.a stackless корутины С#/JS/C++ Kotlin Виртуальные потоки в Loom a.k.a stackful корутины Loom идеальный Loom сейчас
  203. больше меньше ThreadPool + Callbacks/Future/ Combinators CompletableFuture ReactiveFrameworks Влияние на

    код и язык Автоматическое преобразование кода в CPS a.k.a stackless корутины С#/JS/C++ Kotlin Виртуальные потоки в Loom или горутины в Go a.k.a stackful корутины Go прямо сейчас Loom сейчас
  204. o Решений проблемы легковесных потоков существует множество o Loom –

    лишь один из путей, дающий свободу выбора o Реализация иногда сильно влияет на язык
  205. o Решений проблемы легковесных потоков существует множество o Loom –

    лишь один из путей, дающий свободу выбора o Реализация иногда сильно влияет на язык. Но это не значит, что с этим нужно мириться
  206. Benchmark Score Error Units baseline 0,337 ± 0,027 us/op suspendUsual

    3,843 ± 0,278 us/op noInline 2,677 ± 0,075 us/op @CompilerControl(CompilerControl.Mode.DONT_INLINE) suspend fun test(): Int { val r = foo() validate(r) val l = bar(r + 42) validate(l) return baz(r + l) } suspend fun validate() {…} @Benchmark fun suspendNoInline() = runBlocking { test() } suspend fun foo() { validate(...) ... } suspend fun bar(i: Int) { foo() ... } suspend fun baz(i: Int) { validate(i) ... } to hide a thread pool
  207. Benchmark Score Error Units baseline 0,337 ± 0,027 us/op suspendUsual

    3,843 ± 0,278 us/op noInline 2,677 ± 0,075 us/op suspendNoInline 10,460 ± 0,110 us/op @CompilerControl(CompilerControl.Mode.DONT_INLINE) suspend fun test(): Int { val r = foo() validate(r) val l = bar(r + 42) validate(l) return baz(r + l) } suspend fun validate() {…} @Benchmark fun suspendNoInline() = runBlocking { test() } suspend fun foo() { validate(...) ... } suspend fun bar(i: Int) { foo() ... } suspend fun baz(i: Int) { validate(i) ... } to hide a thread pool
  208. to hide a thread pool Benchmark Score Error Units baseline

    0,337 ± 0,027 us/op suspendUsual 3,843 ± 0,278 us/op noInline 2,677 ± 0,075 us/op suspendNoInline 10,460 ± 0,110 us/op testRunBlocking 0,191 ± 0,011 us/op fun test(): Int { val r = foo() validate(r) val l = bar(r + 42) validate(l) return baz(r + l) } fun validate() {…} @Benchmark fun testRunBlocking() = runBlocking { // test() } fun foo() { validate(...) ... } fun bar(i: Int) { foo() ... } fun baz(i: Int) { validate(i) ... }
  209. better threads Benchmark (stackDepth) Score Error Units noYielding 5 1007.704

    ± 41.553 ns/op noYielding 50 1401.873 ± 36.681 ns/op noYielding 100 2034.516 ± 56.821 ns/op noYielding 200 3429.187 ± 73.425 ns/op noYielding 300 4754.425 ± 139.880 ns/op noYielding 400 6043.952 ± 231.421 ns/op ---------------------------------------------------- yieldAndReturn 5 1051.371 ± 26.917 ns/op yieldAndReturn 50 1880.173 ± 32.059 ns/op yieldAndReturn 100 4423.114 ± 70.889 ns/op yieldAndReturn 200 9993.032 ± 73.666 ns/op yieldAndReturn 300 15378.223 ± 346.750 ns/op yieldAndReturn 400 20933.469 ± 120.113 ns/op Понятно, что baseline быстрее, но какой из этого можно сделать вывод?
  210. И еще про стеки: o Стеки в хипе => размечаются

    сборщиком мусора o VirtualThread → Continuation → StackChunk → локалы better threads
  211. И еще про стеки: o Стеки в хипе => размечаются

    сборщиком мусора o VirtualThread → Continuation → StackChunk → локалы better threads
  212. И еще про стеки: o Стеки в хипе => размечаются

    сборщиком мусора o VirtualThread → Continuation → StackChunk → локалы Из-за недостаточной поддержки stack chunks в G1 могут случаться SOE на стеке размером 256KB better threads
  213. 0 5000 10000 15000 20000 25000 30000 5 100 200

    300 400 500 Invoke => yield Resume => return ns/op stack depth