Python代码优化(比C慢20倍)

时间:2010-02-24 18:33:03

标签: python optimization math performance

我写过这个非常优化的C代码,可以进行简单的数学计算:

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))


unsigned long long int p(int);
float fullCheck(int);

int main(int argc, char **argv){
  int i, g, maxNumber;
  unsigned long long int diff = 1000;

  if(argc < 2){
    fprintf(stderr, "Usage: %s maxNumber\n", argv[0]);
    return 0;
  }
  maxNumber = atoi(argv[1]);

  for(i = 1; i < maxNumber; i++){
    for(g = 1; g < maxNumber; g++){
      if(i == g)
        continue;
      if(p(MAX(i,g)) - p(MIN(i,g)) < diff &&  fullCheck(p(MAX(i,g)) - p(MIN(i,g))) && fullCheck(p(i) + p(g))){
          diff = p(MAX(i,g)) - p(MIN(i,g));
          printf("We have a couple %llu %llu with diff %llu\n", p(i), p(g), diff);
      }
    }
  }

  return 0;
}

float fullCheck(int number){
  float check = (-1 + sqrt(1 + 24 * number))/-6;
  float check2 = (-1 - sqrt(1 + 24 * number))/-6;
  if(check/1.00 == (int)check)
    return check;
  if(check2/1.00 == (int)check2)
    return check2;
  return 0;
}

unsigned long long int p(int n){
  return n * (3 * n - 1 ) / 2;
}

然后我尝试(只是为了好玩)在Python下移植它以查看它会如何反应。我的第一个版本几乎是1:1转换,运行速度非常慢(Python中为120 +秒,C中为<1秒)。 我做了一些优化,这就是我获得的:

#!/usr/bin/env/python
from cmath import sqrt
import cProfile
from pstats import Stats

def quickCheck(n):
        partial_c = (sqrt(1 + 24 * (n)))/-6 
        c = 1/6 + partial_c
        if int(c.real) == c.real:
                return True
        c = c - 2*partial_c
        if int(c.real) == c.real:
                return True
        return False

def main():        
        maxNumber = 5000
        diff = 1000
        for i in range(1, maxNumber):
                p_i = i * (3 * i - 1 ) / 2
                for g in range(i, maxNumber):
                        if i == g:
                                continue
                        p_g = g * (3 * g - 1 ) / 2
                        if p_i > p_g:
                                ma = p_i
                                mi = p_g
                        else:
                                ma = p_g
                                mi = p_i

                        if ma - mi < diff and quickCheck(ma - mi):
                                if quickCheck(ma + mi):
                                        print ('New couple ', ma, mi)
                                        diff = ma - mi


cProfile.run('main()','script_perf')
perf = Stats('script_perf').sort_stats('time', 'calls').print_stats(10)

大约16秒运行,这比C更好但也差不多20倍。 现在,我知道C对于这种计算来说比Python更好,但是我想知道的是,如果有一些我错过的东西(Python方式,就像一个非常慢的函数或类似的东西)可以使这个功能快点。 请注意,我使用的是Python 3.1.1,如果这有所不同

6 个答案:

答案 0 :(得分:17)

由于quickCheck被调用接近25,000,000次,您可能希望使用memoization来缓存答案。

您可以在C和Python中进行memoization。 C中的事情也会快得多。

您在quickCheck的每次迭代中计算1/6。我不确定这是否会被Python优化,但如果你可以避免重新计算常量值,你会发现事情更快。 C编译器会为你做这件事。

if condition: return True; else: return False这样的事情很愚蠢 - 而且非常耗时。只需return condition

在Python 3.x中,/2必须创建浮点值。你似乎需要整数。你应该使用//2分裂。就它的功能而言,它将更接近C版本,但我认为它的速度要快得多。

最后,Python通常被解释。解释器总是比C慢得多。

答案 1 :(得分:9)

我在机器上从约7秒到约3秒:

  • 每个值的预计算i * (3 * i - 1 ) / 2,在您的计算中它被计算两次很多
  • 对quickCheck的缓存调用
  • 通过向范围
  • 添加+1来删除if i == g
  • 已删除if p_i > p_g,因为p_i 始终小于p_g

还将quickCheck函数放在main中,使所有变量都是本地的(查找比全局更快)。 我相信还有更多微优化可供选择。

