2022/05/02(月) [n年前の日記]
#1 [python][gimp] Pythonのリスト内包表記を試してみた
昨日、Pythonスクリプトのベンチマークを取りながら、ARGBのデータをRGBAに変換する処理を速くできないか試していたのだけど。
_Pythonのstructを使った時の処理時間を調べてた
高速化に関するページを眺めていたら、リスト内包表記とやらにするだけでも高速化できるという話を見かけたので試してみた。
環境は、Windows10 x64 21H2 + Python 2.7.18 32bit。CPU は AMD Ryzen 7 1700 (3GHz, 8コア16スレッド)。
ベンチマークをとってみた。スクリプトは以下。
_03_argb_rgba_conv.py
py -2 03_argb_rgba_conv.py で実行。結果は以下。
「rgb shift F」「rgb shift G」が、リスト内包表記を使った版。たしかに、2.0750秒が、1.7300秒になった。リスト内包表記にするだけでも高速化はできるらしい。
また、論理和(OR)演算子(|)を使っていたところを、+演算子に変えてみたところ、それだけでもほんのちょっとだけ処理が速くなった。ビット演算は微妙に遅いということだろうか…?
つまるところ、以下のように変更したことで、ある程度高速化できた、ということになるのかなと。
ただ、これは GIMP の Python-Fu に限定した話だけど、少々問題もあって…。リスト内包表記にしたことで、GIMPのプログレスバーを更新して処理の進行具合をユーザに伝えることができなくなってしまった。今まではforループで処理してたから、forループ内でプログレスバーを更新する処理を呼ぶことができていたのだけど…。でもまあ、5秒近くかかっていた処理が1.7秒になるのだから、プログレスバーを更新しなくても許されるかなと…。
さておき。もっと高速化できないものだろうか…。
スクリプトを動かしている環境がビッグエンディアンなのかリトルエンディアンなのか分からないので、struct.unpack("=L", xxx) を使って処理をしているけれど、どちらのエンディアンなのか事前に判別すれば、struct を使わなくても済むのではないか…? データの並びが BGRABGRA...、あるいは ARGBARGB... になっているものと決め打ちできれば、もっとシンプルに書けて高速化できないか。
_Pythonのstructを使った時の処理時間を調べてた
高速化に関するページを眺めていたら、リスト内包表記とやらにするだけでも高速化できるという話を見かけたので試してみた。
環境は、Windows10 x64 21H2 + Python 2.7.18 32bit。CPU は AMD Ryzen 7 1700 (3GHz, 8コア16スレッド)。
ベンチマークをとってみた。スクリプトは以下。
_03_argb_rgba_conv.py
from benchmarker import Benchmarker import struct import sys LOOP = 5 def create_src_data(): print("start.") rgba = [] w, h = 2048, 2048 r, g, b, a = 0, 32, 64, 128 for y in range(h): for x in range(w): rgba.append(struct.pack('4B', b, g, r, a)) r += 1 g += 1 b += 1 a += 1 if r > 255: r = 0 if g > 255: g = 0 if b > 255: b = 0 if a > 255: a = 0 src = ''.join(rgba) print("create source data.") return src def get_rgba_str_slow(src): buf = "" size = len(src) for i in range(size / 4): i0 = i * 4 i1 = i0 + 4 bgra = struct.unpack('=L', src[i0: i1])[0] a = (bgra >> 24) & 0x0ff r = (bgra >> 16) & 0x0ff g = (bgra >> 8) & 0x0ff b = bgra & 0x0ff rgba = struct.pack('4B', r, g, b, a) buf += rgba return buf def get_rgba_str(src): unpack = struct.Struct('=L').unpack_from pack = struct.Struct('>L').pack lmax = len(src) / 4 rgba = [None] * lmax for i in range(lmax): d = unpack(src, i * 4)[0] rgba[i] = pack(((d & 0x00ffffff) << 8) | ((d >> 24) & 0x0ff)) return ''.join(rgba) def get_rgba_str_b(src): unpack = struct.Struct('=L').unpack_from pack = struct.Struct('>L').pack lmax = len(src) / 4 rgba = [] for i in range(lmax): d = unpack(src, i * 4)[0] rgba.append(pack(((d & 0x00ffffff) << 8) | ((d >> 24) & 0x0ff))) return ''.join(rgba) def get_rgba_str_c(src): lmax = len(src) / 4 argb = list(struct.unpack("=%dL" % lmax, src)) spack = struct.Struct('>L').pack rgba = [None] * lmax for i, d in enumerate(argb): rgba[i] = spack(((d & 0x0ffffff) << 8) | ((d >> 24) & 0x0ff)) return ''.join(rgba) def get_rgba_str_d(src): lmax = len(src) / 4 argb = list(struct.unpack("=%dL" % lmax, src)) rgba = [None] * lmax for i, d in enumerate(argb): rgba[i] = ((d & 0x0ffffff) << 8) | ((d >> 24) & 0x0ff) return struct.pack(">%dL" % lmax, *rgba) def get_rgba_str_e(src): lmax = len(src) / 4 argb = list(struct.unpack("=%dL" % lmax, src)) rgba = [] for d in argb: rgba.append(((d & 0x0ffffff) << 8) | ((d >> 24) & 0x0ff)) return struct.pack(">%dL" % lmax, *rgba) def get_rgba_str_f(src): lmax = len(src) / 4 argb = list(struct.unpack("=%dL" % lmax, src)) rgba = [(((d & 0x0ffffff) << 8) | ((d >> 24) & 0x0ff)) for d in argb] return struct.pack(">%dL" % lmax, *rgba) def get_rgba_str_g(src): lmax = len(src) / 4 argb = list(struct.unpack("=%dL" % lmax, src)) rgba = [(((d & 0x0ffffff) << 8) + ((d >> 24) & 0x0ff)) for d in argb] return struct.pack(">%dL" % lmax, *rgba) def check_result(src): dst = [] dst.append(get_rgba_str(src)) dst.append(get_rgba_str_slow(src)) dst.append(get_rgba_str_b(src)) dst.append(get_rgba_str_c(src)) dst.append(get_rgba_str_d(src)) dst.append(get_rgba_str_e(src)) dst.append(get_rgba_str_f(src)) dst.append(get_rgba_str_g(src)) for i in range(len(dst)): if i == 0: continue if dst[0] == dst[i]: print("Success [%d]" % i) else: print("Failure [%d]" % i) src = create_src_data() # check_result(src) # sys.exit() with Benchmarker(LOOP, width=20) as bench: @bench("argb split") def check_use_split(bm): get_rgba_str_slow(src) @bench("rgb shift A") def check_use_rgb_shift(bm): get_rgba_str(src) @bench("rgb shift B") def check_use_rgb_shift_b(bm): get_rgba_str_b(src) @bench("rgb shift C") def check_use_rgb_shift_c(bm): get_rgba_str_c(src) @bench("rgb shift D") def check_use_rgb_shift_d(bm): get_rgba_str_d(src) @bench("rgb shift E") def check_use_rgb_shift_e(bm): get_rgba_str_e(src) @bench("rgb shift F") def check_use_rgb_shift_f(bm): get_rgba_str_f(src) @bench("rgb shift G") def check_use_rgb_shift_g(bm): get_rgba_str_g(src)
py -2 03_argb_rgba_conv.py で実行。結果は以下。
D:\home\prg\python\_test_sample\benchmarker> py -2 03_argb_rgba_conv.py start. create source data. ## benchmarker: release 4.0.1 (for python) ## python version: 2.7.18 ## python compiler: MSC v.1500 32 bit (Intel) ## python platform: Windows-10-10.0.19041 ## python executable: C:\Python\Python27\python.exe ## cpu model: AMD64 Family 23 Model 1 Stepping 1, AuthenticAMD ## parameters: loop=5, cycle=1, extra=0 ## real (total = user + sys) argb split 16.1320 16.1094 11.1250 4.9844 rgb shift A 4.7300 4.7344 4.7031 0.0312 rgb shift B 4.8720 4.8750 4.7656 0.1094 rgb shift C 2.5780 2.5781 2.5000 0.0781 rgb shift D 2.0750 2.0625 2.0156 0.0469 rgb shift E 2.1690 2.1719 2.0000 0.1719 rgb shift F 1.7300 1.7344 1.5781 0.1562 rgb shift G 1.6780 1.6875 1.5781 0.1094 ## Ranking real rgb shift G 1.6780 (100.0) ******************** rgb shift F 1.7300 ( 97.0) ******************* rgb shift D 2.0750 ( 80.9) **************** rgb shift E 2.1690 ( 77.4) *************** rgb shift C 2.5780 ( 65.1) ************* rgb shift A 4.7300 ( 35.5) ******* rgb shift B 4.8720 ( 34.4) ******* argb split 16.1320 ( 10.4) ** ## Matrix real [01] [02] [03] [04] [05] [06] [07] [08] [01] rgb shift G 1.6780 100.0 103.1 123.7 129.3 153.6 281.9 290.3 961.4 [02] rgb shift F 1.7300 97.0 100.0 119.9 125.4 149.0 273.4 281.6 932.5 [03] rgb shift D 2.0750 80.9 83.4 100.0 104.5 124.2 228.0 234.8 777.4 [04] rgb shift E 2.1690 77.4 79.8 95.7 100.0 118.9 218.1 224.6 743.8 [05] rgb shift C 2.5780 65.1 67.1 80.5 84.1 100.0 183.5 189.0 625.8 [06] rgb shift A 4.7300 35.5 36.6 43.9 45.9 54.5 100.0 103.0 341.1 [07] rgb shift B 4.8720 34.4 35.5 42.6 44.5 52.9 97.1 100.0 331.1 [08] argb split 16.1320 10.4 10.7 12.9 13.4 16.0 29.3 30.2 100.0
「rgb shift F」「rgb shift G」が、リスト内包表記を使った版。たしかに、2.0750秒が、1.7300秒になった。リスト内包表記にするだけでも高速化はできるらしい。
また、論理和(OR)演算子(|)を使っていたところを、+演算子に変えてみたところ、それだけでもほんのちょっとだけ処理が速くなった。ビット演算は微妙に遅いということだろうか…?
つまるところ、以下のように変更したことで、ある程度高速化できた、ということになるのかなと。
def get_rgba_str(src): unpack = struct.Struct('=L').unpack_from pack = struct.Struct('>L').pack lmax = len(src) / 4 rgba = [None] * lmax for i in range(lmax): d = unpack(src, i * 4)[0] rgba[i] = pack(((d & 0x00ffffff) << 8) | ((d >> 24) & 0x0ff)) return ''.join(rgba)↓
def get_rgba_str_g(src): lmax = len(src) / 4 argb = list(struct.unpack("=%dL" % lmax, src)) rgba = [(((d & 0x0ffffff) << 8) + ((d >> 24) & 0x0ff)) for d in argb] return struct.pack(">%dL" % lmax, *rgba)
- forループの中で struct.unpack()、struct.pack() を呼んで処理するより、struct.unpack()、struct.pack() の書式文字列に繰り返し回数を指定して、一発でリストに変換したほうが速い。
- ''.join() で結合するより、struct.pack() で結合したほうが速い。
- forループで処理するより、リスト内包表記を使ったほうが速い。
- OR演算子(|)より、+演算子のほうが速い。
ただ、これは GIMP の Python-Fu に限定した話だけど、少々問題もあって…。リスト内包表記にしたことで、GIMPのプログレスバーを更新して処理の進行具合をユーザに伝えることができなくなってしまった。今まではforループで処理してたから、forループ内でプログレスバーを更新する処理を呼ぶことができていたのだけど…。でもまあ、5秒近くかかっていた処理が1.7秒になるのだから、プログレスバーを更新しなくても許されるかなと…。
さておき。もっと高速化できないものだろうか…。
スクリプトを動かしている環境がビッグエンディアンなのかリトルエンディアンなのか分からないので、struct.unpack("=L", xxx) を使って処理をしているけれど、どちらのエンディアンなのか事前に判別すれば、struct を使わなくても済むのではないか…? データの並びが BGRABGRA...、あるいは ARGBARGB... になっているものと決め打ちできれば、もっとシンプルに書けて高速化できないか。
[ ツッコむ ]
以上です。