09. Julia is fast (한글)
Julia is fast¶
출처¶
https://github.com/JuliaComputing/JuliaBoxTutorials/tree/master/introductory-tutorials/intro-to-julia (github : JuliaComputing/JuliaBoxTutorials/introductory-tutorials/intro-to-julia/)
Topics:
함께보기¶
- http://deepstat.tistory.com/45 (01. Getting started)(in English)
- http://deepstat.tistory.com/46 (01. Getting started(한글))
- http://deepstat.tistory.com/47 (02. Strings)(in English)
- http://deepstat.tistory.com/48 (02. Strings(한글))
- http://deepstat.tistory.com/49 (03. Data structures)(in English)
- http://deepstat.tistory.com/50 (03. Data structures(한글))
- http://deepstat.tistory.com/51 (04. Loops)(in English)
- http://deepstat.tistory.com/52 (04. Loops(한글))
- http://deepstat.tistory.com/53 (05. Conditionals)(in English)
- http://deepstat.tistory.com/54 (05. Conditionals(한글))
- http://deepstat.tistory.com/55 (06. Functions)(in English)
- http://deepstat.tistory.com/56 (06. Functions(한글))
- http://deepstat.tistory.com/57 (07. Packages)(in English)
- http://deepstat.tistory.com/58 (07. Packages(한글))
- http://deepstat.tistory.com/59 (08. Plotting)(in English)
- http://deepstat.tistory.com/60 (08. Plotting(한글))
- http://deepstat.tistory.com/61 (09. Julia is fast)(in English)
종종, 벤치마크를 이용해서 언어들을 비교한다. 이런 벤치마크를 통해서, 벤치마킹 대상을 더 잘 파악하게되고, 무엇이 차이인지 알게된다.
이 notebook의 목적은 간단한 벤치마크를 보여주기 위함이다.
(이 자료는 MIT의 Steven Johnson의 훌륭한 강의로부터 시작되었다: https://github.com/stevengj/18S096/blob/master/lectures/lecture1/Boxes-and-registers.ipynb.)
sum: 이해하기 쉬운 연산¶
sum(a) 라는 숫자를 합하는 함수를 생각해보자. 이는 아래와 같은 수식으로 표현된다.$$sum(a) = \sum_{i=1}^n a_i$$ 단, $n$은 a의 길이이다.
a = rand(10^7)
sum(a)
각 원소가 평균 0.5인 분포에서 생성되는 난수이므로, 기대되는 결과는 0.5 * 10^7이다.
@time sum(a)
@time sum(a)
@time sum(a)
@time 매크로로부터 얻어지는 결과는 조금씩 다르기 때문에, 벤치마킹 하기에 최적의 선택은 아니다.
운 좋게도, Julia는 BenchmarkTools.jl 패키지가 있어서 쉽고 정확한 벤치마킹을 할 수 있다.
using Pkg
Pkg.add("BenchmarkTools")
using BenchmarkTools
C는 보통 좋은 표준이라고 한다. 왜냐하면 사람에게 어렵고, 컴퓨터에게 좋은 언어이기 때문이다. 그럼에도 불구하고, C 사용자는 좋든 나쁘든 많은 종류의 최적화를 사용할 수 있다.
이 notebook을 만든 사람은 C에 대해서 말하려는 것도 아니고, 아래 코드를 읽을 것도 아니지만, Julia session에서 C 코드를 돌릴 수 있다는 것을 아는 것만으로도 충분하다. 참고로 """ 기호는 여러 줄의 문자를 넣으려고 쓰는 것이다.
using Libdl
C_code = """
#include <stddef.h>
double c_sum(size_t n, double *X) {
double s = 0.0;
for (size_t i = 0; i < n; ++i) {
s += X[i];
}
return s;
}
"""
const Clib = tempname() # 임시 파일을 만든다.
# gcc에 C_code를 넣어서 공유 라이브러리를 컴파일한다.
# (gcc가 설치돼 있을때만 작동한다.):
open(`gcc -fPIC -O3 -msse3 -xc -shared -o $(Clib * "." * Libdl.dlext) -`, "w") do f
print(f, C_code)
end
# C 함수를 불러오는 Julia 함수를 정의한다.
c_sum(X::Array{Float64}) = ccall(("c_sum", Clib), Float64, (Csize_t, Ptr{Float64}), length(X), X)
c_sum(a)
c_sum(a) ≈ sum(a) # \approx 를 치고 <TAB>을 누르면 ≈ 기호를 쓸 수 있다.
c_sum(a) - sum(a)
≈ # `isapprox` 함수의 별명이라는 것을 알 수 있다.
?isapprox
이제 C 코드를 Julia에서 바로 벤치마크 할 수 있다.
c_bench = @benchmark c_sum($a)
println("C: Fastest time was $(minimum(c_bench.times) / 1e6) msec")
d = Dict() # a "dictionary", i.e. an associative array
d["C"] = minimum(c_bench.times) / 1e6 # in milliseconds
d
만일 C가 부동 소수점 연산을 재정렬하도록 허용하면 SIMD (single instruction, multiple data) instruction으로 벡터화될거다.
const Clib_fastmath = tempname() # make a temporary file
# The same as above but with a -ffast-math flag added
open(`gcc -fPIC -O3 -msse3 -xc -shared -ffast-math -o $(Clib_fastmath * "." * Libdl.dlext) -`, "w") do f
print(f, C_code)
end
# define a Julia function that calls the C function:
c_sum_fastmath(X::Array{Float64}) = ccall(("c_sum", Clib_fastmath), Float64, (Csize_t, Ptr{Float64}), length(X), X)
c_fastmath_bench = @benchmark $c_sum_fastmath($a)
d["C -ffast-math"] = minimum(c_fastmath_bench.times) / 1e6 # in milliseconds
PyCall 패키지를 이용해서 Python을 Julia에서 사용할 수 있다:
using Pkg; Pkg.add("PyCall")
using PyCall
# Python 내장 "sum"함수를 불러온다:
pysum = pybuiltin("sum")
pysum(a)
pysum(a) ≈ sum(a)
py_list_bench = @benchmark $pysum($a)
d["Python 내장"] = minimum(py_list_bench.times) / 1e6
d
하드웨어 "SIMD"를 활용하지만 작동 할 때만 작동한다.
numpy는 Python에서 호출 할 수 있는 최적화된 C 라이브러리다. 다음과 같이 Julia 내에 불러올 수 있다.
numpy_sum = pyimport("numpy")["sum"]
py_numpy_bench = @benchmark $numpy_sum($a)
numpy_sum(a)
numpy_sum(a) ≈ sum(a)
d["Python numpy"] = minimum(py_numpy_bench.times) / 1e6
d
py"""
def py_sum(A):
s = 0.0
for a in A:
s += a
return s
"""
sum_py = py"py_sum"
py_hand = @benchmark $sum_py($a)
sum_py(a)
sum_py(a) ≈ sum(a)
d["Python 직접 작성"] = minimum(py_hand.times) / 1e6
d
C가 아니라 Julia로 바로 써졌다!
@which sum(a)
j_bench = @benchmark sum($a)
d["Julia 내장"] = minimum(j_bench.times) / 1e6
d
function mysum(A)
s = 0.0 # s = zero(eltype(a))
for a in A
s += a
end
s
end
j_bench_hand = @benchmark mysum($a)
d["Julia 직접 작성"] = minimum(j_bench_hand.times) / 1e6
d
function mysum_simd(A)
s = 0.0 # s = zero(eltype(A))
@simd for a in A
s += a
end
s
end
j_bench_hand_simd = @benchmark mysum_simd($a)
mysum_simd(a)
d["Julia SIMD이용"] = minimum(j_bench_hand_simd.times) / 1e6
d
for (key, value) in sort(collect(d), by=last)
println(rpad(key, 25, "."), lpad(round(value; digits=2), 6, "."))
end