Статьи

Джулия — новый подход к численным вычислениям и науке о данных

Язык программирования Julia был создан в 2009 году Джеффом Безансоном, Стефаном Карпински и Viral B Shah. Он был широко анонсирован в 2012 году, и с тех пор у него растет сообщество участников и пользователей. В официальном реестре более 700 пакетов, а базовый язык насчитывает более 400 участников .

Юлия стремится решить «проблему двух языков», которая слишком часто встречается в технических вычислениях. Интерактивное исследование данных и создание прототипов алгоритмов невероятно удобно и продуктивно для динамических языков высокого уровня, таких как Python, Matlab или R, но масштабирование исходных прототипов для обработки больших наборов данных быстро приводит к ограничениям производительности в этих средах. Обычный декабрьский подход заключался в использовании скомпилированных расширений C (или C ++, или Fortran) для оптимизации вычислений, критичных к производительности. Для обычных операций со стандартными массивами и фреймами данных существуют зрелые библиотеки расширений — например, NumPy, SciPy, Pandas и т. Д. В Python. Однако существует напряженность, поскольку существующие библиотеки не реализуют всю необходимую вам функциональность. Переход на статически скомпилированный язык более низкого уровня для повышения производительности приводит к возникновению ошибок и снижению производительности при переключении контекста, а также увеличивает барьер для входа при добавлении или написании новых библиотек. Существуют недавние проекты на существующих языках, которые помогают улучшить эту ситуацию — Cython и Numba для Python, улучшенная компиляция точно в срок (JIT) в Matlab, Rcpp для R и другие. Однако требования совместимости для работы в той же языковой семантике, что и существующая экосистема, означают, что эти подходы либо ограничены легко оптимизируемым ограничительным подмножеством языка, либо не подходят для повседневного интерактивного использования.

Юлия использует другой («жадный») подход. Разработка нового языка без необходимости поддерживать исходную (и двоичную, чтобы скомпилированные расширения продолжали работать) совместимость с языками, разработанными десятилетия назад, позволяет нам внедрять современные методы, которые в настоящее время хорошо известны для того, чтобы заставить динамический язык работать хорошо. Вывод типов и тщательный дизайн стандартной библиотеки, позволяющие использовать преимущества информации о типах, позволяют оптимизировать и агрессивно специализировать полностью универсальный код. Код Julia — это JIT, скомпилированный с нативными инструкциями через среду компилятора LLVM, которая обеспечивает переносимость на несколько платформ и архитектур. Благодаря LLVM код Julia может использовать аппаратную векторизацию посредством инструкций SIMD на современных процессорах.

В качестве простого примера мы можем рассмотреть базовую реализацию двойных чисел. Это числа вида `a + b ε`, похожие на комплексные числа, но вместо` i² = -1`, двойные числа определяются как `ε² = 0`. Это числовой тип, который должен иметь семантику неизменяемых значений, а не изменяемую ссылочную семантику, поэтому мы определяем их в Julia с помощью ключевого слова «immutable». Компонент value и компонент epsilon должны иметь одинаковый числовой тип, который мы представляем в Julia с параметрическим типом `{T}` следующим образом:

1
2
3
4
5
6
```
immutable DualNumber{T}
    value::T
    epsilon::T
end
```

Чтобы определить основные арифметические операции для этого нового типа, мы сначала импортируем операторы сложения и умножения, а затем расширяем их новыми методами, определенными для типа `DualNumber`:

1
2
3
4
5
6
7
8
9
```
import Base: +, *
# one-liner function definition:
+(x::DualNumber, y::DualNumber) = DualNumber(x.value + y.value, x.epsilon + y.epsilon)
# longer form function definition:
function *(x::DualNumber, y::DualNumber)
    return DualNumber(x.value * y.value, x.value * y.epsilon + y.value * x.epsilon)
end
```

Мы можем проанализировать различные этапы компиляции Julia с помощью LLVM для оптимизации собственного кода для различных специализаций

