julia!!

8 분 소요

what is julia?

  • julia라는 언어가 있습니다. 예전에 들어본 적이 있기는 해요. 외국에 계신 산업공학 분야(정확히는 최적화)의 교수님의 블로그에서 예전에 들어본 적은 있었는데 사용하지는 않았어요.
  • 사용하지 않은 이유는, 1) 이미 나는 파이썬을 배우고 있었고, 2) 파이썬에 숙달해 있다고 보기 어려웠고, 3) 사소하지만 array indexing이 0이 아닌 1부터 시작한다는 사실이 별로였고, 정도가 있었네요. 물론 기억은 명확하지 않습니다만.
  • 아무튼, julia라는 언어는 MIT에서 만든 언어고, 설명은 다음과 같습니다.

Julia is a high-level general-purpose[12] dynamic programming language that was originally designed to address the needs of high-performance numerical analysis and computational science

  • 입니다. 일단 high-level이라는 것은 파이썬이나, R처럼 나름 자연어에 가깝게 코딩할 수 있다는 것을 의미하죠(어셈블리, c처럼 컴퓨터에 가깝게 코딩하는 것이 아니라, 사람에게 가깝게 코딩하는 것을 말합니다).
  • 또한 dynamic programming이라는 것은 간단히 말하면 type을 compile레벨에서 잡는 것이 아니라는 것이죠. 예를 들어 보면 아래 코드에서 c에서는 변수의 타입을 함께 선언해준 반면, python에서는 변수의 타입을 선언해주지 않습니다. 즉 동적으로 타이핑한다는 것이죠.
