EverET.org

好记性不如烂笔头

对Python图像处理库EffectLab进行性能测试

| Comments

EffectLab也是一个基于PIL的Python的图像库,目的是为了提供更多的特效处理以及更快的测试。

目前EffectLab可以实现的特效可以围观之前的文章:http://everet.org/2012/07/effectlab.html

古人云:选择了脚本语言就要忍受其速度。

但是,有时脚本语言的速度已经慢到了无法形容的地步时,我们就开始考虑性能优化了。

寻找性能热点

Python有一对很好的性能测试工具:cProfile与pstats。

我们来选择一个波浪效果来做测试:

python
1
2
3
4
5
6
7
img = Image.new("RGB", (100, 100))
wave = GlobalWaveEffect(1, 0.5)
test = partial(wave, img)

cProfile.run("test()", "profile.data")
p = pstats.Stats("profile.data")
p.strip_dirs().sort_stats("time").print_stats()

我们可以看到其输出:

console
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Tue Aug 14 17:21:10 2012    profile.data

         417923 function calls (417922 primitive calls) in 0.434 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.150    0.150    0.434    0.434 Effect.py:92(filter)
    80000    0.068    0.000    0.068    0.000 {round}
    40000    0.051    0.000    0.063    0.000 Effect.py:304(transform)
    41889    0.034    0.000    0.034    0.000 {map}
    41890    0.029    0.000    0.042    0.000 Image.py:606(load)
    40000    0.028    0.000    0.091    0.000 Effect.py:317()
    33433    0.025    0.000    0.071    0.000 Image.py:946(getpixel)
    41890    0.013    0.000    0.013    0.000 {built-in method pixel_access}
    40000    0.012    0.000    0.012    0.000 {math.sin}
    33433    0.011    0.000    0.011    0.000 {built-in method getpixel}
     8457    0.008    0.000    0.019    0.000 Image.py:1260(putpixel)
     8457    0.004    0.000    0.004    0.000 {built-in method putpixel}

可以发现,运行时间最长的函数有第92行的filter,以及第304行的transform。我们可以查看第92行的函数filter,这个函数看上去非常的简短,主要做的是处理每一个像素以及有抗锯齿运算。

python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def filter(self, img):
    width, height = img.size
    new_img = Image.new(img.mode, img.size, Effect.empty_color)

    nband = len(img.getpixel((0, 0)))
    antialias = self.antialias
    left, top, right, bottom = self.box if self.box else (0, 0, width, height)

    for x in xrange(left, right):
        for y in xrange(top, bottom):
            found = 0
            psum = (0, ) * nband

            # anti-alias
            for ai in xrange(antialias):
                _x = x + ai / float(antialias)
                for aj in xrange(antialias):
                    _y = y + aj / float(antialias)

                    u, v = self.formula(_x, _y)

                    u = int(round(u))
                    v = int(round(v))
                    if not (0 <= u < width and 0 <= v < height):
                        continue
                    pt = img.getpixel((u, v))
                    psum = map(operator.add, psum, pt)
                    found += 1

            if found > 0:
                psum = map(operator.div, psum, (found, ) * len(psum))
                new_img.putpixel((x, y), tuple(psum))

    return new_img

以及transform函数:

python
1
2
3
4
5
def transform(self, x, y, width, height, delta_w, delta_h):
    radian = 2 * math.pi * (x + self.xoffset) / float(width) * delta_w
    offset = 0.5 * sin(radian) * height * delta_h

    return x, y + offset

解决性能热点

嗯,这个看上去热点都是纯计算的代码,貌似已经没什么优化的空间了,这时怎么办呢?

鉴于CPython可以非常容易的使用C/C++扩展模块,我们用C语言来实现里面这些纯计算的部分,看看性能有什么提升。

我们用C来实现Filter函数。重新运行cProfile看看,

console
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Tue Aug 14 17:38:56 2012    profile.data

         12 function calls in 0.002 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.002    0.002    0.002    0.002 {EffectLab.EffectLabCore.wave_warp}
        1    0.000    0.000    0.000    0.000 {built-in method copy}
        1    0.000    0.000    0.000    0.000 Image.py:460(_new)
        1    0.000    0.000    0.000    0.000 Image.py:740(copy)
        1    0.000    0.000    0.002    0.002 Effect.py:310(filter)
        1    0.000    0.000    0.002    0.002 :1()
        1    0.000    0.000    0.000    0.000 Image.py:606(load)
        1    0.000    0.000    0.002    0.002 Effect.py:37(__call__)
        1    0.000    0.000    0.000    0.000 Image.py:449(__init__)
        1    0.000    0.000    0.000    0.000 {built-in method pixel_access}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.000    0.000    0.000    0.000 {method 'copy' of 'dict' objects}

此时热点函数已经被C语言的模块给替换了。

我们用timeit模块统计一下运行时间,统计代码如下(其中test函数见上面,里面就是调用了波浪处理效果:

python
1
2
3
4
t = Timer('test()', 'from __main__ import test')
N = 3
TIMES = 30
print sum(t.repeat(N, TIMES)) / N / TIMES * 1000, 'ms'

结果

我们来看看运行3轮,每轮运行30次,平均一次的时间是多少。 Python版本的平均一次时间为:303.63 ms

C版本平均一次时间为:1.91 ms

可见运行速度是原来的159倍

本文链接: http://everet.org/profile-of-effectlab.html

您可能也喜欢

Comments