Python 速度慢,試試這個(gè)方法提高 1000 倍


龜兔比賽(我6歲兒子 Charles Zhu 的繪畫(huà)作品)
1、人們一直詬病 Python 程序的速度很慢,它到底有多慢呢?
在每次的編程語(yǔ)言速度競(jìng)賽中,Python 的名次通常都比較墊底。有人解釋這是因?yàn)?Python 是一種解釋型語(yǔ)言(代碼無(wú)需編譯即可執(zhí)行),而所有的解釋型編程語(yǔ)言執(zhí)行速度都很慢。然而,我們知道 Java 也是一種解釋型語(yǔ)言,它的字節(jié)碼是由 JVM 解釋的。而在這個(gè)基準(zhǔn)測(cè)試速度比較頁(yè)面上的結(jié)果卻顯示:Java 要比 Python 的速度快得多。
下面是一個(gè)可以用來(lái)演示 Python 速度慢的示例。它使用傳統(tǒng)的 for 循環(huán)來(lái)產(chǎn)生一個(gè)數(shù)的倒數(shù):
import numpy as np
np.random.seed(0)
values = np.random.randint(1, 100, size=1000000)
def get_reciprocal(values):
output = np.empty(len(values))
for i in range(len(values)):
output[i] = 1.0/values[i]
%timeit get_reciprocal(values)結(jié)果顯示:
每個(gè)循環(huán)平均耗時(shí)3.37秒(標(biāo)準(zhǔn)偏差±582毫秒)(共計(jì)運(yùn)行了7次程序,每次一個(gè)循環(huán))計(jì)算 1,000,000 個(gè)倒數(shù)竟然需要 3.37 秒。使用 C 語(yǔ)言執(zhí)行同樣的運(yùn)算只需要不到一眨眼的工夫:9 毫秒;C# 需要 19 毫秒;Nodejs 需要 26 毫秒;Java 僅僅需要 5 毫秒!而 Python 竟然用了讓人懷疑人生的 3.37秒(它到底做了些什么)?。ㄗⅲ涸诒疚牡淖詈?,我附上了所有語(yǔ)言的測(cè)試代碼)。
2、Python 速度緩慢的根本原因
我們通常把 Python 稱(chēng)為一種動(dòng)態(tài)類(lèi)型編程語(yǔ)言。而 Python 程序中的一切變量都是以對(duì)象的形式存在,換句話(huà)說(shuō),每次 Python 代碼處理數(shù)據(jù)時(shí),都需要進(jìn)行對(duì)象拆箱操作,以確定對(duì)象的具體類(lèi)型。在 for 循環(huán)內(nèi)部,每次循環(huán)都需要拆箱對(duì)象,檢查類(lèi)型并計(jì)算倒數(shù)。那3秒鐘的時(shí)間都在類(lèi)型檢查中浪費(fèi)了。
C 語(yǔ)言和其他傳統(tǒng)的編程語(yǔ)言則不同,它們對(duì)數(shù)據(jù)的訪(fǎng)問(wèn)是直接的。但在 Python 中,大量的 CPU 時(shí)間都用在了類(lèi)型檢查上。
即使是一個(gè)簡(jiǎn)單的賦值操作也會(huì)花費(fèi)很長(zhǎng)的時(shí)間。如:
a = 1這個(gè)簡(jiǎn)單的賦值操作,它需要如下兩個(gè)步驟:
步驟 1:將 a->PyObject_HEAD->typecode 設(shè)置為 Integer 類(lèi)型.
步驟 2. 將值 1 賦值 a (a->val =1).
關(guān)于 Python 為什么速度慢的更多信息,Jake 寫(xiě)的這篇精彩文章值得一讀:Why Python is Slow: Looking Under the Hood
那么,有沒(méi)有一種方法可以繞過(guò)類(lèi)型檢查,從而提高 Python 程序的性能呢?
3、答案是:使用 NumPy 通用函數(shù)
與 Python 列表(list)不同,NumPy 數(shù)組是圍繞 C 數(shù)組構(gòu)建的對(duì)象。NumPy 數(shù)組訪(fǎng)問(wèn)項(xiàng)不需要任何步驟來(lái)檢查類(lèi)型。這給我們找到解決方案指明了方向:使用 NumPy 通用函數(shù)(亦即UFunc)。

