- PVSM.RU - https://www.pvsm.ru -

Много лет я пользовался языком программирования Julia [1] для преобразования, очистки, анализа и визуализации данных, расчёта статистики и выполнения симуляций.
Я опубликовал несколько опенсорсных пакетов для работы с такими вещами, как поля расстояний со знаком [2], поиск ближайших соседей [3] и паттерны Тьюринга [4] (а также [5] с другими [6]), создавал визуальные объяснения таких концепций Julia, как broadcasting [7] и массивы [8], а ещё применял Julia при создании генеративной графики для моих визиток [9].
Какое-то время назад я перестал пользоваться Julia, но иногда мне задают о нём вопросы. Когда люди спрашивают меня, я отвечаю, что больше не рекомендую его. Мне подумалось, что стоит написать, почему.
После многолетнего использования Julia я пришёл к выводу, что в его экосистеме слишком много багов корректности и сочетаемости, и это не позволяет использовать этот язык в контекстах, где важна корректность.
По моему опыту, Julia и его пакеты имеют наибольшую частоту серьёзных багов корректности из всех использованных мной программных систем, а ведь я начинал программировать с Visual Basic 6 в середине 2000-х.
Наверно, будет полезно привести конкретные примеры.
Вот проблемы корректности, о которых я составил отчёты:
Базовые функции sum!, prod!, any! и all! могут возвращать некорректные результаты без сообщения об ошибке [14]Вот похожие проблемы, о которых сообщали другие люди:
Неверные результаты, поскольку некоторые методы copyto! не выполняют проверку на наложение [20]Я сталкивался с багами подобного уровня серьёзности достаточно часто, поэтому это заставило меня подвергнуть сомнению корректность любых вычислений умеренной сложности на Julia.
Особенно справедливо это было при использовании нового сочетания пакетов или функций — комбинирование функциональности из нескольких источников было существенным источником багов.
Иногда проблемы возникают из-за несочетаемых друг с другом пакетов, в других случаях к неожиданному сбою приводит неожиданное сочетание возможностей Julia внутри одного пакета.
Например, я выяснил, что евклидово расстояние из пакета Distances не работает с векторами Unitful [22]. Другие люди обнаружили, что функция Julia для запуска внешних команд не работает с подстроками [23]. Кто-то выяснил, что поддержка отсутствующих значений Julia в некоторых случаях ломает матричное умножение [24]. И что макрос стандартной библиотеки @distributed не работал с OffsetArray [25].
OffsetArray [26] вообще оказался серьёзным источником багов корректности. Пакет предоставляет тип массива, использующий гибкую функцию настраиваемых индексов [27] Julia, позволяющую создавать массивы, индексы которых не обязаны начинаться с нуля или единицы.
Их использование часто приводит к доступу к памяти out-of-bounds, с которым вы могли встречаться в C или C++. Если повезёт, это приведёт к segfault, а если нет, то результаты будут неверными без сообщений об ошибках. Однажды я нашёл баг в ядре Julia [28], который может привести к доступу к памяти out-of-bounds, даже если и пользователь, и создатели библиотеки написали корректный код.
Я отправил множество отчётов о проблемах индексации в организацию JuliaStats, занимающуюся обслуживанием статистических пакетов наподобие Distributions [29], от которого зависят 945 пакетов, и StatsBase [30], от которого зависят 1660 пакетов. Вот некоторые из них:
Первопричиной этих проблем является не сама индексация, а её совместное использование с другой фичей Julia под названием @inbounds, позволяющей Julia удалять проверки границ при доступе к массивам.
Пример:
function sum(A::AbstractArray)
r = zero(eltype(A))
for i in 1:length(A)
@inbounds r += A[i] # ← 🌶
end
return r
end
Показанный выше код выполняет итерации i от 1 до длины массива. Если передать массив с необычным диапазоном индексов, код выполнит доступ к памяти out-of-bounds: операции доступа к массив аннотированы @inbounds, что убирает проверку границ.
Этот код показывает, как неправильно использовать @inbounds. Однако многие годы это был официальный пример по правильному использованию @inbounds. Этот пример был расположен прямо над предупреждением о том, почему он неправилен:

