Python优化问题?

2 投票
4 回答
562 浏览
提问于 2025-04-15 22:44

最近我有个作业(别担心,我已经用C++完成了),但我很好奇如果用Python怎么做。这个问题是关于两个发光源的,它们会发出光。我就不详细讲了。

这是我写的代码(我在后面优化了一些):

import math, array
import numpy as np
from PIL import Image

size = (800,800)
width, height = size

s1x = width * 1./8
s1y = height * 1./8
s2x = width * 7./8
s2y = height * 7./8

r,g,b = (255,255,255)
arr = np.zeros((width,height,3))
hy = math.hypot
print 'computing distances (%s by %s)'%size,
for i in xrange(width):
    if i%(width/10)==0:
        print i,    
    if i%20==0:
        print '.',
    for j in xrange(height):
        d1 = hy(i-s1x,j-s1y)
        d2 = hy(i-s2x,j-s2y)
        arr[i][j] = abs(d1-d2)
print ''

arr2 = np.zeros((width,height,3),dtype="uint8")        
for ld in [200,116,100,84,68,52,36,20,8,4,2]:
    print 'now computing image for ld = '+str(ld)
    arr2 *= 0
    arr2 += abs(arr%ld-ld/2)*(r,g,b)/(ld/2)
    print 'saving image...'
    ar2img = Image.fromarray(arr2)
    ar2img.save('ld'+str(ld).rjust(4,'0')+'.png')
    print 'saved as ld'+str(ld).rjust(4,'0')+'.png'

我已经优化了大部分代码,但在有两个for循环的部分,性能差距还是很大,我想不出用常见的数组操作来解决这个问题……欢迎大家给我建议 :D

编辑:为了回应Vlad的建议,我来详细说明一下问题: 有两个光源,每个光源发出的光都是一个正弦波: E1 = E0*sin(omega1*time+phi01) E2 = E0*sin(omega2*time+phi02) 为了简单起见,我们假设omega1=omega2=omega=2*PI/T,phi01=phi02=phi0。 假设x1是从第一个光源到平面上某点的距离,那么在这个点的光强度是 Ep1 = E0*sin(omega*time - 2*PI*x1/lambda + phi0) 其中 lambda = 光速 * T(振荡周期)。 考虑到两个光源在平面上,公式变成 Ep = 2*E0*cos(PI*(x2-x1)/lambda)sin(omegatime - PI*(x2-x1)/lambda + phi0) 从中我们可以看出,当 (x2-x1)/lambda = (2*k) * PI/2 时光强度达到最大,而当 (x2-x1)/lambda = (2*k+1) * PI/2 时光强度达到最小,中间的值则是变化的,其中k是一个整数。

在某个时刻,给定光源的坐标,以及已知的lambda和E0,我们需要写个程序来绘制光的样子。 在我看来,我已经尽可能地优化了这个问题……

4 个回答

2

列表推导式比循环要快得多。例如,你可以用下面的方式来写:

for j in xrange(height):
        d1 = hy(i-s1x,j-s1y)
        d2 = hy(i-s2x,j-s2y)
        arr[i][j] = abs(d1-d2)

而不是这样:

arr[i] = [abs(hy(i-s1x,j-s1y) - hy(i-s2x,j-s2y)) for j in xrange(height)]

另一方面,如果你真的想要“优化”代码,那你可能需要用C语言重新实现这个算法,然后使用SWIG之类的工具从Python中调用它。

3

如果你使用数组操作而不是循环,速度会快很多很多。对我来说,生成图像现在是最耗时的部分。与其用两个 i,j 循环,不如用我下面这个:

I,J = np.mgrid[0:width,0:height]
D1 = np.hypot(I - s1x, J - s1y)
D2 = np.hypot(I - s2x, J - s2y)

arr = np.abs(D1-D2)
# triplicate into 3 layers
arr = np.array((arr, arr, arr)).transpose(1,2,0)
# .. continue program

你未来要记住的基本点是:这不是在优化;在numpy中使用数组形式其实就是在按照它应该被使用的方式使用它。随着经验的积累,你未来的项目应该不再绕弯子去用python循环,数组形式应该是最自然的选择。

我们在这里做的事情其实很简单。我们用 numpy.hypot 替代了 math.hypot。像所有这样的numpy函数一样,它接受ndarray作为参数,并且正好做我们想要的事情。

5

干涉图样真有意思,对吧?

首先,这个程序在我笔记本上运行其实只需要十二秒半,所以影响不大。

不过,我们来看看能不能通过numpy数组操作来优化一下第一部分,好吗?基本上你想要的是:

arr[i][j] = abs(hypot(i-s1x,j-s1y) - hypot(i-s2x,j-s2y))

对于所有的 ij

因为numpy有一个可以对数组使用的 hypot 函数,我们就用这个。我们的第一个挑战是创建一个合适大小的数组,让每个元素都等于 i,再创建一个每个元素都等于 j 的数组。不过这并不难;其实下面的回答提到了一个我之前不知道的很棒的 numpy.mgrid,它正好可以做到这一点:

array_i,array_j = np.mgrid[0:width,0:height]

还有一个小问题,就是要把你的 (width, height) 大小的数组变成 (width, height, 3),这样才能和你的图像生成语句兼容,不过这也很简单:

arr = (arr * np.ones((3,1,1))).transpose(1,2,0)

然后我们把这个放进你的程序里,让数组操作来完成任务:

import math, array
import numpy as np
from PIL import Image

size = (800,800)
width, height = size

s1x = width * 1./8
s1y = height * 1./8
s2x = width * 7./8
s2y = height * 7./8

r,g,b = (255,255,255)

array_i,array_j = np.mgrid[0:width,0:height]

arr = np.abs(np.hypot(array_i-s1x, array_j-s1y) -
             np.hypot(array_i-s2x, array_j-s2y))

arr = (arr * np.ones((3,1,1))).transpose(1,2,0)

arr2 = np.zeros((width,height,3),dtype="uint8")
for ld in [200,116,100,84,68,52,36,20,8,4,2]:
    print 'now computing image for ld = '+str(ld)
    # Rest as before

新的运行时间是……8.2秒。所以你大约节省了四秒时间。不过,现在几乎所有的时间都花在图像生成上了,所以也许你可以通过只生成需要的图像来进一步优化。

撰写回答