2022/07/07(木) [n年前の日記]
#1 [python] Pillowのgetpixel()は本当に遅いのか気になったので確認してみた
Python で画像を扱える Pillow (PIL)モジュールについて、1ドット単位で値を読んだり書いたりできる .getpixel()/.putpixel() というメソッドがあるのだけど。巷の各種記事では処理速度が遅いと書いてあって、本当にそうなのかなと気になってきた。
そんなわけで、ベンチマークを取ってみた。環境は、Windows10 x64 21H2 + Python 3.9.13 64bit + Pillow 9.1.1。CPU は AMD Ryzen 5 5600X (6コア12スレッド、ベースクロック3.7GHz)。
4288x2848ドットの画像に対してひたすらドットを読むだけの処理をしてみた。ちなみに利用した画像は以下。
_十代の少女 可愛い 肖像画 - Pixabayの無料写真
ソースは以下。
_05getpixel_bench.py
動作には、Pillow と benchmarker が必要。
結果は以下のような感じになった。
.getdata() を使えばたしかに速くなるけれど、一次元配列に対してアクセスするような書き方になるので、可読性はほんのちょっと、若干かすかに、ビミョーに悪くなるような気もする。
対して、.load() を使ったアクセスなら、.getpixel() と同様に x, y を指定してアクセスできるし、しかも .getdata() を使った場合とそれほど処理速度も変わらないわけで…。
個人的には、.getdata() より、可読性と処理速度の両方をそこそこ得られる .load() を使ったほうがいいのではないかと思えてきた。
余談。手元で実験に使ってるスクリプトが、とにかく遅くて…。.load() を使って画像の各ドットにアクセスしていたのだけど、.getdata() を使ったらもっと速く処理できないかと少し期待しながらベンチマークを取ったわけで。ある意味、残念な結果になってしまった。今回、そこらへんを変えてみても結果は変わらないようだなと…。
そんなわけで、ベンチマークを取ってみた。環境は、Windows10 x64 21H2 + Python 3.9.13 64bit + Pillow 9.1.1。CPU は AMD Ryzen 5 5600X (6コア12スレッド、ベースクロック3.7GHz)。
4288x2848ドットの画像に対してひたすらドットを読むだけの処理をしてみた。ちなみに利用した画像は以下。
_十代の少女 可愛い 肖像画 - Pixabayの無料写真
ソースは以下。
_05getpixel_bench.py
from PIL import Image from benchmarker import Benchmarker infile = "teen-girl-4467541_4288x2848.jpg" def main(): im = Image.open(infile) width, height = im.size print("%d x %d" % (width, height)) with Benchmarker(100) as bench: @bench(".getdata()") def _(bm): im = Image.open(infile) dt = im.getdata() for y in range(height): idx = width * y for x in range(width): _ = dt[idx + x] @bench(".load()") def _(bm): im = Image.open(infile) src = im.load() for y in range(height): for x in range(width): _ = src[x, y] @bench(".getpixel()") def _(bm): im = Image.open(infile) for y in range(height): for x in range(width): _ = im.getpixel((x, y)) if __name__ == '__main__': main()
動作には、Pillow と benchmarker が必要。
pip install Pillow -U pip install Benchmarker -U
結果は以下のような感じになった。
> py 05getpixel_bench.py 4288 x 2848 ## benchmarker: release 4.0.1 (for python) ## python version: 3.9.13 ## python compiler: MSC v.1929 64 bit (AMD64) ## python platform: Windows-10-10.0.19044-SP0 ## python executable: C:\Python\Python39-64\python.exe ## cpu model: AMD64 Family 25 Model 33 Stepping 2, AuthenticAMD ## parameters: loop=100, cycle=1, extra=0 ## real (total = user + sys) .getdata() 0.7155 0.7188 0.6875 0.0312 .load() 0.7961 0.7969 0.7969 0.0000 .getpixel() 6.2659 6.2656 6.2656 0.0000 ## Ranking real .getdata() 0.7155 (100.0) ******************** .load() 0.7961 ( 89.9) ****************** .getpixel() 6.2659 ( 11.4) ** ## Matrix real [01] [02] [03] [01] .getdata() 0.7155 100.0 111.3 875.8 [02] .load() 0.7961 89.9 100.0 787.1 [03] .getpixel() 6.2659 11.4 12.7 100.0
- .getpixel() は、たしかに遅かった。他のアクセス方法より数倍遅い。
- .getdata() は、たしかに一番速かった。
- ただ、Image.load() をしてからアクセスする方法も、.getdata() 並みの速さでアクセスできるように見える。
.getdata() を使えばたしかに速くなるけれど、一次元配列に対してアクセスするような書き方になるので、可読性はほんのちょっと、若干かすかに、ビミョーに悪くなるような気もする。
対して、.load() を使ったアクセスなら、.getpixel() と同様に x, y を指定してアクセスできるし、しかも .getdata() を使った場合とそれほど処理速度も変わらないわけで…。
個人的には、.getdata() より、可読性と処理速度の両方をそこそこ得られる .load() を使ったほうがいいのではないかと思えてきた。
余談。手元で実験に使ってるスクリプトが、とにかく遅くて…。.load() を使って画像の各ドットにアクセスしていたのだけど、.getdata() を使ったらもっと速く処理できないかと少し期待しながらベンチマークを取ったわけで。ある意味、残念な結果になってしまった。今回、そこらへんを変えてみても結果は変わらないようだなと…。
[ ツッコむ ]
以上です。