def main():
        maxNumber = 5000
        diff = 1000

        p = {}
        quickCache = {}

        for i in range(maxNumber):
            p[i] = i * (3 * i - 1 ) / 2

        def quickCheck(n):
            if n in quickCache: return quickCache[n]
            partial_c = (sqrt(1 + 24 * (n)))/-6 
            c = 1/6 + partial_c
            if int(c.real) == c.real:
                    quickCache[n] = True
                    return True
            c = c - 2*partial_c
            if int(c.real) == c.real:
                    quickCache[n] = True
                    return True
            quickCache[n] = False
            return False

        for i in range(1, maxNumber):
                mi = p[i]
                for g in range(i+1, maxNumber):
                        ma = p[g]
                        if ma - mi < diff and quickCheck(ma - mi) and quickCheck(ma + mi):
                                print('New couple ', ma, mi)
                                diff = ma - mi

答案 2 :(得分:5)

因为函数p()单调递增,所以可以避免将值比较为 g&gt;我暗示 p(g)&gt; P(I)。此外,内循环可以提前破坏,因为 p(g) - p(i)&gt; = diff 暗示 p(g + 1) - p(i)&gt; = diff < / em>的

同样为了正确性,我在quickCheck中更改了相等比较,以比较与epsilon的差异,因为与浮点的精确比较非常脆弱。

在我的机器上,使用Python 2.6将运行时间减少到7.8ms。使用PyPy和JIT将其减少到0.77ms。

这表明在转向微优化之前,寻找算法优化是值得的。微优化使得识别算法的变化更加难以获得相对微小的收益。

EPS = 0.00000001
def quickCheck(n):
    partial_c = sqrt(1 + 24*n) / -6
    c = 1/6 + partial_c
    if abs(int(c) - c) < EPS:
        return True
    c = 1/6 - partial_c
    if abs(int(c) - c) < EPS:
        return True
    return False

def p(i):
    return i * (3 * i - 1 ) / 2

def main(maxNumber):
    diff = 1000

    for i in range(1, maxNumber):
        for g in range(i+1, maxNumber):
            if p(g) - p(i) >= diff:
                break 
            if quickCheck(p(g) - p(i)) and quickCheck(p(g) + p(i)):
                print('New couple ', p(g), p(i), p(g) - p(i))
                diff = p(g) - p(i)

答案 3 :(得分:4)

有些python编译器实际上可能对你有好处。看看Psyco

处理数学密集型程序的另一种方法是将大部分工作重写为数学内核,例如NumPy,以便大量优化的代码正在完成工作,而python代码仅指导计算。为了充分利用这个策略,避免在循环中进行计算,而是让数学内核完成所有这些。

答案 4 :(得分:2)

其他受访者已经提到过几项有所帮助的优化措施。但是,最终,您无法在Python中匹配C的性能。 Python是一个很好的工具,但是由于它被解释,它不适合大量数字运算或其他性能关键的应用程序。

此外,即使在您的C版本中,您的内部循环也可以使用相当多的帮助。更新版本:

  for(i = 1; i < maxNumber; i++){
    for(g = 1; g < maxNumber; g++){
      if(i == g)
        continue;
      max=i;
      min=g;

      if (max<min) { 
          // xor swap - could use swap(p_max,p_min) instead. 
          max=max^min;
          min=max^min;
          max=max^min;
      }

      p_max=P(max);
      p_min=P(min);
      p_i=P(i);
      p_g=P(g);

      if(p_max - p_min < diff &&  fullCheck(p_max-p_min) && fullCheck(p_i + p_g)){
          diff = p_max - p_min;
          printf("We have a couple %llu %llu with diff %llu\n", p_i, p_g, diff);
      }
    }
  }


///////////////////////////
float fullCheck(int number){
  float den=sqrt(1+24*number)/6.0;
  float check = 1/6.0 - den;
  float check2 = 1/6.0 + den;


  if(check == (int)check)
    return check;
  if(check2 == (int)check2)
    return check2;

  return 0.0;
}

分区,功能调用等成本很高。此外,计算它们一次并存储在我已经完成的变量中可以使事情更具可读性。

您可以考虑将P()声明为内联或重写为预处理器宏。根据优化器的优劣程度,您可能希望自己执行一些算法并简化其实现。

你的fullCheck()实现会返回看似无效的结果,因为1/6 == 0,其中1 / 6.0会返回0.166 ......正如你所料。

这是非常简要介绍您可以对C代码执行的操作以提高性能。毫无疑问,这将扩大C和Python性能之间的差距。

答案 5 :(得分:1)

对于一个数字运算任务,Python和C之间的差异对我来说似乎相当不错。

检查通常的performance differences是否有一些CPU密集型任务(请记住,该比例是对数的)。

但从好的方面来看,与大脑相比,1分钟的CPU时间和打字时间是否可以节省编写Python而不是C? : - )