Хватит ждать и начните многопоточность

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

Но ждать скучно.

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

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

Настройка вещей

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

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

julia -t 4

Это запустит Джулию, использующую 4 потока. Мы можем подтвердить это, запросив количество потоков:

julia> using Base.Threads

julia> Threads.nthreads()
4

Делаем медленную функцию

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

Эта «медленная» функция может быть вызовом для построения модели ML, выполнения некоторых SQL-подобных запросов к базе данных или извлечения некоторых данных из облачного хранилища. Используйте свое воображение и сойти с ума!

julia> function collatz(n, i=0)
           if n == 1
               i
           elseif iseven(n)
               collatz(n / 2, i + 1)
           else
               collatz(3n + 1, i + 1)
           end
       end
collatz (generic function with 2 methods)

julia> collatz(989345275647)
1348

julia> averageSteps(n) = sum(i -> collatz(i) / n, 1:n)
averageSteps (generic function with 1 method

Если вам интересно, о чем идет речь выше и почему я выбрал 989 345 275 647, то прочитайте эту страницу вики.

Получение немного магии

Поскольку в нашем пространстве имен есть Threads, мы можем использовать макрос @spawn для отправки вычислений в другой поток. Это означает, что мы немедленно вернем наш REPL и сможем продолжать работать, как раньше.

julia> res = @spawn averageSteps(1e7)
Task (runnable) @0x000000015d061f90

julia> 2^5 + 12
44

julia> fetch(res)
155.2724831

Не обращайте внимания на отсутствие у меня воображения, я просто не мог придумать что-то более сложное после появления.

По сути, здесь происходит то, что @spawn возвращает Task. Его задача автоматически перенаправляется в свободный поток, который может работать над ней в фоновом режиме, позволяя тем временем писать больше кода и задавать больше вопросов. Когда вам понадобятся результаты, вы можете собрать результаты задач с помощью fetch, которые будут ждать завершения Task и возвращать результаты.

Доказательство того, что это работает

Один из способов показать, что это действительно работает, — показать некоторые тайминги.

Во-первых, мы запустим нашу функцию в текущем потоке и измерим время, которое она занимает. Затем мы создадим Task и, наконец, создадим и сразу же дождемся результатов.

julia> @time averageSteps(1e7)
 16.040698 seconds
155.2724831

julia> @time res = @spawn averageSteps(1e7)
  0.009290 seconds (31.72 k allocations: 1.988 MiB)
Task (runnable) @0x000000015d179f90

julia> @time fetch(@spawn averageSteps(1e7))
 16.358641 seconds (24.31 k allocations: 1.553 MiB, 0.06% compilation time)
155.2724831

Как видите, наша функция выполняется примерно за 16 секунд. Но если мы отправляем задачу, то мы немедленно возвращаем задачу. Это связано с некоторыми накладными расходами, как вы можете видеть в последней строке, поскольку это немного (0,3 с) медленнее, чем просто выполнение вычислений в основном потоке.

Спасибо за прочтение!

Надеюсь, этот небольшой трюк расскажет новичкам в Julia о потрясающих сверхспособностях, которые может дать им современный многопоточный язык. Если вам понравилось читать мой бред на эту тему, поставьте мне 👏 или 👏 👏.