Skip to content

Instantly share code, notes, and snippets.

@appleparan
Last active March 15, 2021 21:24
Show Gist options
  • Save appleparan/bcda90b3c6e43c0128d6c31fbd4630bd to your computer and use it in GitHub Desktop.
Save appleparan/bcda90b3c6e43c0128d6c31fbd4630bd to your computer and use it in GitHub Desktop.
Julia Korea 답변

와 좋은 질문이네요. 덕분에 저도 원론적인 부분에서 다시 이해를 다잡는 좋은 기회가 되었습니다.

  • 기본적으로 Julia의 Argument Passing 은 "pass-by-sharing"입니다. 이 점을 염두에 두셔야 합니다.

그럼 BenchmarkTools.jl@btimeInteractiveUtils@code_lowered를 이용해서 각자의 코드가 내부적으로 어떻게 동작하는 지 알아봅시다.

  1. test1

    test1(C, A, B)
    
    37.206 ns (1 allocation: 128 bytes)
    
    CodeInfo(
    1 ─      C@_5 = C@_2%2 = Base.broadcasted(Main.:+, A, B)
    │        C@_5 = Base.materialize(%2)
    └──      return Main.nothing
    )

    이는 A .+ B 를 계산하고 이를 C와 다른 변수에 대입했다는 것입니다. 코드를 설명하자면 broadcast(.+) 할때 broadcasted를 통해 처리하지만 실제로는 계산이 매번 일어나지 않고 마지막에 materialize에서 한번에 일어납니다. (lazy evaluation)

    본론으로 들어가서 =는 이름을 바꾼다고 생각하시면 됩니다. 위에서 언급했듯이 argument passing인 경우에

    Function arguments themselves act as new variable bindings (new locations that can refer to values), but the values they refer to are identical to the passed values."

    라고 되어있죠? new variable "binding"즉 function argument의 변수 이름이 원래 외부의 변수 이름과 다르더라도 같은 object를 가르키지만, 이름은 다르게 불리울 수 있다는 것을 뜻합니다. 이걸 이해하시고 엄청 오래된 글이지만 이 글을 보시면 이해가 잘되실 겁니다. 여기서 설명하긴 너무 길어서요 ㅜㅜ

  2. test2

    test2(C, A, B)
    
    15.809 ns (0 allocations: 0 bytes)
    
    CodeInfo(
    1%1 = Base.broadcasted(Main.:+, A, B)
    │        Base.materialize!(C, %1)
    └──      return Main.nothing
    )

    이는 A .+ B 를 계산하고 이를 C에 대입했다는 것입니다. .= 은 실제로 내용을 바꾸는 겁니다. materialize!라고 되어있죠. !는 argument를 바꾼다고 함수에 표시할 때 씁니다. 이 함수 정의는 실제론 잘못되었죠. test!(C, A, B) 라고 정의했어야 합니다.

  3. test3

    test3(C, A, B)
    
    40.831 ns (1 allocation: 128 bytes)
    
    CodeInfo(
    1%1 = Base.broadcasted(Main.:+, A, B)
    │   %2 = Base.materialize(%1)
    │        Base.setindex!(C, %2, Main.:(:))
    └──      return Main.nothing
    )

    이는 A .+ B 를 계산하고 이를 C[:]에 대입했다는 것입니다. Julia는 Functional Programming도 들어가있습니다. FP는 모든 것은 함수로 이루어졌다고 봅니다. 그 중 Array Indexing은 불러올때는 getindex를 대입할 때는 setindex!라는 함수를 호출한다고 생각하시면 됩니다. 즉 여기선, test1과 비슷한 동작을 했지만, 대신 이건 [:]를 통해 다시 C에 대입합니다. 이러면 당연히 test2보다 allocation이 한 번 더 일어나므로 비효율적이죠. 그리고 이 함수 또한 test3!(C, A, B)라고 정의했어야합니다.

질문하신건 설명을 다 드린 것 같고 다른걸 테스트해보다가 재밌는걸 발견했습니다. @btime 으로 테스트해보니 제일 효율적인건