int a = 10;//c에서는 변수의 타입을 함께 선언해줘야함 
a = 10 ## python에서는 변수의 타입이 함께 선언될 필요가 없음. 
  • 저는 python같은 언어의 경우 변수에 대한 타입이 런타임시에 바뀔 수 있도록 세팅되어 있어서 속도가 느릴 수 밖에 없다, 라고 생각하고 있었는데, julia의 경우는 python같은 언어의 강점인 high-level interface를 가지면서 속도도 매우 빠르다 라는 것이죠…..스고이….

  • 실제로 julia performance benchmark를 보면 다른 언어들과 비교할 수 없을만큼 빠르다는 것을 알 수 있어요. 특히 y축은 exponential이기 때문에, julia의 경우 fibonacci recursion문제에 대해서는 python에 비해서 100배 정도 빠르다고 합니다(memoization를 하지 않고 돌린 것이 아닐까 싶습니다만. 벤치마크는 제가 나중에 따로 해보도록 하겠습니다.

install it

  • 아무튼 말이 길었고 설치해보도록 하겠습니다.

julia on cloud

  • 설치하기 귀찮으신 분들은 여기에서 클라우드 상에서 쥴리아를 돌릴 수 있습니다.
  • 단, 무료로 사용했을 경우에는 저는 매우 느려서 그만뒀습니다.

install julia

  • julia download에서 설치하시면 됩니다. 저는 맥을 씁니다.
  • 보통 설치하고 나면 자동으로 .bash_profile에 등록되는데, julia의 경우 자동으로 등록되지 않아서 아래 부분을 추가해줍니다.
# julia added
 export PATH="/Applications/Julia-1.0.app/Contents/Resources/julia/bin:$PATH"
  • 이제 터미널에서 julia를 치면 아래처럼 잘 됩니다.
MDOM14110-2:~ frhyme$ julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.0.0 (2018-08-08)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia>

install julia pro

  • julia pro는 python에서 아나콘다와 유사한 느낌으로 보입니다.
  • 특히, jupyter notebook등과 연동도 되는 것처럼 보여서, 일단은 이 아이를 설치하려고 합니다. 그냥 julia를 설치하면 jupyter notebook과 연동하려면 조금 귀찮은 점들이 생기는 것 같아요.
  • 제가 python3을 설치하고 library를 이것저것 설치하다가 디펜던시 문제때문에 힘들었던 경험을 아나콘다가 다 해결해준 기억이 있어서, 가능하면 묶여진 형태를 설치하는 걸 좋아해요.

  • 설치는 어렵지 않습니다. 그냥 설치하고 그다음에 jupyter notebook에서 콘솔을 julia로 변경해줄 수 있습니다.

  • 또한, julia 진영에서는 juno라는 IDE를 홍보하고 있는 것 같습니다만, 저는 환영하지 않아요. atom기반의 에디터인것 같은데, atom이 맥에서 너무 느려서 싫어합니다.

julia tutorial

  • 이제 설치도 다 했고, 기본적인 julia syntax를 알아보겠습니다. 뭐 물론 learn julia in y min에 들어가면 다 있는 내용이기는 합니다.
  • 저는 vscode를 사용하는데, 마크다운에서 julia code block을 인식하지 못해서 Markdown Julia라는 익스텐션을 설치했습니다.

julia basic

  • 간단하게 사용해봤습니다. 문법적으로는 파이썬과 비슷한 부분이 상당히 많네요.
### basic 
println(repeat("=", 20))
x = 10 
println("$x, $(2x), $(x^2+2x+1)")## 2*x가 아니라 2x로 해도 알아서 잘 인식해줌 
let xx = 0;println(xx);end ## semicolon으로 oneliner 가능 
println("$(typeof('c')), $(typeof("C"))")## char와 string이 따옴표와 큰 따옴표로 구분됨 
println(repeat("=", 20))

## loop and iterator tools 
for x in enumerate(['a', 'b', 'c']);println(x);end ## loop는 python과 매우 유사함, enumerate도 사용가능 
println(repeat("=", 20))
for x in zip([1,2,3], ['a', 'b', 'c']);println(x);end ## zip도 사용가능 
println(repeat("=", 20))

### container 
x_lst = append!([1,2,3], 1)## append가 객체의 method가 아닌 function으로 존재함 또한 알아서 리턴해줌 
println("$x_lst, length: $(length(x_lst))")
println(repeat("=", 20))
dict1 = Dict("one" => 1, "two" => 2, "three" => 3)
println(typeof(dict1))## dictionary의 key, value에 대해서 타입이 정해짐
println("keys: $(keys(dict1))")## key, value가 function으로 사용됨 
println("values: $(values(dict1))")
set1 = Set([1,1,1,1,1,2,2,2,2,3])
println("set1: $set1")

### exception handling
println(repeat("=", 20))
try
    println(x_lst[0])
catch e
    println(e)## indexing이 1부터 시작하기 때문에 에러 발생함 
end
println(repeat("=", 20))

### function
function func1(x, y)
    x+y ## return이 따로 명시하지 않아도됨. 해도 되지만, 마지막에 존재하는 값이 자동으로 리턴됨 
end
anony_func1 = (x, y) -> x+y ## anonymous func
println(func1(2, 2))
println(anony_func1(2,2))
println(repeat("=", 20))

### list comprehension and dot function 
a = [i for i in range(0, 10)]
println(a)
println(a.^2)## dot function을 쓰려면 . 과 함께 써주면 됨 
println(repeat("=", 20))

### time checking 
@time begin
    [i for i in range(0, 10000)].*3
end
println(repeat("=", 20))

### type 
## type간에 inference tree가 있으며, 객체처럼 supertype으로 접근가능
## type inference을 이 방식으로 진행하는듯 
for t in [Float16, Int16]
    print("$t")
    while true
        print(" ==> $(supertype(t))")
        t = supertype(t)
        if t == Any;break;end
    end
    print("\n");println(repeat("=", 60))
end
====================
10, 20, 121
0
Char, String
====================
(1, 'a')
(2, 'b')
(3, 'c')
====================
(1, 'a')
(2, 'b')
(3, 'c')
====================
[1, 2, 3, 1], length: 4
====================
Dict{String,Int64}
keys: String["two", "one", "three"]
values: [2, 1, 3]
set1: Set([2, 3, 1])
====================
BoundsError([1, 2, 3, 1], (0,))
====================
4
4
====================
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
====================
  0.056191 seconds (12.91 k allocations: 857.824 KiB)
====================
Float16 ==> AbstractFloat ==> Real ==> Number ==> Any
============================================================
Int16 ==> Signed ==> Integer ==> Real ==> Number ==> Any
============================================================

pipeline

  • 함수라는 것은 특정 input을 받고 특정 output을 내뱉는 형식이죠. 만약 어떤 값, 혹은 데이터, 데이터 프레임 등 다양한 오브젝트에 대해서 함수를 연속적으로 적용해서 그 결과를 만들어야 한다면 어떻게 처리할 수 있을까요?
  • 보통 아래 코드처럼 처리합니다. 여기에는 두 가지 문제가 있는데요.
    • 우선 괄호와 함수명이 연속으로 들어가 있어서 가독성이 매우 떨어지고
    • 해당 데이터에 함수가 어떻게 적용되는지를 읽으려면 제일 안쪽부터 밖으로 읽어야하죠.
    • 이게 귀찮아서 함수를 그냥 큰 하나의 함수로 만들려고 하는 습관이 생기기 쉽고
  • 물론 map function을 쓰면 좀 나아지기는 합니다만, map function으로는 파이썬에서 새로운 복합합수를 정의해서 사용하기는 어려워요. 물론 reduce를 쓰면 가능해지기는 합니다만, 제가 다음에 다시 작성하겠습니다.
a = 10
func1(func2(func3(func4(a))))
  • 아무튼 간에, python에서는 부족한 이러한 함수적 코딩이라고 할까요, 이런 부분이 R에는 pipeline(%>%)이 있습니다. 그리고 julia에도 |>가 있습니다.
  • 간단하게 사용해보면 다음과 같습니다.
a = 10 |> (x-> x+1) |> (x-> 2*x) |> (x->x^2) |> log
println(a) ## 6.182084906716632
  • 들어갈 값을 넣고, |>를 표시하고, 함수에 넘겨주고, 다시 새로운 함수를 넘겨주고, 이렇게 pipeline을 구성합니다. 이렇게 만들면(물론 지금은 그냥 익명함수를 사용했지만), 개별함수는 모듈로써 만들고 이 함수를 연결하여 새로운 함수를 만드는 것이 용이하죠.
  • 다음은 여러가지로 적용해본 결과를 말합니다.
    • 그냥 값을 처리하기도 하고,
    • 정의된 여러 함수를 사용해서 새로운 복합함수를 만들어보기도 하고
    • 같은 짓을 map function으로 사용해보기도 했습니다.
  • 혹시나 해서 말하지만, julia에서 anonymous function은 x -> x+1과 같은 방식으로 정의됩니다.
## R에는 pipeline(%>%)이 있습니다. 이게 아주 편했는데, julia에도 있군요. 
## pipeline은 함수가 연속으로 나열되는 것을 말합니다. python에서도 map을 이용하면 구현이 되기는 하는데 
## 썩 마음에 들지는 않아요. 
k_lst = [1,2,3]
for (i, k) in enumerate(k_lst)
    ## 아래와 같이 함수를 연속으로 나열합니다. 
    new_x = k |> (x-> x+1) |> (x-> 2*x) |> (x->x^2) |> log
    println("$i: $new_x")
end

### 사실 위처럼 anonymous function을 사용할 때보다 defined function을 여러개 쓸 때 더 편한데요 
## 함수를 작게 쪼개고 이를 연결해서 새로운 함수를 만들 수 있다는 측면에서 매우 유용하죠. 

println(repeat("==", 20))
pipe_comp1 = x -> x+1
pipe_comp2 = x -> 2*x
pipe_comp3 = x -> x^2
pipe_comp4 = x -> log(x)
func_pipe = x -> x |> pipe_comp1 |> pipe_comp2 |> pipe_comp3 |> pipe_comp4
for (i, x) in enumerate([1, 2, 3])
    println("$i ==> $(func_pipe(x))")
end

println(repeat("==", 20))
## 물론 아래처럼 map function을 써도 됩니다, 취향이죠. 
r = [1,2,3,4]; x = copy(r)
pipes = [pipe_comp1, pipe_comp2, pipe_comp3, pipe_comp4]
for p in pipes 
    x = map(p, x)
end
println(x)
1: 2.772588722239781
2: 3.58351893845611
3: 4.1588830833596715
========================================
1 ==> 2.772588722239781
2 ==> 3.58351893845611
3 ==> 4.1588830833596715
========================================
[2.77259, 3.58352, 4.15888, 4.60517]

package managing

  • python에서는 pip로 package를 설치하고, import를 사용해서 패키지를 사용합니다.
  • julia에서는 다음과 같아요.
Pkg.available()## 사용하거나, 설치할 수 있는 모든 라이브러리 
Pkg.add("Calculus") ## 라이브러리를 설치 
Pkg.installed()## 설치된 라이브러리 리스트업
Pkg.add("Calculus")## install it with all dependent lib
using Calculus ## like from Calculus import * 
import Calculus ## like import pandas  

plotting

  • plotting하기 위해서는 Winston이라는 라이브러리를 쓴다고 합니다. 설치해야 할 경우에는 시간이 아주 많이 걸렸습니다. 약간 오버해서 1시간 정도 걸린 것 같네요. 그리고 결과적으로는 실패했습니다.

  • 일단 설치를 합니다. 아주 많은 디펜던트 패키지들을 설치하기 시작합니다. 이게 약 한시간 정도 걸리고요.

Pkg.add("Winston")
INFO: Cloning cache of Cairo from https://github.com/JuliaGraphics/Cairo.jl.git
INFO: Cloning cache of IniFile from https://github.com/JuliaIO/IniFile.jl.git
INFO: Cloning cache of Tk from https://github.com/JuliaGraphics/Tk.jl.git
INFO: Cloning cache of Winston from https://github.com/JuliaGraphics/Winston.jl.git
INFO: Installing Cairo v0.5.2
INFO: Installing IniFile v0.4.0
INFO: Installing Tk v0.5.2
INFO: Installing Winston v0.13.2
INFO: Building Homebrew
...
  • 이제 사용하려고 보니, 또 에러메시지가 뜹니다. Cairo를 빌드해주라고 하는군요..
using Winston
INFO: Precompiling module Cairo.
ERROR: LoadError: Cairo not properly installed. Please run
Pkg.build("Cairo")
  • 빌드해주고
Pkg.build("Cairo")
  • 이제 써보려고 하는데, 이제는 plot이라는 함수변수가 없다고 하네요….어쩌라는건지…
# plot some data
using Winston 

pl = Winston.plot(cumsum(rand(500) .- 0.5), "r", cumsum(rand(500) .- 0.5), "b")
# display the plot (not done automatically!)
display(pl)

# save the current figure
savefig("winston.svg")
# .eps, .pdf, & .png are also supported
# we used svg here because it respects the width and height specified above
UndefVarError: plot not defined
  • 그래서 좀 더 찾아보니, 이런 경우들이 종종 있는것 같아요. 아마도 제가 설치한 버전과 플로팅하기 위해 사용하는 것(특히, jupyter notebok처럼 웹에서 돌아가는 용도로는)에는 문제가 좀 있는 것 같고요.

  • 그래서 PyPlot라는 라이브러리를 깔아봅니다. 이 아이를 사용하려면, python에서 matplotlib가 설치되어 있어야 한다는데, 그럴 거면, 제가 그냥 파이썬으로 하지, 뭐하러 julia를 깔았는가 싶지만 뭐, 그냥 합니다.
  • 그러고 아래 코드를 실행하면 잘 됩니다. 흠…
Pkg.add("PyPlot")
using PyPlot
x = -2pi:0.1:2pi;
plot(x, sin(x.^2)./x);

is it faster??

  • 됐고, 이제 빠른지 확인해보겠습니다.

compute pi

  • pi를 계산해보려고 합니다. spigot algorithm 을 이용할 거에요.

  • 파이썬 코드로 바꾸면
%%timeit -n 10 -r 1
def compute_pi(n=10):
    r = 0
    for k in range(0, n):
        r+= 1/(16**k)*( 4/(8*k+1) - 2/(8*k+4) - 1/(8*k+5) - 1/(8*k+6))
    return r
compute_pi(20000)
  • julia 코드로 바꾸면
@time begin
    function compute_pi(n=10)
        r = 0
        for k in 0:9
            r+= 1/(16^k)*( 4/(8k+1) - 2/(8k+4) - 1/(8k+5) - 1/(8k+6))
        end
        r
    end
    compute_pi(20000)
end 
  • python의 경우는 2.46초가 걸리고, julia의 경우는 0.02초가 걸립니다….
  • 좀 더 비교를 해보긴 해야겠지만, 빠르긴 진짜 개빠르네요…..

  • 물론 numpy를 사용하면 더 빨라지기는 해요. 470 microsecond, 즉 0.00047 초니까, 더 빠르군요. 물론 다른 라이브러리를 써야 하는 것과, pure julia와는 비교를 하기 어렵기는 하죠.
%%timeit -n 1 -r 1
def compute_pi(n=10):
    return np.array([ 1/(16**k)*( 4/(8*k+1) - 2/(8*k+4) - 1/(8*k+5) - 1/(8*k+6)) for k in range(0, 10)]).sum()
print(compute_pi(20000))

wrap-up

  • 저는 일단 이 포스트를 다 쓰고 난 다음에, juliaPro를 지우고 julia1.0을 다시 설치하는 것이 더 적합하지 않을까? 생각하고 있습니다.
  • 디펜던시를 잘 해결하기 위해서 묶여있는 juliaPro를 설치한 건데, 생각보다 잘 해주지 못하고 있는 것 같아요.
  • 속도 측면에서는 julia를 python이 이길 수가 없습니다. 물론 다른 라이브러리르 가져와서 처리하면 달라지기는 하지만, pure하게 저렇게 빠르다는 것은 굉장히 큰 장점이죠. 실제로 numpy의 경우는 c를 사용하는건데, 순수하게 julia만으로 저 속도가 나오는 건 좀 압도적인 것 같아요.
  • 물론 몇 가지 테스트를 더 해보고싶기는 합니다.

reference

댓글남기기