当从文件中读取和评估方程列表时,加速Python eval

问题描述:

我已经放在一起了一个简单的Python脚本,它从单独的行上的文本文件中读取大量代数表达式,评估每行上的数学并将其放入一个numpy数组。然后找到这个矩阵的特征值。然后参数A,B,C将被改变并且程序再次运行,因此使用函数来实现这一点。当从文件中读取和评估方程列表时,加速Python eval

其中一些文本文件将有数百万行方程式,因此在对代码进行分析后,我发现eval命令约占执行时间的99%。我意识到使用eval的危险,但是这些代码只能由我自己使用。代码的所有其他部分都很快,除了拨打eval

以下是mat_size设置为500的代码,代表500 * 500的数组,表示从该文件读入250,000行方程。我无法提供大小约为0.5GB的文件,但提供了下面的示例,它只使用基本的数学运算。

import numpy as np 
from numpy import * 
from scipy.linalg import eigvalsh 

mat_size = 500 

# Read the file line by line 
with open("test_file.txt", 'r') as f: 
    lines = f.readlines() 

# Function to evaluate the maths and build the numpy array 
def my_func(A,B,C): 
    lst = [] 
    for i in lines: 
     # Strip the \n 
     new = eval(i.rstrip()) 
     lst.append(new) 
    # Build the numpy array 
    AA = np.array(lst,dtype=np.float64) 
    # Resize it to mat_size 
    matt = np.resize(AA,(mat_size,mat_size)) 
    return matt 

# Function to find eigenvalues of matrix 
def optimise(x): 
    A,B,C = x 
    test = my_func(A,B,C) 
    ev=-1*eigvalsh(test) 
    return ev[-(1)] 

# Define what A,B,C are, this can be changed each time the program is run 
x0 = [7.65,5.38,4.00] 

# Print result 
print(optimise(x0)) 

一个例子输入文本文件的几行:(mat_size可改为2运行此文件)

.5/A**3*B**5+C 
35.5/A**3*B**5+3*C 
.8/C**3*A**5+C**9 
.5/A*3+B**5-C/45 

我知道eval通常是不好的做法和缓慢的,所以我看了用于其他手段来实现加速。我尝试了方法概述here但这些似乎没有工作。我也试着用sympy来解决这个问题,但那导致了一个巨大的放缓。解决这个问题的更好方法是什么?

编辑

从建议使用numexpr相反,我所遇到的地方研磨到比标准eval停止的问题。对于某些情况,矩阵元素包含相当多的代数表达式。下面是一个矩阵元素的例子,即文件中的其中一个方程(它包含了几个未在上面的代码中定义的术语,但可以很容易地在代码的顶部定义):

