龟兔比赛
人们一直诟病 Python 程序的速度很慢,它到底有多慢呢?
下面是一个可以用来演示 Python 速度慢的示例。它使用传统的 for 循环来产生一个数的倒数:
import numpy as np
np.random.seed(0)
values= np.random.randint(1,100,size=1000000)
defget_reciprocal(values):
output= np.empty(len(values))
foriinrange(len(values)):
output[i] =1.0/values[i]
%timeit get_reciprocal(values)
结果显示:
每个循环平均耗时3.37秒(标准偏差±582毫秒)(共计运行了7次程序,每次一个循环)
计算 1,000,000 个倒数竟然需要 3.37 秒。使用 C 语言执行同样的运算只需要不到一眨眼的工夫:9 毫秒;C# 需要 19 毫秒;Nodejs 需要 26 毫秒;Java 仅仅需要 5 毫秒!而 Python 竟然用了让人怀疑人生的 3.37秒(它到底做了些什么)!(注:在本文的最后,我附上了所有语言的测试代码)。
Python 速度缓慢的根本原因
我们通常把 Python 称为一种动态类型编程语言。而 Python 程序中的一切变量都是以对象的形式存在,换句话说,每次 Python 代码处理数据时,都需要进行对象拆箱操作,以确定对象的具体类型。在 for 循环内部,每次循环都需要拆箱对象,检查类型并计算倒数。那3秒钟的时间都在类型检查中浪费了。
C 语言和其他传统的编程语言则不同,它们对数据的访问是直接的。但在 Python 中,大量的 CPU 时间都用在了类型检查上。
即使是一个简单的赋值操作也会花费很长的时间。如:
a=1
这个简单的赋值操作,它需要如下两个步骤:
步骤 1:将 a-PyObject_HEAD-typecode 设置为 Integer 类型.
步骤 2. 将值 1 赋值 a .
关于 Python 为什么速度慢的更多信息,Jake 写的这篇精彩文章值得一读:Why Python is Slow: Looking Under the Hood
那么,有没有一种方法可以绕过类型检查,从而提高 Python 程序的性能呢?
答案是:使用 NumPy 通用函数
与 Python 列表不同,NumPy 数组是围绕 C 数组构建的对象。NumPy 数组访问项不需要任何步骤来检查类型。这给我们找到解决方案指明了方向:使用 NumPy通用函数(亦即UFunc)。
简而言之,UFunc 是一种可以直接对整个数组进行算术运算的方法。下面我们将前面那个慢速的 Python 示例改写为 UFunc 版本,它就像下面这样:
import numpy as np
np.random.seed(0)
values= np.random.randint(1,100,size=1000000)
%timeitresult=1.0/values
改写后的代码不仅提高了速度,而且代码变得更短。猜猜现在这个程序执行要花多少时间?它比我上面提到的最快的语言快了2.7毫秒:
每个循环平均耗时2.71毫秒(标准偏差±50.8微秒)(共运行了7次程序,每次循环100个)
返回代码,关键是 1.0/values 这一行。这里的 values 不是一个数字,而是一个 NumPy 数组。和除法运算符一样,Numpy 还有许多其他运算符(如下图示)。
总结
对于那些使用 Python 的人来说,使用 Python 处理数据和数字的可能性很大。这些数据可以存储在 NumPy 或 Pandas DataFrame中,因为DataFrame 是基于 NumPy 实现的。所以 Ufunc 也可以使用。
UFunc 使我们能够以超越几个数量级的更快速度在 Python 中执行重复操作。最慢的 Python 甚至可以跑得 C 语言更快。这一点太让人激动了。
附录— C,C#,Java 和 NodeJS 的测试代码
C 语言:
usingSystem;
namespacespeed_test{
classProgram{
staticvoidMain(string[] args){
intlength =1000000;
double[] rand_array =newdouble[length];
double[] output =newdouble[length];
varrand =newRandom();
for(inti =0; ilength;i++){
rand_array = rand.Next;
//Console.WriteLine(rand_array[i]);
}
longstart = DateTimeOffset.Now.ToUnixTimeMilliseconds();
for(inti =0;ilength;i++){
output[i] =1.0/rand_array[i];
}
longend = DateTimeOffset.Now.ToUnixTimeMilliseconds();
Console.WriteLine;
}
}
}
Java:
importjava.util.Random;
publicclassspeed_test{
publicstaticvoidmain(String[] args){
intlength =1000000;
long[] rand_array =newlong[length];
double[] output =newdouble[length];
Random rand =newRandom ();
for(inti =0; ilength; i++){
rand_array = rand.nextLong;
}
longstart = System.currentTimeMillis();
for(inti =0;ilength; i++){
output[i] =1.0/rand_array[i];
}
longend = System.currentTimeMillis();
System.out.println;
}
}
NodeJS:
letlength =1000000;
letrand_array = [];
letoutput = [];
for(vari=0;ilength;i++){
rand_array[i] =Math.floor(Math.random()*10000000);
}
letstart = (newDate()).getMilliseconds();
for(vari=0;ilength;i++){
output[i] =1.0/rand_array[i];
}
letend = (newDate()).getMilliseconds();
console.log;
4月20日晚八点,欢迎来到CSDN悦读时间直播间,与四位大咖一起探索UNIX传奇往事的启示,围观《UNIX传奇》新书发布会!