【问题标题】:3x3 Matrix determinant function - making it faster3x3 矩阵行列式函数 - 使其更快
【发布时间】:2012-02-26 17:25:01
【问题描述】:

我正在编写一个更大的程序,并且尽可能快地获得 3x3 矩阵的行列式对于它运行良好非常重要。我读过我可以使用 numPy 来做这件事,但我认为编写自己的代码可能会更有教育意义,因为我正在 CompSci 的第三学期。

所以我写了两个函数,我使用 time.clock()(我在 win7 机器上)来计算每个函数返回值所需的时间。

这是第一个函数:

def dete(a):
   x = (a[0][0] * a[1][1] * a[2][2]) + (a[1][0] * a[2][1] * a[3][2]) + (a[2][0] * a[3][1] * a[4][2])
   y = (a[0][2] * a[1][1] * a[2][0]) + (a[1][2] * a[2][1] * a[3][0]) + (a[2][2] * a[3][1] * a[4][0])
   return x - y

这是第二个功能:

def det(a):
    a.append(a[0]); a.append(a[1]);
    x = 0
    for i in range(0, len(a)-2):
        y=1;        
        for j in range(0, len(a)-2):    
            y *= a[i+j][j]      
        x += y

    p = 0
    for i in range(0, len(a)-2):
        y=1;
        z = 0;
        for j in range(2, -1, -1):  
            y *= a[i+z][j]  
            z+=1        
        z += 1
        p += y  
    return x - p

他们都给出了正确的答案,但是第一个似乎稍微快了一点,这让我觉得因为 for 循环使用起来更优雅而且通常更快,所以我做错了 - 我也做了循环又慢又胖。我尝试将其修剪下来,但似乎 *= 和 += 操作花费了太多时间,它们太多了。 我还没有检查 numPy 处理这个问题的速度有多快,但我想更好地编写高效的代码。 关于如何使这些循环更快的任何想法?

【问题讨论】:

  • 似乎稍微快一点?请使用timeit 和分析器来准确地显示速度有多快。
  • 所以 for 循环通常比简单的展开直接计算更快?嗯...看来我真的需要学习很多关于 Python 的东西 ;)
  • 如果你想计算一个 3x3 行列式,没有什么比你的第一个函数中的这个优化公式更优雅了。

标签: python performance matrix determinants


【解决方案1】:

循环更优雅、更通用,但它们并不比单个表达式中的几个内联乘法“通常更快”。

首先,python 中的 forloop 必须组装你将交互的对象(对 range 的调用),然后为循环中的每个项目调用该迭代器上的方法。

因此,根据您的操作,如果内联表单的速度足以让您保留它 - 如果它仍然太慢(通常是我们在 Python 中进行数值计算时的情况),您应该使用数字库(例如 NumpY),可以计算本机代码中的行列式。对于这样的数字操作代码,您可以使用本机代码将其运行速度提高数百倍。

如果 yo9u 需要一些已经制作的库无法执行的数值计算,如果您寻求速度(例如,图像处理中的像素操作),您可能更愿意编写一个在本机代码中运行的扩展(使用 C、Cython 或其他一些东西)以使其快速运行。

另一方面,如果速度不是至关重要的,并且您甚至注意到内联表达式只是“稍微快一点”,那么只需使用完整循环 - 您将获得更具可读性和可维护性的代码 - 这是使用 Python 的主要原因毕竟。

在您给出的具体示例中,您可以通过硬编码对元组的“范围”调用来提高循环代码的速度 - 例如,更改: for i in range(0, len(a)-2):for i in (0, 1, 2) - 请注意,与内联的情况一样,您失去了处理不同大小矩阵的能力。

【讨论】:

  • 非常感谢您提供的非常详尽的答案,我一定会尝试您提到的所有选项,如果我确实需要代码运行得更快,我会用 Java 重做。我真的很喜欢用 Python 做事,太愉快了。
【解决方案2】:

首先让我注意,微尺度速度优化应该以另一种语言进行。因此,您最好使用使用 c 编写功能的库。

关于你的 for 循环: 展开(小)循环以加快速度是一种常用技术,因此让循环完成这项工作并不总是更快。通常它只是更通用(大多数通用算法实际上比专用算法慢)。

如 cmets 中所述,当用 * 替换 - 时,它不会提高 python 的速度,但如果涉及的算术运算较少,它可能会提高速度。因此,我将在此处发布分解后的术语:

def dete(a):
    return (a[0][0] * (a[1][1] * a[2][2] - a[2][1] * a[1][2])
           -a[1][0] * (a[0][1] * a[2][2] - a[2][1] * a[0][2])
           +a[2][0] * (a[0][1] * a[1][2] - a[1][1] * a[0][2]))

如您所见,有 5 个+/- 和 9 个*,而在原始版本中有 5 个+/- 和 12 个*。另请注意,此版本仅访问了 a 15 次,而原始版本访问了 18 次。

总结起来,这产生了 3 个算术运算和 3 个变量访问,比完全乘法的版本少。

【讨论】:

  • * 更改为 Python 代码中的一系列总和会更慢 - Python 对于算法操作速度慢的原因是操作涉及自省和方法调用 - 对于每个操作。这比 CPU 绑定乘法和加法之间的差异要多数百倍。
  • 好的,我删除了它,这更像是我的 C++ 背景中的一个注释:)
  • @jsbueno 在任何现代架构上将 * 更改为一系列总和会更慢。 ALU 具有本机乘法运算是有原因的,而 JIT 编译器使用它也是有原因的。
  • 在作为浮点数或整数的基本 cpu 数据类型上,这可能是正确的,但大多数矩阵运算在更高级别的数据类型中利用它们的优势,在 * 中仍然比在 - 中慢得多。但 OP 可能只有那些“简单”类型的矩阵。
  • 为了记录,将常用术语分解为没有人最初建议的确实减少了所需的操作总数。与 17 次相比,您可以将其减少到 14 次算术运算,因此这不仅仅是乘减法交换,即使我们规定 * 的成本与 - 一样多,这也是一种胜利。
【解决方案3】:

当你展开它时,如上所述,你也可以将两个块合并为一个,因为快速浏览发现两个块之间没有依赖关系(如果我错了,请纠正我)

【讨论】:

    【解决方案4】:

    循环几乎不可能比显式的长表达式更快,所以难怪第一个变体更快。我怀疑您能否比第一个功能更快地提出 smt。

    【讨论】:

      【解决方案5】:

      您可以展开循环并利用您处理 3x3 矩阵而不是 nxn 矩阵这一事实。

      通过这种优化,您可以摆脱对矩阵大小的确定。您可以稍微加快速度来交易灵活性。您可以简单地为结果矩阵的每个 cell 写下具体公式。顺便说一句:(c++) 编译器会做这样的优化工作。

      我只建议这样做,如果你真的确定这样一个小优化值得专门的代码。为确保您优化代码的正确部分,请使用例如分析工具http://docs.python.org/library/profile.htmltimeit

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-02-11
        • 2015-05-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多