1
`DualNumber`, such as `DualNumber{Int64}` and `DualNumber{Float64}`, using the `@code_llvm` and `@code_native` macros:
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
```
julia> x_i64 = DualNumber(1, 2)
DualNumber{Int64}(1,2)
 
julia> y_i64 = DualNumber(3, 4)
DualNumber{Int64}(3,4)
 
julia> @code_llvm x_i64 * y_i64
 
define void @"julia_*_21313"(%DualNumber* sret, %DualNumber*, %DualNumber*) {
top:
  %3 = load %DualNumber* %1, align 8
  %4 = extractvalue %DualNumber %3, 0
  %5 = extractvalue %DualNumber %3, 1
  %6 = load %DualNumber* %2, align 8
  %7 = extractvalue %DualNumber %6, 0
  %8 = extractvalue %DualNumber %6, 1
  %9 = mul i64 %7, %4
    %10 = insertvalue %DualNumber undef, i64 %9, 0
  %11 = mul i64 %8, %4
  %12 = mul i64 %7, %5
  %13 = add i64 %11, %12
  %14 = insertvalue %DualNumber %10, i64 %13, 1
  store %DualNumber %14, %DualNumber* %0, align 8
  ret void
}
 
julia> @code_native x_i64 * y_i64
        .text
Filename: none
Source line: 3
        pushq   %rbp
        movq    %rsp, %rbp
Source line: 3
        movq    (%rsi), %r8
        movq    8(%rdx), %rax
Source line: 3
        imulq   %r8, %rax
Source line: 3
        movq    (%rdx), %rcx
        movq    8(%rsi), %rdx
Source line: 3
        imulq   %rcx, %rdx
        imulq   %r8, %rcx
        movq    %rcx, (%rdi)
        addq    %rax, %rdx
        movq    %rdx, 8(%rdi)
        movq    %rdi, %rax
        popq    %rbp
        ret
 
julia> x_f64 = DualNumber(1.0, 2.0)
DualNumber{Float64}(1.0,2.0)
 
julia> y_f64 = DualNumber(3.0, 4.0)
DualNumber{Float64}(3.0,4.0)
 
julia> @code_llvm x_f64 * y_f64
 
define void @"julia_*_21344"(%DualNumber.12* sret, %DualNumber.12*, %DualNumber.12*) {
top:
  %3 = load %DualNumber.12* %1, align 8
  %4 = extractvalue %DualNumber.12 %3, 0
  %5 = extractvalue %DualNumber.12 %3, 1
  %6 = load %DualNumber.12* %2, align 8
  %7 = extractvalue %DualNumber.12 %6, 0
  %8 = extractvalue %DualNumber.12 %6, 1
  %9 = fmul double %4, %7
  %10 = insertvalue %DualNumber.12 undef, double %9, 0
  %11 = fmul double %4, %8
  %12 = fmul double %5, %7
  %13 = fadd double %11, %12
  %14 = insertvalue %DualNumber.12 %10, double %13, 1
  store %DualNumber.12 %14, %DualNumber.12* %0, align 8
  ret void
}
 
julia> @code_native x_f64 * y_f64
        .text
Filename: none
Source line: 3
        pushq   %rbp
        movq    %rsp, %rbp
Source line: 3
        vmovsd  (%rsi), %xmm1
Source line: 3
        vmulsd  8(%rdx), %xmm1, %xmm0
Source line: 3
        vmovsd  (%rdx), %xmm3
Source line: 3
        vmulsd  8(%rsi), %xmm3, %xmm2
        vmulsd  %xmm3, %xmm1, %xmm1
        vmovsd  %xmm1, (%rdi)
        vaddsd  %xmm2, %xmm0, %xmm0
        vmovsd  %xmm0, 8(%rdi)
        movq    %rdi, %rax
        popq    %rbp
        ret
```
 
Custom printing for a type can be achieved by extending the `show` function:
 
```
julia> function Base.show(io::IO, x::DualNumber)
           print(io, string(x.value, "+", x.epsilon, "ε"))
       end
show (generic function with 98 methods)
 
julia> DualNumber(1, 2)
1+2ε
```

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
```
julia> Base.zero{T}(::Type{DualNumber{T}}) = DualNumber(zero(T), zero(T))
zero (generic function with 14 methods)
 
julia> values1 = rand(1:10, 6, 4);
 
julia> epsilon1 = rand(1:10, 6, 4);
 
julia> values2 = rand(1:10, 4, 3);
 
julia> epsilon2 = rand(1:10, 4, 3);
 
julia> dualmat1 = map(DualNumber, values1, epsilon1)
6x4 Array{DualNumber{Int64},2}:
 9+10ε  4+7ε   6+3ε   4+4ε
 10+3ε  7+9ε   4+4ε   7+2ε
 2+7ε   9+4ε   10+8ε  5+8ε
 10+4ε  2+8ε   3+6ε   6+2ε
 3+2ε   7+10ε  5+2ε   5+4ε
 3+4ε   4+2ε   7+3ε   8+1ε
 
julia> dualmat2 = map(DualNumber, values2, epsilon2)
4x3 Array{DualNumber{Int64},2}:
 6+2ε   2+8ε  4+3ε
 5+3ε   5+2ε  10+1ε
 3+7ε   5+6ε  3+3ε
 1+10ε  6+8ε  2+6ε
 
julia> dualmat1 * dualmat2
6x3 Array{DualNumber{Int64},2}:
 96+220ε   92+242ε   102+200ε
 114+216ε  117+257ε  136+209ε
 92+245ε   129+256ε  138+183ε
 85+191ε   81+240ε   81+195ε
 73+184ε   96+196ε   107+183ε
 67+191ε   109+177ε  89+129ε
```