簡(jiǎn)而言之,UFunc 是一種可以直接對(duì)整個(gè)數(shù)組進(jìn)行算術(shù)運(yùn)算的方法。下面我們將前面那個(gè)慢速的 Python 示例改寫(xiě)為 UFunc 版本,它就像下面這樣:
import numpy as np
np.random.seed(0)
values = np.random.randint(1, 100, size=1000000)
%timeit result = 1.0/values改寫(xiě)后的代碼不僅提高了速度,而且代碼變得更短。猜猜現(xiàn)在這個(gè)程序執(zhí)行要花多少時(shí)間?它比我上面提到的最快的語(yǔ)言快了2.7毫秒:
每個(gè)循環(huán)平均耗時(shí)2.71毫秒(標(biāo)準(zhǔn)偏差±50.8微秒)(共運(yùn)行了7次程序,每次循環(huán)100個(gè))返回代碼,關(guān)鍵是 1.0/values 這一行。這里的 values 不是一個(gè)數(shù)字,而是一個(gè) NumPy 數(shù)組。和除法運(yùn)算符一樣,Numpy 還有許多其他運(yùn)算符(如下圖示)。

點(diǎn)擊這里可以找到所有 Ufunc 運(yùn)算(操作)符。
4、總結(jié)
對(duì)于那些使用 Python 的人來(lái)說(shuō),使用 Python 處理數(shù)據(jù)和數(shù)字的可能性很大。這些數(shù)據(jù)可以存儲(chǔ)在 NumPy 或 Pandas DataFrame中,因?yàn)镈ataFrame 是基于 NumPy 實(shí)現(xiàn)的。所以 Ufunc 也可以使用。
UFunc 使我們能夠以超越幾個(gè)數(shù)量級(jí)的更快速度在 Python 中執(zhí)行重復(fù)操作。最慢的 Python 甚至可以跑得 C 語(yǔ)言更快。這一點(diǎn)太讓人激動(dòng)了。
5、附錄— C,C#,Java 和 NodeJS 的測(cè)試代碼
C 語(yǔ)言:
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
int main(){
struct timeval stop, start;
int length = 1000000;
int rand_array[length];
float output_array[length];
for(int i = 0; i<length; i++){
rand_array[i] = rand();
}
gettimeofday(&start, NULL);
for(int i = 0; i<length; i++){
output_array[i] = 1.0/(rand_array[i]*1.0);
}
gettimeofday(&stop, NULL);
printf("took %lu us\n", (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec);
printf("done\n");
return 0;
}C#(.net 5.0):
using System;
namespace speed_test{
class Program{
static void Main(string[] args){
int length = 1000000;
double[] rand_array =new double[length];
double[] output = new double[length];
var rand = new Random();
for(int i =0; i<length;i++){
rand_array[i] = rand.Next();
//Console.WriteLine(rand_array[i]);
}
long start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
for(int i =0;i<length;i++){
output[i] = 1.0/rand_array[i];
}
long end = DateTimeOffset.Now.ToUnixTimeMilliseconds();
Console.WriteLine(end - start);
}
}
}Java:
import java.util.Random;
public class speed_test {
public static void main(String[] args){
int length = 1000000;
long[] rand_array = new long[length];
double[] output = new double[length];
Random rand = new Random ();
for(int i =0; i<length; i++){
rand_array[i] = rand.nextLong();
}
long start = System.currentTimeMillis();
for(int i = 0;i<length; i++){
output[i] = 1.0/rand_array[i];
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}NodeJS:
let length = 1000000;
let rand_array = [];
let output = [];
for(var i=0;i<length;i++){
rand_array[i] = Math.floor(Math.random()*10000000);
}
let start = (new Date()).getMilliseconds();
for(var i=0;i<length;i++){
output[i] = 1.0/rand_array[i];
}
let end = (new Date()).getMilliseconds();
console.log(end - start);原文鏈接:
https://python.plainenglish.io/a-solution-to-boost-python-speed-1000x-times-c9e7d5be2f40
文章轉(zhuǎn)載:Python程序員
(版權(quán)歸原作者所有,侵刪)
![]()