Эту проблему [37] уже устранили, однако вызывает беспокойство то, что @inbounds можно так легко использовать неверно, что приводит к незаметному повреждению данных и некорректным математическим результатам.
По моему опыту, подобные ошибки касаются не только математической части экосистемы Julia.
Я сталкивался с багами библиотек в процессе выполнения повседневных задач, например, при кодировании [38] JSON [39], отправке HTTP-запросов [40], использовании файлов Arrow [41] совместно [42] с DataFrames и редактировании [43] кода [44] Julia [45] в реактивной среде ноутбуков Pluto [46].
Когда мне стало интересно, репрезентативен ли мой опыт, множество пользователей Julia поделилось со мной похожими историями. Недавно стали появляться и публичные отчёты о подобном опыте.
Например, в этом посте [47] Патрик Киджер описывает свои попытки использовать Julia для исследований машинного обучения:
На Julia Discourse довольно часто встречаются посты «Библиотека XYZ не работает», на которые следуют ответы одного из мейнтейнеров библиотеки: «Это апстрим-баг в новой версии a.b.c библиотеки ABC, от которой зависит XYZ. Мы запушим исправление ASAP».
Вот каким был опыт Патрика по выявлению бага корректности (выделено мной):
Чётко помню момент, когда одна из моих моделей Julia отказывалась обучаться. Я много месяцев пыталась заставить её работать, пробуя все трюки, которые мог придумать.
В конце концов, я нашёл ошибку: Julia/Flux/Zygote возвращал некорректные градиенты. После того, как я потратил так много энергии на указанные выше пункты 1 и 2, на этом пункте я просто сдался. Спустя ещё два часа разработки я успешно обучил модель… в PyTorch.
В обсуждении [48] этого поста другие пользователи писали, что у них тоже был похожий опыт.
@Samuel_Ainsworth [49]:
Как и @patrick-kidger, я пострадал от багов с некорректными градиентами в Zygote/ReverseDiff.jl. Это стоило мне недель жизни и заставило меня серьёзно пересмотреть уровень своего опыта во всей системе Julia AD. За все годы работы PyTorch/TF/JAX я ни разу не столкнулся с багом некорректных градиентов.
@JordiBolibar [50]:
С момента, когда я начал работать с Julia, у меня возникло два бага с Zygote, замедливших мою работу на много месяцев. С другой стороны, это заставило меня погрузиться в код и многое узнать о библиотеках, которые я использую. Но я оказался в ситуации, когда нагрузка стала слишком большой и я тратил много времени на отладку кода вместо того, чтобы проводить климатические исследования.
Учитывая чрезвычайную обобщённость Julia, не очевидно, можно ли решить проблемы корректности. В Julia нет формального понятия интерфейсов, в generic-функциях часто в пограничных случаях семантика не указывается, а природа самых общих косвенных интерфейсов не сделана чёткой (например, в сообществе Julia нет согласия по поводу того, что является числом).
В сообществе Julia множество способных и талантливых людей, щедро делящихся своим временем, трудом и опытом. Однако подобные систематические проблемы редко удаётся исправить снизу вверх, и мне кажется, что руководство проекта не согласно, что существует серьёзная проблема корректности. Оно соглашается с существованием отдельных несвязанных друг с другом проблем, но не с паттерном, который подразумевают эти проблемы.
Например, в то время, когда экосистема машинного обучения Julia была ещё более незрелой, один из создателей языка с энтузиазмом рассказывал об использовании Julia в продакшене для беспилотных автомобилей:

