Factored kernelsを使った簡単な実験
前回の内容を踏まえて、Factored kernelsのご利益を検証してみました。
ドキュメントでFactored kernelsの例としてGaussian kernelsが引き合いに出されていましたが、Kernel
モジュールは2次元配列をそのまま返してしまいますので、KernelFactors
モジュールを使って比較します。なお、どちらもImageFiltering
パッケージに含まれています。
実験に使ったスクリプトはこちら
前準備
テスト画像とベンチマーク計測用の関数を用意しておきます。関数filtering
とfiltering!
はそれぞれimfilter
とimfilter!
用です。
using Images, TestImages, ImageFiltering
using BenchmarkTools
println("nthreads = $(Threads.nthreads())")
img = testimage("")
function filtering(img, kernel)
imfilter(img, kernel) # precompile
bm = @benchmark imfilter(img, kernel)
display("text/plain", bm)
println("")
end
function filtering!(out, img, kernel)
imfilter!(out, img, kernel) # precompile
bm = @benchmark imfilter!(out, img, kernel)
display("text/plain", bm)
println("")
end
パフォーマンスの比較
Gaussian blurを題材に、Kernel
, KernelFactors
とPython+OpenCV
を比べてみます。
Kernelモジュールの場合
まず、2次元のGaussianカーネルを準備します。カーネルサイズは13x13です。
# Kernel module ver.
kernel = Kernel.gaussian((3, 3))
これを使ってimfilter
とimfilter!
のベンチマークを取ってみます。なお、下記の結果はexport JULIA_NUM_THREADS=1
とした状態です。
julia> filtering(img, kernel)
BenchmarkTools.Trial:
memory estimate: 4.14 MiB
allocs estimate: 309
--------------
minimum time: 12.670 ms (0.00% GC)
median time: 13.316 ms (0.00% GC)
mean time: 13.907 ms (4.15% GC)
maximum time: 72.896 ms (78.81% GC)
--------------
samples: 359
evals/sample: 1
julia> out = similar(img)
julia> filtering!(out, img, kernel)
BenchmarkTools.Trial:
memory estimate: 413.09 KiB
allocs estimate: 119
--------------
minimum time: 13.554 ms (0.00% GC)
median time: 14.015 ms (0.00% GC)
mean time: 14.413 ms (1.58% GC)
maximum time: 73.900 ms (79.41% GC)
--------------
samples: 347
evals/sample: 1
どちらも約14ms程度でした。これは遅い。
KernelFactorsモジュールの場合
前回の記事で紹介したFactored kernelの特性を利用した方法で計算させてみます。Kernel
モジュールをKernelFactors
に切り替えるだけで、あとは先程と同じです。
## KernelFactors module ver.
kernel = KernelFactors.gaussian((3, 3))
先ほどと同じようにベンチマークを取ってみます。なお、下記の結果もexport JULIA_NUM_THREADS=1
とした状態です。
julia> filtering(img, kernel)
BenchmarkTools.Trial:
memory estimate: 4.13 MiB
allocs estimate: 292
--------------
minimum time: 9.564 ms (0.00% GC)
median time: 10.171 ms (0.00% GC)
mean time: 10.691 ms (5.07% GC)
maximum time: 73.211 ms (83.50% GC)
--------------
samples: 467
evals/sample: 1
julia> out = similar(img)
julia> filtering!(out, img, kernel)
BenchmarkTools.Trial:
memory estimate: 398.23 KiB
allocs estimate: 102
--------------
minimum time: 10.450 ms (0.00% GC)
median time: 10.801 ms (0.00% GC)
mean time: 11.042 ms (1.67% GC)
maximum time: 71.028 ms (82.64% GC)
--------------
samples: 453
evals/sample: 1
4ms程度速くなりましたが、まだまだ遅く感じます。
Python + OpenCVの場合
ここで、比較対象としてOpenCVがどの程度か確認してみます。
In [1]: import cv2
In [2]: img = cv2.imread("cameraman.png")
In [6]: %%timeit
...: cv2.GaussianBlur(img, (13, 13), 3)
...:
1.53 ms ± 215 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
- Python 3.7.0
- opencv-python v3.4.2
ちょっとしたチューニング
上記の結果は少し期待はずれだったのですが、上記の実験ではどうもCPUを使い切れていないようでした。そこで、Juliaが使えるスレッド数を増やしてパフォーマンスの変換を観察してみます。
やり方は、
- 今までの実験を一つのスクリプトとして保存しておきます
-
export JULIA_NUM_THREADS=n
(n=1~4)とする - 先程のスクリプトを実行
この結果のサマリは次のようになりました。なお、imfilter
とimfilter!
の速度はほぼ同じだったので、以降ではimfilter!
の結果のみを示します。また、各スレッド数のベンチマークは@benchmark
のmean timeのみを示します。
Kernelモジュールとimfilter!
スレッドを増やすとパフォーマンスが落ちてしまいました。
nthreads | Benchmark |
---|---|
1 | 14.413 |
2 | 27.138 |
3 | 27.249 |
4 | 24.990 |
KernelFactorsモジュールとimfilter!
スレッド数2で頭打ちでした。
nthreads | Benchmark |
---|---|
1 | 11.042 |
2 | 5.211 |
3 | 5.812 |
4 | 5.394 |
以上をまとめたのが次のグラフです。スレッド数が1か2以上でパフォーマンスが大きく変わってしまうみたいですね。
あとがき
- Kernelのバージョンでスレッド数を増やすと、なんでこんなにパフォーマンス落ちたんでしょう。
- アルゴリズムの改良は大事ですね。
- OpenCV速いですね。
実行環境
_
_ _ _(_)_ | Documentation: https://docs.julialang.org
(_) | (_) (_) |
_ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help.
| | | | | | |/ _` | |
| | |_| | | | (_| | | Version 1.0.0 (2018-08-08)
_/ |\__'_|_|_|\__'_| | Official https://julialang.org/ release
|__/ |
julia> versioninfo()
Julia Version 1.0.0
Commit 5d4eaca0c9 (2018-08-08 20:58 UTC)
Platform Info:
OS: macOS (x86_64-apple-darwin14.5.0)
CPU: Intel(R) Core(TM) i5-6360U CPU @ 2.00GHz
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-6.0.0 (ORCJIT, skylake)