Двойные числа имеют очень полезное приложение для оптимизации, где их можно использовать для автоматического дифференцирования. См. Http://julialang.org/blog/2015/10/auto-diff-in-julia для отчета Summer of Code о пакете ForwardDiff.jl, который содержит всестороннюю и высокопроизводительную реализацию двойных чисел и обобщений для более высокого уровня. производные.

Предполагается, что Julia — лучший из языков обоих миров, предлагая как производительность Python, так и производительность, сравнимую с C ++, и предоставляя широкий спектр стилей кода, начиная от циклов типа C и заканчивая функциональными абстракциями высокого уровня. Но есть миллионы строк существующего кода на растущем числе других языков, которые было бы глупо не использовать. Дизайнеры Джулии признают это и включают функции, облегчающие взаимодействие с другими языками. Вызов функций из общих библиотек C или Fortran может быть выполнен без лишнего стандартного связующего кода, интерфейс внешней функции `ccall` прост в использовании даже из интерактивного приглашения Джулии read-eval-print-loop (REPL).

1
2
3
4
```
julia> ccall((:printf, "libc"), Void, (Cstring, Cstring), "%s\n", "Hello from C printf!")
Hello from C printf!
```

См. Http://docs.julialang.org/en/release-0.4/manual/calling-c-and-fortran-code для получения более подробной информации. Пакеты PyCall.jl (https://github.com/stevengj/PyCall.jl) и JavaCall.jl (https://github.com/aviks/JavaCall.jl) позволяют напрямую вызывать библиотеки Python или Java в аналогичных библиотеках. путь. Интерфейс сторонней функции для библиотек C ++ посредством встраивания библиотеки компилятора LLVM Clang находится в стадии разработки (https://github.com/Keno/Cxx.jl).

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

Джулия встроила поддержку параллельного программирования с распределенной памятью, используя многопроцессорную модель передачи сообщений ( http://docs.julialang.org/en/release-0.4/manual/parallel-computing ). Экспериментальная поддержка многопоточности совместно используемой памяти была недавно включена в основную ветку разработки благодаря сотрудничеству с лабораториями Intel.

Julia версия 0.4.0 была выпущена в октябре со многими новыми функциями, такими как кэширование скомпилированных пакетов и переписанный сборщик мусора с более высокой производительностью. Точечные выпуски помечаются ежемесячно, что приводит к исправлению ошибок в стабильной ветке, в то время как на master для 0.5.0 разрабатываются новые функции, выпуск которых планируется в начале 2016 года. Язык прогрессирует до версии 1.0, доработав интерфейсы стандартных библиотек и функции, и возможности разработки, такие как интегрированный плагин отладчика на основе LLDB (http://lldb.llvm.org) и Atom (https://atom.io) и среда разработки. Язык уже очень удобен для анализа данных и разработки алгоритмов, и быстро развиваются библиотеки для соединений с базой данных, разработки пользовательского интерфейса, статистического анализа, численной оптимизации и визуализации. Пакеты для крупномасштабного параллельного анализа данных с использованием библиотек HDFS, Hive и MPI, таких как Elemental, являются одними из основных моментов растущей экосистемы Julia, список зарегистрированных пакетов см. На http://pkg.julialang.org/.

Вы можете попробовать Джулию в своем браузере на https://www.juliabox.org или загрузить последнюю версию с http://julialang.org/downloads. Язык разработан на GitHub по адресу https://github.com/JuliaLang/julia, а список рассылки — по адресу https://groups.google.com/forum/#!forum/julia-users.