为什么我的 Python NumPy 代码比 C 更快?

新手上路,请多包涵

为什么这个 Python NumPy 代码,

 import numpy as np
import time

k_max = 40000
N = 10000

data = np.zeros((2,N))
coefs = np.zeros((k_max,2),dtype=float)

t1 = time.time()
for k in xrange(1,k_max+1):
    cos_k = np.cos(k*data[0,:])
    sin_k = np.sin(k*data[0,:])
    coefs[k-1,0] = (data[1,-1]-data[1,0]) + np.sum(data[1,:-1]*(cos_k[:-1] - cos_k[1:]))
    coefs[k-1,1] = np.sum(data[1,:-1]*(sin_k[:-1] - sin_k[1:]))
t2 = time.time()

print('Time:')
print(t2-t1)

比下面的 C++ 代码更快?

 #include <cstdio>
#include <iostream>
#include <cmath>
#include <time.h>

using namespace std;

// consts
const unsigned int k_max = 40000;
const unsigned int N = 10000;

int main()
{
    time_t start, stop;
    double diff;
    // table with data
    double data1[ N ];
    double data2[ N ];
    // table of results
    double coefs1[ k_max ];
    double coefs2[ k_max ];
    // main loop
    time( & start );
    for( unsigned int j = 1; j<N; j++ )
    {
        for( unsigned int i = 0; i<k_max; i++ )
        {
            coefs1[ i ] += data2[ j-1 ]*(cos((i+1)*data1[ j-1 ]) - cos((i+1)*data1[ j ]));
            coefs2[ i ] += data2[ j-1 ]*(sin((i+1)*data1[ j-1 ]) - sin((i+1)*data1[ j ]));
        }
    }
    // end of main loop
    time( & stop );
    // speed result
    diff = difftime( stop, start );
    cout << "Time: " << diff << " seconds";
    return 0;
}

第一个显示:“时间:8 秒”,而第二个:“时间:11 秒”

我知道 NumPy 是用 C 编写的,但我仍然认为 C++ 示例会更快。我错过了什么吗?有没有办法改进 C++ 代码(或 Python 代码)?

代码版本 2

我已按照其中一条评论的建议更改了 C++ 代码(将动态表更改为静态表)。 C++ 代码现在更快,但仍然比 Python 版本慢得多。

代码版本 3

我已从调试模式更改为发布模式,并将“k”从 4000 增加到 40000。现在 NumPy 稍微快一点(8 秒到 11 秒)。

原文由 Mateusz 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 611
2 个回答

我发现这个问题很有趣,因为每次我遇到关于 NumPy 速度(与 C/C++ 相比)的类似话题时,总是会有类似“它是一个瘦包装器,它的核心是用 C 编写的,所以它很快”这样的答案,但是这个没有解释为什么 C 应该比带有附加层的 C 慢(即使是薄层)。

答案是: 正确编译后,您的 C++ 代码不会比 Python 代码慢

我做了一些基准测试,起初 NumPy 似乎快得惊人。但我忘了用 GCC 优化编译。

我再次计算了所有内容,并将结果与您的代码的纯 C 版本进行了比较。我正在使用 GCC 版本 4.9.2 和 Python 2.7.9(使用相同的 GCC 从源代码编译)。要编译我使用的 C++ 代码 g++ -O3 main.cpp -o main ,要编译我使用的 C 代码 gcc -O3 main.c -lm -o main 。在所有示例中,我用一些数字(0.1、0.4)填充了 data 变量,因为它会改变结果。我还将 np.arrays 更改为使用双精度数( dtype=np.float64 ),因为 C++ 示例中有双精度数。我的代码的纯 C 版本(类似):

 #include <math.h>
#include <stdio.h>
#include <time.h>

const int k_max = 100000;
const int N = 10000;

int main(void)
{
    clock_t t_start, t_end;
    double data1[N], data2[N], coefs1[k_max], coefs2[k_max], seconds;
    int z;
    for( z = 0; z < N; z++ )
    {
        data1[z] = 0.1;
        data2[z] = 0.4;
    }

    int i, j;
    t_start = clock();
    for( i = 0; i < k_max; i++ )
    {
        for( j = 0; j < N-1; j++ )
        {
            coefs1[i] += data2[j] * (cos((i+1) * data1[j]) - cos((i+1) * data1[j+1]));
            coefs2[i] += data2[j] * (sin((i+1) * data1[j]) - sin((i+1) * data1[j+1]));
        }
    }
    t_end = clock();

    seconds = (double)(t_end - t_start) / CLOCKS_PER_SEC;
    printf("Time: %f s\n", seconds);
    return coefs1[0];
}

对于 k_max = 100000, N = 10000 结果如下:

  • 蟒蛇 70.284362 秒
  • C++ 69.133199 秒
  • C 61.638186 秒

Python 和 C++ 的时间基本相同,但请注意,有一个长度为 k_max 的 Python 循环,与 C/C++ 相比,它应该慢得多。它是。

对于 k_max = 1000000, N = 1000 我们有:

  • 蟒蛇 115.42766 秒
  • C++ 70.781380 秒

对于 k_max = 1000000, N = 100

  • 蟒蛇 52.86826 秒
  • C++ 7.050597 秒

So the difference increases with fraction k_max/N , but python is not faster even for N much bigger than k_max , eg k_max = 100, N = 100000 :

  • 蟒蛇 0.651587 秒
  • C++ 0.568518 秒

显然,C/C++ 和 Python 的主要速度差异在于 for 循环。但我想找出在 NumPy 和 C 中对数组进行简单操作之间的区别。在代码中使用 NumPy 的优点包括:1. 将整个数组乘以一个数字,2. 计算整个数组的 sin/cos, 3. 对数组的所有元素求和,而不是分别对每个项目进行这些操作。所以我准备了两个脚本来只比较这些操作。

