为什么我的python / numpy示例比纯C实现更快?

时间:2013-01-22 19:57:39

标签: python c performance numpy

我在python和C. Python示例中有相同的代码:

import numpy
nbr_values = 8192
n_iter = 100000

a = numpy.ones(nbr_values).astype(numpy.float32)
for i in range(n_iter):
    a = numpy.sin(a)

C示例:

#include <stdio.h>
#include <math.h>
int main(void)
{
  int i, j;
  int nbr_values = 8192;
  int n_iter = 100000;
  double x;  
  for (j = 0; j < nbr_values; j++){
    x = 1;
    for (i=0; i<n_iter; i++)
    x = sin(x);
  }
  return 0;
}

当我运行两个例子时发生了一些奇怪的事情:

$ time python numpy_test.py 
real    0m5.967s
user    0m5.932s
sys     0m0.012s

$ g++ sin.c
$ time ./a.out 
real    0m13.371s
user    0m13.301s
sys     0m0.008s

看起来python / numpy比C快两倍。上面的实验有什么错误吗?你怎么解释它?

P.S。我有Ubuntu 12.04,8G ram,核心i5顺便说一句

3 个答案:

答案 0 :(得分:17)

首先,启用优化。其次,细微之处很重要。你的C代码肯定不是“基本相同”。

这是等效的C代码:

sinary2.c:

#include <math.h>
#include <stdlib.h>

float *sin_array(const float *input, size_t elements)
{
    int i = 0;
    float *output = malloc(sizeof(float) * elements);
    for (i = 0; i < elements; ++i) {
        output[i] = sin(input[i]);
    }
    return output;
}

sinary.c:

#include <math.h>
#include <stdlib.h>

extern float *sin_array(const float *input, size_t elements)

int main(void)
{
    int i;
    int nbr_values = 8192;
    int n_iter = 100000;
    float *x = malloc(sizeof(float) * nbr_values);  
    for (i = 0; i < nbr_values; ++i) {
        x[i] = 1;
    }
    for (i=0; i<n_iter; i++) {
        float *newary = sin_array(x, nbr_values);
        free(x);
        x = newary;
    }
    return 0;
}

结果:

$ time python foo.py 

real    0m5.986s
user    0m5.783s
sys 0m0.050s
$ gcc -O3 -ffast-math sinary.c sinary2.c -lm
$ time ./a.out 

real    0m5.204s
user    0m4.995s
sys 0m0.208s

程序必须分成两部分的原因是为了哄骗优化器。否则它将意识到整个循环根本没有效果并优化它。将事物放在两个文件中并不能让编译器在编译sin_array时可以看到main的可能副作用,因此它必须假设它实际上有一些并重复调用它。

由于多种原因,您的原始程序完全不相同。一个是你在C版本中有嵌套循环而你没有在Python中。另一个是您正在使用Python版本中的值数组而不是C版本。另一个是您在Python版本中创建和丢弃数组,而不是在C版本中。最后,您在Python版本中使用float,在C版本中使用double

简单地调用sin函数适当的次数不能进行等效测试。

此外,对于C来说,优化器是一个非常大的优点。当你想知道速度比较时,将优化器尚未用于其他任何事情的C代码进行比较是不对的。当然,你还需要注意。 C优化器非常复杂,如果你正在测试一些真正没有做任何事情的东西,C优化器可能会注意到这个事实而根本就没有做任何事情,导致程序的速度非常快。

答案 1 :(得分:2)

因为“numpy”是为速度而实现的专用数学库。 C具有sin / cos的标准函数,通常是为了精确度而导出的。

你也没有比较苹果和苹果,因为你在C中使用double,在python中使用float32(float)。如果我们改变python代码来计算float64,那么我的机器上的时间会增加大约2.5秒,使其与正确优化的C版本大致匹配。

如果整个测试做了更复杂的事情,需要更多的控制结构(if / else,do / while等),那么你可能会看到C和Python之间的差别更小 - 因为C编译器可以'真的更快地做“罪” - 除非你实现更好的“罪”功能。

更新的想法是你的代码在双方都不一样......;)

答案 2 :(得分:0)

你似乎在C 8192 x 10000次中执行相同的操作但在python中只有10000次(我之前没有使用过numpy所以我可能会误解代码)。你为什么在python的情况下使用数组(再次我不习惯numpy所以也许隐藏了dereferencing)。如果你想使用一个数组,小心双打在缓存和优化的矢量化方面会有性能影响 - 你在两个实现之间使用不同的类型(float vs double)但是给定算法我觉得不重要。

围绕C与Pythis,Pythat的大量异常性能基准问题的主要原因......仅仅是C实现通常很差。

https://www.ibm.com/developerworks/community/blogs/jfp/entry/A_Comparison_Of_C_Julia_Python_Numba_Cython_Scipy_and_BLAS_on_LU_Factorization?lang=en

如果你注意到那个人写C来处理一个双打数组(他没有使用restrict或const关键字),那么他构建优化然后强制编译器使用SIMD而不是AVE。简而言之,如果他想要性能,编译器就会使用低效的双精度指令集和错误类型的寄存器 - 你可以确定numba和numpy将使用尽可能多的铃声和口哨,并且将提供非常高效的C和C ++库开始。简而言之,如果你想要C的速度你必须考虑它,你甚至可能必须反汇编代码并可能禁用优化并使用编译器instrinsics。它为您提供了执行此操作的工具,因此不要指望编译器为您完成所有工作。如果你想要那个自由度,可以使用Cython,Numba,Numpy,Scipy等。它们非常快,但是你无法从机器中获得所有的性能 - 使用C,C ++或者新的FORTRAN的版本。

这是关于这些要点的非常好的文章(我使用SciPy):

https://www.scipy.org/scipylib/faq.html