我开始编写一个执行非线性光束计算的程序。我之所以选择Python是因为它的Matlab就像代码一样,而且我目前正在进行速度测试(确定python是否是进行快速数值计算的正确语言)并尝试熟悉python3。 我尝试了一种算法,计算从t = 1到n的1 / t ^ 2之和(来自书Julia High Perfromance),以比较python3与julia的速度。 现在我有一些问题:
1)在我的计算中,朱莉娅没有预期的那么快? Julia ist JIT编译。它应该非常快。为什么python更快?
2)为什么python中的循环如此缓慢?
3)为什么python sum方法比numpy.sum方法慢
4)为什么python geting的sum函数与numpy.sum函数的解决方案略有不同?
我希望你能帮助我。
代码:
# Benchmark Python vs Julia
# (from Julia High Performance page 7 from Avik Sengupta)
import scipy as sp
import time
# Sum of 1/t^2 from t = 1 to n by using loops:
# --------------------------------------------
def pisum(w,n):
u_sum = 0
for vi in range(w):
u_sum = 0
for vj in range(1,n+1,1):
u_sum += 1.0/(vj*vj)
return u_sum
# Sum of 1/t^2 from t = 1 to n by using scipy functions:
# ------------------------------------------------------
def pisum1(w,n):
for vi in range(w):
vj = sp.arange(1,n+1,1)
vj = sp.multiply(vj,vj)
u_sum_sp = (sp.divide(sp.ones(n),vj)).sum()
return u_sum_sp
# Sum of 1/t^2 from t = 1 to n by using scipy functions & calculating
# the sum via pure python:
# -------------------------------------------------------------------
def pisum2(w,n):
for vi in range(w):
vj = sp.arange(1,n+1,1)
vj = sp.multiply(vj,vj)
u_sum_py = sum(sp.divide(sp.ones(n),vj))
return u_sum_py
# Benchmarking the methods :
# ==========================
w = 500
n = 10000
# 1) Loops:
# ---------
ta = time.clock()
u_sum_loops = pisum(w,n)
eltime_loops = time.clock() - ta
# 2) scipy:
# ---------
ta = time.clock()
u_sum_sp = pisum1(w,n)
eltime_sp= time.clock() - ta
# 3) scipy & sum via python:
# --------------------------
ta = time.clock()
u_sum_py = pisum2(w,n)
eltime_py= time.clock() - ta
# Julia with loops:
# -----------------
eltime_ju_loops = 0.150857295
u_sum_ju = 1.6448340718480652
row_format = '{:<35} {:<5} {:<5} {:<} {:<}'
print("Overview calculation time:")
print("-"*50)
print(row_format.format("elapsed time using loops:","%.5f" % eltime_loops,"sec. (", "%.2f"% (eltime_loops/eltime_ju_loops),"*time Julia)"))
print(row_format.format("elapsed time using scipy:","%.5f" % eltime_sp,"sec. (", "%.2f"% (eltime_sp/eltime_ju_loops),"*time Julia)"))
print(row_format.format("elapsed time using python:","%.5f" % eltime_py,"sec. (","%.2f"% (eltime_py/eltime_ju_loops),"*time Julia)"))
print(row_format.format("elapsed time using Julia and loops:","%.5f" % eltime_ju_loops,"sec.","","\n"))
line1 = "sum loops:",u_sum_loops
line2 = "sum scipy:",u_sum_sp
line3 = "sum scipy and sum via python:",u_sum_py
line4 = "sum_julia:",u_sum_ju
row_format = '{:<29} {:<18}'
print("Overview Sum:")
print("-"*50)
print(row_format.format(*line1))
print(row_format.format(*line2))
print(row_format.format(*line3))
print(row_format.format(*line4))
# Julia Code:
# ===========
# function pisum(w,n)
# u_sum = 0;
# for vi = 1:w
# u_sum = 0;
# for vj = 1:n
# u_sum += 1.0/(vj*vj);
# end
# end
# u_sum
# end
#
# tic()
# u_sum = pisum(500,10000)
# eltime = toc()
答案 0 :(得分:19)
1)在我的计算中,朱莉娅没有预期的那么快? Julia ist JIT编译。它应该非常快。为什么python更快?
您的功能不是类型稳定的。由于JIT编译器,Julia并不快:由于它的类型系统,它很快。它的类型系统被设计为在类型稳定函数(输出类型是输入类型的函数的函数)上使用多个调度,以在代码的每个阶段完全推导出类型,从而允许其函数基本上静态编译。通过这样做,通过将类型稳定函数链接在一起构建的函数本身可以通过类型稳定,并且与您想要编写的C / Fortran代码相比编译为1x(因为这是足够的信息)对于所有相关的编译器优化)。这将更详细地解释at this blog post。
所以类型稳定的代码是:
function pisum(w,n)
u_sum = 0.0;
for vi = 1:w
u_sum = 0.0;
for vj = 1:n
u_sum += 1.0/(vj*vj);
end
end
u_sum
end
@time u_sum = pisum(500,10000)
对我而言,这个时间大约为0.02秒,比SciPy示例快2倍,比计算机上的其他实现快50倍-100倍。
请注意,您可以通过调用@code_warntype pisum(500,10000)
来检查类型稳定性,这将是大写和红色突出显示返回类型不是类型稳定的行。这将向您显示您的版本u_sum
以Int
开头,然后变成Float64
。想想你是否编写了一个静态编译的函数(C / Fortran):你无法编译,因为u_sum
的类型是未知的。
2)为什么python中的循环如此缓慢?
因为Python中的变量是动态的。每当它命中一个变量时,解释器就需要找出变量是什么,为它找到正确的编译函数,并处理返回的值(可能进行一些转换以隐藏可能发生的真实底层变化)。它本质上是一种非类型稳定的Julia函数。
3)为什么python sum方法比numpy.sum方法慢
编写NumPy假设该数组是一个浮点数数组。 Python数组(列表)通常是任何东西。 Julia表示法是Vector{Float64}
vs Vector{Any}
。在第一个中,您可以准确地知道类型是什么,消除类型检查,转换等。您可以知道内存中每种类型的大小是相同的,并且由于它们都是相同的,您可以将它们内联到向量,而不是让内存成为一堆指向真实对象的指针(并且指针间接禁用了许多优化)。
4)为什么python geting的sum函数与numpy.sum函数的解决方案略有不同?
浮点很奇怪,不是关联的。 SciPy中可能存在优化,这会改变某些计算的顺序,可能是某种循环展开
通过优化代码变得快速。如果编译器有更多信息,代码可以更好地优化,因为它可以做出更好的假设并删除许多不必要的检查和间接。完全动态的Python可以给解释器/运行时几乎没有信息,迫使它通过最少优化的路径。你可以通过调用用C编写的特定参数类型函数来改善这一点,因为编译器可以优化C代码(这就是SciPy所做的)。
Julia旨在让您为编译器提供静态编译语言的完整信息,但是使用的是大多数动态语言。这是由于类型系统和多次调度。因此,在这些情况下,它能够匹配C / Fortran的编译代码,获得全速,而无需在运行时调用FFI。如果您编写的代码中编译器不能拥有任何信息,例如使输出类型随机,那么编译器就无法进行优化,代码本质上变得动态且速度与Python一样慢。这很好,因为在这些情况下C只会出现段错误......