-71*A**3/(A+B)**7-61*B**3/(A+B)**7-3/2/B**2/C**2*A**6/(A+B)**7-7/4/B**3/m3*A**6/(A+B)**7-49/4/B**2/C*A**6/(A+B)**7+363/C*A**3/(A+B)**7*z3+451*B**3/C/(A+B)**7*z3-3/2*B**5/C/A**2/(A+B)**7-3/4*B**7/C/A**3/(A+B)**7-1/B/C**3*A**6/(A+B)**7-3/2/B**2/C*A**5/(A+B)**7-107/2/C/m3*A**4/(A+B)**7-21/2/B/C*A**4/(A+B)**7-25/2*B/C*A**2/(A+B)**7-153/2*B**2/C*A/(A+B)**7-5/2*B**4/C/m3/(A+B)**7-B**6/C**3/A/(A+B)**7-21/2*B**4/C/A/(A+B)**7-7/4/B**3/C*A**7/(A+B)**7+86/C**2*A**4/(A+B)**7*z3+90*B**4/C**2/(A+B)**7*z3-1/4*B**6/m3/A**3/(A+B)**7-149/4/B/C*A**5/(A+B)**7-65*B**2/C**3*A**4/(A+B)**7-241/2*B/C**2*A**4/(A+B)**7-38*B**3/C**3*A**3/(A+B)**7+19*B**2/C**2*A**3/(A+B)**7-181*B/C*A**3/(A+B)**7-47*B**4/C**3*A**2/(A+B)**7+19*B**3/C**2*A**2/(A+B)**7+362*B**2/C*A**2/(A+B)**7-43*B**5/C**3*A/(A+B)**7-241/2*B**4/C**2*A/(A+B)**7-272*B**3/C*A/(A+B)**7-25/4*B**6/C**2/A/(A+B)**7-77/4*B**5/C/A/(A+B)**7-3/4*B**7/C**2/A**2/(A+B)**7-23/4*B**6/C/A**2/(A+B)**7-11/B/C**2*A**5/(A+B)**7-13/B**2/m3*A**5/(A+B)**7-25*B/C**3*A**4/(A+B)**7-169/4/B/m3*A**4/(A+B)**7-27*B**2/C**3*A**3/(A+B)**7-47*B/C**2*A**3/(A+B)**7-27*B**3/C**3*A**2/(A+B)**7-38*B**2/C**2*A**2/(A+B)**7-131/4*B/m3*A**2/(A+B)**7-25*B**4/C**3*A/(A+B)**7-65*B**3/C**2*A/(A+B)**7-303/4*B**2/m3*A/(A+B)**7-5*B**5/C**2/A/(A+B)**7-49/4*B**4/m3/A/(A+B)**7-1/2*B**6/C**2/A**2/(A+B)**7-5/2*B**5/m3/A**2/(A+B)**7-1/2/B/C**3*A**7/(A+B)**7-3/4/B**2/C**2*A**7/(A+B)**7-25/4/B/C**2*A**6/(A+B)**7-45*B/C**3*A**5/(A+B)**7-3/2*B**7/C**3/A/(A+B)**7-123/2/C*A**4/(A+B)**7-37/B*A**4/(A+B)**7-53/2*B*A**2/(A+B)**7-75/2*B**2*A/(A+B)**7-11*B**6/C**3/(A+B)**7-39/2*B**5/C**2/(A+B)**7-53/2*B**4/C/(A+B)**7-7*B**4/A/(A+B)**7-7/4*B**5/A**2/(A+B)**7-1/4*B**6/A**3/(A+B)**7-11/C**3*A**5/(A+B)**7-43/C**2*A**4/(A+B)**7-363/4/m3*A**3/(A+B)**7-11*B**5/C**3/(A+B)**7-45*B**4/C**2/(A+B)**7-451/4*B**3/m3/(A+B)**7-5/C**3*A**6/(A+B)**7-39/2/C**2*A**5/(A+B)**7-49/4/B**2*A**5/(A+B)**7-7/4/B**3*A**6/(A+B)**7-79/2/C*A**3/(A+B)**7-207/2*B**3/C/(A+B)**7+22/B/C**2*A**5/(A+B)**7*z3+94*B/C**2*A**3/(A+B)**7*z3+76*B**2/C**2*A**2/(A+B)**7*z3+130*B**3/C**2*A/(A+B)**7*z3+10*B**5/C**2/A/(A+B)**7*z3+B**6/C**2/A**2/(A+B)**7*z3+3/B**2/C**2*A**6/(A+B)**7*z3+7/B**3/C*A**6/(A+B)**7*z3+52/B**2/C*A**5/(A+B)**7*z3+169/B/C*A**4/(A+B)**7*z3+131*B/C*A**2/(A+B)**7*z3+303*B**2/C*A/(A+B)**7*z3+49*B**4/C/A/(A+B)**7*z3+10*B**5/C/A**2/(A+B)**7*z3+B**6/C/A**3/(A+B)**7*z3-3/4*B**7/C/m3/A**3/(A+B)**7-7/4/B**3/C/m3*A**7/(A+B)**7-49/4/B**2/C/m3*A**6/(A+B)**7-149/4/B/C/m3*A**5/(A+B)**7-293*B/C/m3*A**3/(A+B)**7+778*B**2/C/m3*A**2/(A+B)**7-480*B**3/C/m3*A/(A+B)**7-77/4*B**5/C/m3/A/(A+B)**7-23/4*B**6/C/m3/A**2/(A+B)**7 