function test4(C, A, B)
	for i=1:length(C)
		C[i] = A[i] + B[i]
	end
end

7.357 ns (0 allocations: 0 bytes)

더군요. test4가 빠르다는건 예상했는데 broadcast보다 두배나 더 빠를 줄은 몰랐네요. 아마 Type Inference과정때문인거 같은데 이건 한번 타입 지정해보시고 알아보시죠 ㅋㅋ (실은 귀찮아서..)

@chkwon
Copy link

chkwon commented Mar 15, 2021

재미있네요. 자세한 설명 감사합니다. 따라 해보다가 더 재미있는걸 발견했네요. Matrix 크기에 따라 test2!test4! 의 속도 차이가 달라지네요.

n이 아주 작을땐 test4!가 빠르다가, 나중엔 test2!가 더 빠른데, 점점 크기가 더 커지니까 둘 다 비슷해지다가 다시 test4!가 역전을. 왜 그러는지는 잘 모르겠습니다.

------ n = 1 ----------
test2:   14.881 ns (0 allocations: 0 bytes)
test4:   2.951 ns (0 allocations: 0 bytes)
------ n = 5 ----------
test2:   30.999 ns (0 allocations: 0 bytes)
test4:   20.056 ns (0 allocations: 0 bytes)
------ n = 10 ----------
test2:   67.883 ns (0 allocations: 0 bytes)
test4:   80.464 ns (0 allocations: 0 bytes)
------ n = 50 ----------
test2:   658.313 ns (0 allocations: 0 bytes)
test4:   1.538 μs (0 allocations: 0 bytes)
------ n = 100 ----------
test2:   2.338 μs (0 allocations: 0 bytes)
test4:   6.974 μs (0 allocations: 0 bytes)
------ n = 500 ----------
test2:   100.673 μs (0 allocations: 0 bytes)
test4:   175.881 μs (0 allocations: 0 bytes)
------ n = 1000 ----------
test2:   965.300 μs (0 allocations: 0 bytes)
test4:   960.546 μs (0 allocations: 0 bytes)
------ n = 2000 ----------
test2:   5.218 ms (0 allocations: 0 bytes)
test4:   4.265 ms (0 allocations: 0 bytes)
------ n = 3000 ----------
test2:   13.092 ms (0 allocations: 0 bytes)
test4:   9.497 ms (0 allocations: 0 bytes)

코드는

using BenchmarkTools

function test2!(C::Matrix{T}, A::Matrix{T}, B::Matrix{T}) where T <: Float64
    C .= A .+ B
    return nothing
end

function test4!(C::Matrix{T}, A::Matrix{T}, B::Matrix{T}) where T <: Float64
    for i in 1:length(C)
        C[i] = A[i] + B[i]
    end
    return nothing    
end

function test24(n)
    A = rand(n, n)
    B = rand(n, n)
    C = zeros(n, n)
    
    println("------ n = $n ----------")
    print("test2: "); @btime test2!($C, $A, $B)
    print("test4: "); @btime test4!($C, $A, $B)    
end

for n in [1, 5, 10, 50, 100, 500, 1000, 2000, 3000]
    test24(n)
end

@appleparan
Copy link
Author

appleparan commented Mar 15, 2021

@btime은 한번 돌려서 저렇게 되는건가 싶어서 @benchmark로 돌려봤는데 test4같은 경우에는 사이즈가 커질 수록 오래 걸리네요. 아마 작을때는 broadcast를 fuse하는 overhead가 살짝있지만 scale이 커지면서 broadcast가 훨씬 더 효율적으로 작동하는 것 같습니다.