И хотя с тех пор, как я перестал быть активным участником, отношение к этому могло измениться, следующая цитата ещё одного создателя языка, произнесённая примерно в то же время, стала хорошей иллюстрацией разницы восприятий (выделено мной):
Думаю, самый важный вывод здесь заключается не в том, что Julia — отличный язык (хотя это и так) и что его нужно использовать для всего (хотя это и не самая плохая идея), а в том что его архитектура сделала важный шаг к возможности повторного использования кода. На самом деле в Julia вы можете взять обобщённые алгоритмы, написанные одним человеком, и собственные типы, написанные другими людьми, и просто использовать и эффективным образом. Это серьёзно повышает ставки уровня повторного использования кода в языках программирования. Проектировщикам языков не нужно копировать все возможности Julia, но им, по крайней мере, стоит понимать, почему он так хорошо работает, и стремиться к тому же уровню повторного использования кода в архитектурах будущего.
Каждый раз, когда появляется пост с критикой Julia, люди из сообщества обычно быстро пишут в ответ, что раньше были проблемы, но сейчас всё существенно улучшилось и большинство проблем уже устранено.
Примеры:
Эти ответы в своём узком контексте часто кажутся разумными, но в целом из-за них настоящие ситуации кажутся приуменьшенными, а глубокие проблемы остаются непризнанными и нерешёнными.
Мой опыт взаимодействия с языком и сообществом за прошедший десяток лет намекает, что, по крайней мере, с точки зрения базовой корректности язык Julia сейчас ненадёжен и не находится на пути к становлению надёжным. В большинстве случаев использования, на которые нацелена команда разработчиков Julia, риски попросту не стоят выгод.
Десять лет назад создатели языка Julia рассказали миру о вдохновляющих и амбициозных целях [56]. Я по-прежнему верю, что однажды их можно будет достичь, но без пересмотра паттернов, приведших проект в текущее состояние, это невозможно.
Автор:
PatientZero
Источник [57]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/375237
Ссылки в тексте:
[1] Julia: https://julialang.org/
[2] поля расстояний со знаком: https://github.com/JuliaGraphics/SignedDistanceFields.jl
[3] поиск ближайших соседей: https://github.com/yurivish/LowDimNearestNeighbors.jl
[4] паттерны Тьюринга: https://github.com/yurivish/TuringPatterns.jl
[5] также: https://github.com/JuliaWeb/Hyperscript.jl
[6] другими: https://github.com/yurivish/Treaps.jl
[7] broadcasting: https://julia-guide.netlify.app/broadcasting
[8] массивы: https://observablehq.com/@yurivish/julia-array-notation
[9] визиток: https://yuri.is/cardcrafting
[10] Сэмплирование плотности вероятности даёт некорректный результат: https://github.com/JuliaStats/Distributions.jl/issues/1241
[11] Сэмплирование массива может давать результаты со смещением: https://github.com/JuliaStats/StatsBase.jl/issues/642
[12] Функция произведения может давать некорректные результаты для 8-битных, 16-битных и 32-битных integer: https://github.com/JuliaLang/julia/issues/39183
[13] Подгонка гистограммы под массив Float64 может давать некорректные результаты: https://github.com/JuliaStats/StatsBase.jl/issues/616
[14] Базовые функции sum!, prod!, any! и all! могут возвращать некорректные результаты без сообщения об ошибке: https://github.com/JuliaLang/julia/issues/39385
[15] Summarystats возвращает квантили NaN для массивов со средним значением 0: https://github.com/JuliaStats/StatsBase.jl/pull/632
[16] OrderedDict может повреждать ключи: https://github.com/JuliaCollections/OrderedCollections.jl/issues/71
[17] Ошибка смещения на единицу в dayofquarter() в високосные годы: https://github.com/JuliaLang/julia/pull/36543
[18] Некорректные результаты симуляции при использовании типа number с величиной ошибки: https://github.com/JuliaPhysics/Measurements.jl/issues/64
[19] Pipeline со stdout=IOStream выполняет запись не по порядку: https://github.com/JuliaLang/julia/issues/36069
[20] Неверные результаты, поскольку некоторые методы copyto! не выполняют проверку на наложение: https://github.com/JuliaLang/julia/issues/39460
[21] Неверный поток управления if-else: https://github.com/JuliaLang/julia/issues/41096
[22] не работает с векторами Unitful: https://github.com/JuliaStats/Distances.jl/issues/201
[23] не работает с подстроками: https://github.com/JuliaLang/julia/issues/36406
[24] в некоторых случаях ломает матричное умножение: https://github.com/JuliaLang/julia/issues/39362
[25] не работал с OffsetArray: https://github.com/JuliaLang/julia/issues/34870
[26] OffsetArray: https://github.com/JuliaArrays/OffsetArrays.jl
[27] функцию настраиваемых индексов: https://julialang.org/blog/2017/04/offset-arrays/
[28] баг в ядре Julia: https://github.com/JuliaLang/julia/issues/39379
[29] Distributions: https://juliahub.com/ui/Packages/Distributions/xILW0/0.25.58
[30] StatsBase: https://juliahub.com/ui/Packages/StatsBase/EZjIG/0.33.16
[31] Большинство методов сэмплирования небезопасно и некорректно в случае наличия смещённых осей: https://github.com/JuliaStats/StatsBase.jl/issues/646
[32] Выравнивание распределения DiscreteUniform может вернуть некорректный ответ без сообщения об ошибке: https://github.com/JuliaStats/Distributions.jl/issues/1253
[33] counteq, countne, sqL2dist, L2dist, L1dist, L1infdist, gkldiv, meanad, maxad, msd, rmsd и psnr со смещёнными индексами могут возвращать некорректные результаты: https://github.com/JuliaStats/StatsBase.jl/issues/638
[34] Некорректное использование @inbounds может вызывать неверный расчёт статистики: https://github.com/JuliaStats/Distributions.jl/issues/1265
[35] Colwise и pairwise могут возвращать некорректные расстояния: https://github.com/JuliaStats/Distances.jl/issues/206
[36] Отображение вектора Weights, оборачивающего массив со смещением, выполняет доступ к памяти out-of-bounds: https://github.com/JuliaStats/StatsBase.jl/issues/643
[37] Эту проблему: https://github.com/JuliaLang/julia/issues/39367
[38] кодировании: https://github.com/quinnj/JSON3.jl/issues/63
[39] JSON: https://github.com/JuliaLang/julia/issues/34249
[40] HTTP-запросов: https://github.com/JuliaWeb/HTTP.jl/issues/626
[41] файлов Arrow: https://github.com/apache/arrow-julia/issues/101
[42] совместно: https://github.com/apache/arrow-julia/issues/102
[43] редактировании: https://github.com/fonsp/Pluto.jl/issues/826
[44] кода: https://github.com/fonsp/Pluto.jl/issues/751
[45] Julia: https://github.com/fonsp/Pluto.jl/issues/836
[46] Pluto: https://github.com/fonsp/Pluto.jl
[47] этом посте: https://kidger.site/thoughts/jax-vs-julia/
[48] обсуждении: https://discourse.julialang.org/t/state-of-machine-learning-in-julia/74385/4
[49] @Samuel_Ainsworth: https://discourse.julialang.org/t/state-of-machine-learning-in-julia/74385/13
[50] @JordiBolibar: https://discourse.julialang.org/t/state-of-machine-learning-in-julia/74385/21
[51] «Проблемы, перечисленные в этом посте, устранены».: https://news.ycombinator.com/item?id=11073642
[52] «Когда начинал знакомство с языком, я тоже жаловался на „ковбойскую“ культуру, которую наблюдал в среде Julia-разработчиков, но те времена уже прошли».: https://news.ycombinator.com/item?id=17726336
[53] «В 2016 году да. Но теперь проблемы уже решены».: https://news.ycombinator.com/item?id=22138071
[54] «В Julia нет технического принуждения к согласованности, однако семантическое значение generic-функций соблюдается большинством и обобщённый код работает».: https://news.ycombinator.com/item?id=27921467
[55] «Разумеется, баги есть, но ни один из них не является серьёзным».: https://news.ycombinator.com/item?id=30939963
[56] вдохновляющих и амбициозных целях: https://julialang.org/blog/2012/02/why-we-created-julia/
[57] Источник: https://habr.com/ru/post/666332/?utm_source=habrahabr&utm_medium=rss&utm_campaign=666332
Нажмите здесь для печати.