Python脚本:

 import numpy as np
from time import time

N = 10000
x_len = 100000

def main():
    x = np.ones(x_len, dtype=np.float64) * 1.2345

    start = time()
    for i in xrange(N):
        y1 = np.cos(x, dtype=np.float64)
    end = time()
    print('cos: {} s'.format(end-start))

    start = time()
    for i in xrange(N):
        y2 = x * 7.9463
    end = time()
    print('multi: {} s'.format(end-start))

    start = time()
    for i in xrange(N):
        res = np.sum(x, dtype=np.float64)
    end = time()
    print('sum: {} s'.format(end-start))

    return y1, y2, res

if __name__ == '__main__':
    main()

# results
# cos: 22.7199969292 s
# multi: 0.841291189194 s
# sum: 1.15971088409 s

C脚本:

 #include <math.h>
#include <stdio.h>
#include <time.h>

const int N = 10000;
const int x_len = 100000;

int main()
{
    clock_t t_start, t_end;
    double x[x_len], y1[x_len], y2[x_len], res, time;
    int i, j;
    for( i = 0; i < x_len; i++ )
    {
        x[i] = 1.2345;
    }

    t_start = clock();
    for( j = 0; j < N; j++ )
    {
        for( i = 0; i < x_len; i++ )
        {
            y1[i] = cos(x[i]);
        }
    }
    t_end = clock();
    time = (double)(t_end - t_start) / CLOCKS_PER_SEC;
    printf("cos: %f s\n", time);

    t_start = clock();
    for( j = 0; j < N; j++ )
    {
        for( i = 0; i < x_len; i++ )
        {
            y2[i] = x[i] * 7.9463;
        }
    }
    t_end = clock();
    time = (double)(t_end - t_start) / CLOCKS_PER_SEC;
    printf("multi: %f s\n", time);

    t_start = clock();
    for( j = 0; j < N; j++ )
    {
        res = 0.0;
        for( i = 0; i < x_len; i++ )
        {
            res += x[i];
        }
    }
    t_end = clock();
    time = (double)(t_end - t_start) / CLOCKS_PER_SEC;
    printf("sum: %f s\n", time);

    return y1[0], y2[0], res;
}

// results
// cos: 20.910590 s
// multi: 0.633281 s
// sum: 1.153001 s

蟒蛇结果:

  • 因为:22.7199969292 秒
  • 多:0.841291189194 秒
  • 总和:1.15971088409 秒

C 结果:

  • 因为:20.910590 秒
  • 多:0.633281 秒
  • 总和:1.153001 秒

如您所见,NumPy 非常快,但总是比纯 C 慢一点。

原文由 Gwidryj 发布,翻译遵循 CC BY-SA 4.0 许可协议

在我的计算机上,您的(当前)Python 代码运行时间为 14.82 秒(是的,我的计算机很慢)。

我将您的 C++ 代码重写为我认为合理的东西(基本上,我几乎忽略了您的 C++ 代码,只是将您的 Python 重写为 C++。这给了我这个:

 #include <cstdio>
#include <iostream>
#include <cmath>
#include <chrono>
#include <vector>
#include <assert.h>

const unsigned int k_max = 40000;
const unsigned int N = 10000;

template <class T>
class matrix2 {
    std::vector<T> data;
    size_t cols;
    size_t rows;
public:
    matrix2(size_t y, size_t x) : cols(x), rows(y), data(x*y) {}
    T &operator()(size_t y, size_t x) {
        assert(x <= cols);
        assert(y <= rows);
        return data[y*cols + x];
    }

    T operator()(size_t y, size_t x) const {
        assert(x <= cols);
        assert(y <= rows);
        return data[y*cols + x];
    }
};

int main() {
    matrix2<double> data(N, 2);
    matrix2<double> coeffs(k_max, 2);

    using namespace std::chrono;

    auto start = high_resolution_clock::now();

    for (int k = 0; k < k_max; k++) {
        for (int j = 0; j < N - 1; j++) {
            coeffs(k, 0) += data(j, 1) * (cos((k + 1)*data(j, 0)) - cos((k + 1)*data(j+1, 0)));
            coeffs(k, 1) += data(j, 1) * (sin((k + 1)*data(j, 0)) - sin((k + 1)*data(j+1, 0)));
        }
    }

    auto end = high_resolution_clock::now();
    std::cout << duration_cast<milliseconds>(end - start).count() << " ms\n";
}

这运行了大约 14.4 秒,因此它比 Python 版本略有改进——但鉴于 Python 主要是一些 C 代码的非常薄的包装器,因此只有轻微的改进几乎是我们应该期待的。

下一个明显的步骤是使用多个内核。为了在 C++ 中做到这一点,我们可以添加这一行:

 #pragma omp parallel for

…在外部 for 循环之前:

 #pragma omp parallel for
for (int k = 0; k < k_max; k++) {
    for (int j = 0; j < N - 1; j++) {
        coeffs(k, 0) += data(j, 1) * (cos((k + 1)*data(j, 0)) - cos((k + 1)*data(j+1, 0)));
        coeffs(k, 1) += data(j, 1) * (sin((k + 1)*data(j, 0)) - sin((k + 1)*data(j+1, 0)));
    }
}

-openmp 添加到编译器的命令行(当然,确切的标志取决于您使用的编译器),这运行了大约 4.8 秒。如果您有 4 个以上的内核,您可能会期望得到比这更大的改进(相反,如果您的内核少于 4 个,则期望得到较小的改进 - 但现在,多于 4 个比更少更常见)。

原文由 Jerry Coffin 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题