------ n = 1 ----------
test2: 
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     20.951 ns (0.00% GC)
  median time:      21.637 ns (0.00% GC)
  mean time:        21.812 ns (0.00% GC)
  maximum time:     42.025 ns (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     997
test4: 
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     4.330 ns (0.00% GC)
  median time:      4.463 ns (0.00% GC)
  mean time:        4.457 ns (0.00% GC)
  maximum time:     21.277 ns (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1000
------ n = 5 ----------
test2: 
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     43.907 ns (0.00% GC)
  median time:      47.826 ns (0.00% GC)
  mean time:        47.401 ns (0.00% GC)
  maximum time:     1.685 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     989
test4: 
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     24.677 ns (0.00% GC)
  median time:      25.417 ns (0.00% GC)
  mean time:        25.269 ns (0.00% GC)
  maximum time:     52.694 ns (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     996
------ n = 10 ----------
test2: 
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     98.400 ns (0.00% GC)
  median time:      107.225 ns (0.00% GC)
  mean time:        108.075 ns (0.00% GC)
  maximum time:     2.495 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     937
test4: 
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     89.296 ns (0.00% GC)
  median time:      97.254 ns (0.00% GC)
  mean time:        96.683 ns (0.00% GC)
  maximum time:     139.074 ns (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     957
------ n = 50 ----------
test2: 
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     1.189 μs (0.00% GC)
  median time:      1.349 μs (0.00% GC)
  mean time:        1.336 μs (0.00% GC)
  maximum time:     2.610 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     18
test4: 
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     2.049 μs (0.00% GC)
  median time:      2.231 μs (0.00% GC)
  mean time:        2.264 μs (0.00% GC)
  maximum time:     5.049 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     9
------ n = 100 ----------
test2: 
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     2.811 μs (0.00% GC)
  median time:      3.530 μs (0.00% GC)
  mean time:        3.370 μs (0.00% GC)
  maximum time:     9.596 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     9
test4: 
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     8.629 μs (0.00% GC)
  median time:      8.885 μs (0.00% GC)
  mean time:        9.025 μs (0.00% GC)
  maximum time:     18.049 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     3
------ n = 500 ----------
test2: 
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     224.075 μs (0.00% GC)
  median time:      240.439 μs (0.00% GC)
  mean time:        241.592 μs (0.00% GC)
  maximum time:     564.658 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1
test4: 
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     243.153 μs (0.00% GC)
  median time:      258.899 μs (0.00% GC)
  mean time:        263.251 μs (0.00% GC)
  maximum time:     2.545 ms (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1
------ n = 1000 ----------
test2: 
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     1.086 ms (0.00% GC)
  median time:      1.192 ms (0.00% GC)
  mean time:        1.195 ms (0.00% GC)
  maximum time:     2.226 ms (0.00% GC)
  --------------
  samples:          4180
  evals/sample:     1
test4: 
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     1.185 ms (0.00% GC)
  median time:      1.273 ms (0.00% GC)
  mean time:        1.280 ms (0.00% GC)
  maximum time:     5.579 ms (0.00% GC)
  --------------
  samples:          3904
  evals/sample:     1
------ n = 2000 ----------
test2:
BenchmarkTools.Trial:
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     7.403 ms (0.00% GC)
  median time:      7.855 ms (0.00% GC)
  mean time:        7.884 ms (0.00% GC)
  maximum time:     9.566 ms (0.00% GC)
  --------------
  samples:          634
  evals/sample:     1
test4:
BenchmarkTools.Trial:
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     7.818 ms (0.00% GC)
  median time:      8.139 ms (0.00% GC)
  mean time:        8.154 ms (0.00% GC)
  maximum time:     17.210 ms (0.00% GC)
  --------------
  samples:          614
  evals/sample:     1
------ n = 3000 ----------
test2:
BenchmarkTools.Trial:
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     18.067 ms (0.00% GC)
  median time:      18.864 ms (0.00% GC)
  mean time:        18.944 ms (0.00% GC)
  maximum time:     20.921 ms (0.00% GC)
  --------------
  samples:          264
  evals/sample:     1
test4:
BenchmarkTools.Trial:
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     17.833 ms (0.00% GC)
  median time:      19.587 ms (0.00% GC)
  mean time:        22.132 ms (0.00% GC)
  maximum time:     155.150 ms (0.00% GC)
  --------------
  samples:          226
  evals/sample:     1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment