Help us understand the problem. What is going on with this article?

JuliaImagesの備忘録その4

More than 1 year has passed since last update.

Factored kernelsを使った簡単な実験

前回の内容を踏まえて、Factored kernelsのご利益を検証してみました。

ドキュメントでFactored kernelsの例としてGaussian kernelsが引き合いに出されていましたが、Kernelモジュールは2次元配列をそのまま返してしまいますので、KernelFactorsモジュールを使って比較します。なお、どちらもImageFilteringパッケージに含まれています。

実験に使ったスクリプトはこちら

前準備

テスト画像とベンチマーク計測用の関数を用意しておきます。関数filteringfiltering!はそれぞれimfilterimfilter!用です。

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, KernelFactorsPython+OpenCVを比べてみます。

Kernelモジュールの場合

まず、2次元のGaussianカーネルを準備します。カーネルサイズは13x13です。

# Kernel module ver.
kernel = Kernel.gaussian((3, 3))

これを使ってimfilterimfilter!のベンチマークを取ってみます。なお、下記の結果は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)とする
  • 先程のスクリプトを実行

この結果のサマリは次のようになりました。なお、imfilterimfilter!の速度はほぼ同じだったので、以降では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以上でパフォーマンスが大きく変わってしまうみたいですね。

imfilter!.png

あとがき

  • 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)
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away