numexpr当矩阵元素是这种形式时,完全扼流圈,而eval瞬时进行评估。对于仅10 * 10的矩阵(文件中的100个方程)numexpr需要大约78秒来处理文件,而eval需要0.01秒。对使用numexpr的代码进行性能分析发现数字问题的getExprnamesprecompile函数是造成问题的原因,其中precompile占用了总时间的73.5秒,而getExprNames占用了3.5秒的时间。为什么预编译会在getExprNames和getExprNames之间造成这种特殊计算的瓶颈?这个模块不适合长代数表达式吗?

+3

我会用'numexpr.evaluate()' - 这是安全的,速度非常快,支持numpy的阵列... – MaxU

+0

@MaxU为numexpr VS的EVAL一个有趣的现象见编辑。 – Yeti

我找到了一种方法,通过使用multiprocessing库来加速eval()在这个特定的实例中。我像往常一样读取文件,但是然后将列表分成相同大小的子列表,然后可以在不同的CPU上分别处理这些列表,并在最后重新组合评估的子列表。这为原始方法提供了一个很好的加速。我确信下面的代码可以被简化/优化;但现在它起作用了(例如,如果存在素数的列表元素,这将意味着不相等的列表)。一些粗糙的基准测试表明,使用笔记本电脑的4个CPU时,速度提高了3倍。下面是代码:

from multiprocessing import Process, Queue 

with open("test.txt", 'r') as h: 
    linesHH = h.readlines() 


# Get the number of list elements 
size = len(linesHH) 

# Break apart the list into the desired number of chunks 
chunk_size = size/4 
chunks = [linesHH[x:x+chunk_size] for x in xrange(0, len(linesHH), chunk_size)] 

# Declare variables 
A = 0.1 
B = 2 
C = 2.1 
m3 = 1 
z3 = 2 

# Declare all the functions that process the substrings 
def my_funcHH1(A,B,C,que): #add a argument to function for assigning a queue to each chunk function 
    lstHH1 = [] 
    for i in chunks[0]: 
     HH1 = eval(i) 
     lstHH1.append(HH1) 
    que.put(lstHH1) 

def my_funcHH2(A,B,C,que):  
    lstHH2 = [] 
    for i in chunks[1]: 
     HH2 = eval(i) 
     lstHH2.append(HH2) 
    que.put(lstHH2) 

def my_funcHH3(A,B,C,que):  
    lstHH3 = [] 
    for i in chunks[2]: 
     HH3 = eval(i) 
     lstHH3.append(HH3) 
    que.put(lstHH3) 

def my_funcHH4(A,B,C,que):  
    lstHH4 = [] 
    for i in chunks[3]: 
     HH4 = eval(i) 
     lstHH4.append(HH4) 
    que.put(lstHH4) 

queue1 = Queue()  
queue2 = Queue() 
queue3 = Queue() 
queue4 = Queue() 

# Declare the processes 
p1 = Process(target= my_funcHH1, args= (A,B,C,queue1))  
p2 = Process(target= my_funcHH2, args= (A,B,C,queue2)) 
p3 = Process(target= my_funcHH3, args= (A,B,C,queue3)) 
p4 = Process(target= my_funcHH4, args= (A,B,C,queue4)) 

# Start them 
p1.start() 
p2.start() 
p3.start() 
p4.start() 

HH1 = queue1.get() 
HH2 = queue2.get() 
HH3 = queue3.get() 
HH4 = queue4.get() 
p1.join() 
p2.join() 
p3.join() 
p4.join() 

# Obtain the final result by combining lists together again. 
mergedlist = HH1 + HH2 + HH3 + HH4