当前位置: 首页 > 面试题库 >

长期的难题,如何在python中优化多级循环?

苗征
2023-03-14
问题内容

我已经在python中编写了一个函数来计算高斯扩展中的Delta函数,该函数涉及4级循环。但是,效率非常低,以类似方式比使用Fortran慢约10倍。

def Delta_Gaussf(Nw, N_bd, N_kp, hw, eigv):
    Delta_Gauss = np.zeros((Nw,N_kp,N_bd,N_bd),dtype=float)
    for w1 in range(Nw):
        for k1 in range(N_kp):
            for i1 in range(N_bd):
                for j1 in range(N_bd):
                    if ( j1 >= i1 ):
                        Delta_Gauss[w1][k1][i1][j1] = np.exp(pow((eigv[k1][j1]-eigv[k1][i1]-hw[w1])/width,2))
    return Delta_Gauss

我删除了一些常量以使其看起来更简单。

有谁能帮助我优化此脚本以提高效率?


问题答案:

只需编译

为了获得最佳性能,我建议使用Numba(易于使用,性能良好)。另外,Cython可能是一个好主意,但是对您的代码进行了更多更改。

实际上,您一切都正确,并实现了一个易于理解的解决方案(对于人类而言,对于编译器而言最重要)。

基本上有两种获取性能的方法

  1. 向量化代码,如@scnerd所示。这通常比仅编译一个非常简单的代码(仅使用一些for循环)要慢一些,也更复杂。 不要 向量化代码,而要使用编译器。从一个简单的循环方法来说,这通常是需要做的工作,并且会导致更慢和更复杂的结果。此过程的优点是您只需要numpy,这是几乎每个处理某些数值计算的Python项目中的标准依赖项。

  2. 编译代码。如果您已经有一个包含几个循环而没有其他循环的解决方案,或者仅涉及几个非numpy函数,则这通常是最简单,最快的解决方案。

使用Numba的解决方案

您不必进行太多更改,我将pow函数更改为np.power,并对numpy中访问数组的方式进行了一些细微更改(这实际上不是必需的)。

import numba as nb
import numpy as np

#performance-debug info
import llvmlite.binding as llvm
llvm.set_option('', '--debug-only=loop-vectorize')

@nb.njit(fastmath=True)
def Delta_Gaussf_nb(Nw, N_bd, N_kp, hw, width,eigv):
    Delta_Gauss = np.zeros((Nw,N_kp,N_bd,N_bd),dtype=float)
    for w1 in range(Nw):
        for k1 in range(N_kp):
            for i1 in range(N_bd):
                for j1 in range(N_bd):
                    if ( j1 >= i1 ):
                        Delta_Gauss[w1,k1,i1,j1] = np.exp(np.power((eigv[k1,j1]-eigv[k1,i1]-hw[w1])/width,2))
    return Delta_Gauss

由于“如果”,SIMD向量化失败。在下一步中,我们可以将其删除(可能需要在njited函数外部进行调用np.triu(Delta_Gauss))。我还并行化了功能。

@nb.njit(fastmath=True,parallel=True)
def Delta_Gaussf_1(Nw, N_bd, N_kp, hw, width,eigv):
    Delta_Gauss = np.zeros((Nw,N_kp,N_bd,N_bd),dtype=np.float64)
    for w1 in nb.prange(Nw):
        for k1 in range(N_kp):
            for i1 in range(N_bd):
                for j1 in range(N_bd):
                    Delta_Gauss[w1,k1,i1,j1] = np.exp(np.power((eigv[k1,j1]-eigv[k1,i1]-hw[w1])/width,2))
    return Delta_Gauss

性能

Nw = 20
N_bd = 20
N_kp = 20
width=20
hw = np.linspace(0., 1.0, Nw) 
eigv = np.zeros((N_kp, N_bd),dtype=np.float)

Your version:           0.5s
first_compiled version: 1.37ms
parallel version:       0.55ms

这些简单的优化可以使速度提高约1000倍。



 类似资料:
  • 有时候你会遇到循环,或者递归函数,它们会花费很长的执行时间,可能是你的产品的瓶颈。在你尝试使循环变得快一点之前,花几分钟考虑是否有可能把它整个移除掉,有没有一个不同的算法?你可以在计算时做一些其他的事情吗?如果你不能找到一个方法去绕开它,你可以优化这个循环了。这是很简单的,move stuff out。最后,这不仅需要智慧而且需要理解每一种语句和表达式的开销。这里是一些建议: 删除浮点运算操作。

  • 问题内容: 因此,Scala应该和Java一样快。我正在重新研究最初在Java中解决的Scala Project Euler 问题。特别是问题5:“能被从1到20的所有数字均分的最小正数是多少?” 这是我的Java解决方案,需要0.7秒才能在计算机上完成: 这是我对Scala的“直接翻译”,需要103秒(长147倍!) 最后,这是我进行函数式编程的尝试,该过程需要39秒(长55倍) 在Window

  • 在写leetcode每日一题的过程中遇到了一些无法解决的问题。 我进行了多次调试,虽然解决了问题,但是解决的莫名奇妙,也不知道为啥突然就能运行了。。。 题目描述: 给你一个下标从 0 开始的数组 words ,数组中包含 互不相同 的字符串。 如果字符串 words[i] 与字符串 words[j] 满足以下条件,我们称它们可以匹配: 字符串 words[i] 等于 words[j] 的反转字符串

  • 我想找到配对的数量,一个很大的数字。如果我给数字n,并要求确定配对的数量,这样 <代码>S(x) 而constants是

  • 问题内容: 我刚接触Python,但仍处于学习曲线的艰难阶段。感谢您的任何评论。 我有一个很大的for循环要运行(在许多迭代中都很大),例如: 我虽然认为这将是一个如何并行化的常见问题,但在Google上搜索了数小时后,我使用“多重处理”模块找到了解决方案,如下所示: 当循环较小时,此方法有效。但是,如果循环很大,这确实很慢,或者如果循环太大,有时会发生内存错误。看来python会首先生成参数列表

  • 我需要解决一个类似背包问题的最佳化问题。我在这篇文章中详细描述了最佳化问题:动态变量背包优化我实际上需要使用python而不是OPL,所以我安装了docplex和clpex包,以便使用cplex优化框架。 这是我想用docplex转换成python的OPL代码 这是我的第一次代码尝试: 我实际上不知道如何正确地建模OPL代码中的变量xg、xc和z? 关于如何正确建模的任何想法。先谢谢你 编辑:这是