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

在Cython与NumPy中对int与float求和时的巨大性能差异

颜德馨
2023-03-14
问题内容

我正在使用Cython或NumPy对一维数组中的每个元素求和。当对 整数 求和时,Cython快〜20%。当对 浮点 求和时,Cython 约2.5倍。以下是使用的两个简单功能。

#cython: boundscheck=False
#cython: wraparound=False

def sum_int(ndarray[np.int64_t] a):
    cdef:
        Py_ssize_t i, n = len(a)
        np.int64_t total = 0

    for i in range(n):
        total += a[i]
    return total

def sum_float(ndarray[np.float64_t] a):
    cdef:
        Py_ssize_t i, n = len(a)
        np.float64_t total = 0

    for i in range(n):
        total += a[i]
    return total

时机

创建两个数组,每个数组包含一百万个元素:

a_int = np.random.randint(0, 100, 10**6)
a_float = np.random.rand(10**6)

%timeit sum_int(a_int)
394 µs ± 30 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit a_int.sum()
490 µs ± 34.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit sum_float(a_float)
982 µs ± 10.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit a_float.sum()
383 µs ± 4.42 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

附加点

  • NumPy在浮点数方面的表现(相当大的优势)甚至超过了自己的整数和。
  • 的性能差异sum_floatboundscheckwraparound指令缺失相同。为什么?
  • 将整数numpy数组转换sum_int为C指针(np.int64_t *arr = <np.int64_t *> a.data)可将性能提高25%。这样做对浮游物没有任何作用

主要问题

如何在Cython中使用浮点数获得与整数相同的性能?

编辑-只是计数很慢?!?

我写了一个甚至更简单的函数,它只计算迭代次数。前者将计数存储为一个int,后者存储为一个双精度。

def count_int():
    cdef:
        Py_ssize_t i, n = 1000000
        int ct=0

    for i in range(n):
        ct += 1
    return ct

def count_double():
    cdef:
        Py_ssize_t i, n = 1000000
        double ct=0

    for i in range(n):
        ct += 1
    return ct

计时时间

我只运行了一次(怕缓存)。不知道循环是否实际上是针对整数执行的,但是count_double具有 sum_float上面 相同的
性能。这太疯狂了…

%timeit -n 1 -r 1 count_int()
1.1 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)

%timeit -n 1 -r 1 count_double()
971 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)

问题答案:

我不会回答您所有的问题,而只是(在我看来)最有趣的问题。

让我们从您的计数示例开始:

  1. 编译器能够在整数情况下优化for循环-生成的二进制文件不会计算任何内容-它仅需要返回在编译阶段预先计算的值。
  2. 对于双写情况,情况并非如此,因为由于舍入错误,结果将不正确,1.0*10**6并且因为cython默认情况下以IEEE 754(非-ffast-math)模式进行编译。

在查看cython代码时,必须牢记这一点:不允许编译器重新排列总和(IEEE
754),因为下一个需要第一个求和的结果,所以只有一行很长,所有操作都在其中等待。

但最关键的见解:numpy的功能与您的cython代码不同:

>>> sum_float(a_float)-a_float.sum()
2.9103830456733704e-08

是的,没有人告诉numpy(不同于您的cython代码),总和必须这样计算

((((a_1+a2)+a3)+a4)+...

numpy通过两种方式利用它:

  1. 它执行成对求和(种类),从而导致较小的舍入误差。

  2. 它以块为单位计算总和(python的代码有些难以理解,这里是相应的模板,其次是使用的函数列表pairwise_sum_DOUBLE

第二点是您要加快速度的原因,其计算类似于以下模式(至少从下面的源代码中可以理解):

a1  + a9 + .....  = r1 
a2  + a10 + ..... = r2
..
a8  + a16 +       = r8

----> sum=r1+....+r8

这种求和的优点:的结果a2+a10不依赖于a1+a9并且这两个值都可以在现代CPU上同时计算(例如,流水线化),从而提高了观察的速度。

就其价值而言,在我的计算机上cython-integer-sum比numpy的慢。

需要考虑numpy数组的步幅(这仅在运行时才知道,另请参见有关矢量化的问题)的需要阻止了一些优化。一种解决方法是使用内存视图,您可以明确地知道数据是连续的,即:

def sum_int_cont(np.int64_t[::1] a):

这导致我的机器显着加速(因数2):

%timeit sum_int(a_int)
2.64 ms ± 46.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit sum_int_cont(a_int)
1.31 ms ± 19 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit a_int.sum()
2.1 ms ± 105 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

的确,在这种情况下,将内存视图用于双精度不会带来任何提速(不知道为什么),但通常会简化优化器的寿命。例如,将memory-view-
variant与-ffast-mathcompile选项结合使用,这将允许关联性,从而产生与numpy相当的性能:

%%cython -c=-ffast-math
cimport numpy as np
def sum_float_cont(np.float64_t[::1] a):
    cdef:
        Py_ssize_t i, n = len(a)
        np.float64_t total = 0

    for i in range(n):
        total += a[i]
    return total

现在:

>>> %timeit sum_float(a_float)
3.46 ms ± 226 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
>>> %timeit sum_float_cont(a_float)
1.87 ms ± 44 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit a_float.sum()
1.41 ms ± 88.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

清单pairwise_sum_DOUBLE

/*
 * Pairwise summation, rounding error O(lg n) instead of O(n).
 * The recursion depth is O(lg n) as well.
 * when updating also update similar complex floats summation
 */
static npy_double
pairwise_sum_DOUBLE(npy_double *a, npy_uintp n, npy_intp stride)
{
    if (n < 8) {
        npy_intp i;
        npy_double res = 0.;
        for (i = 0; i < n; i++) {
            res += (a[i * stride]);
        }
        return res;
    }
    else if (n <= PW_BLOCKSIZE) {
        npy_intp i;
        npy_double r[8], res;

        /*
         * sum a block with 8 accumulators
         * 8 times unroll reduces blocksize to 16 and allows vectorization with
         * avx without changing summation ordering
         */
        r[0] = (a[0 * stride]);
        r[1] = (a[1 * stride]);
        r[2] = (a[2 * stride]);
        r[3] = (a[3 * stride]);
        r[4] = (a[4 * stride]);
        r[5] = (a[5 * stride]);
        r[6] = (a[6 * stride]);
        r[7] = (a[7 * stride]);

        for (i = 8; i < n - (n % 8); i += 8) {
            r[0] += (a[(i + 0) * stride]);
            r[1] += (a[(i + 1) * stride]);
            r[2] += (a[(i + 2) * stride]);
            r[3] += (a[(i + 3) * stride]);
            r[4] += (a[(i + 4) * stride]);
            r[5] += (a[(i + 5) * stride]);
            r[6] += (a[(i + 6) * stride]);
            r[7] += (a[(i + 7) * stride]);
        }

        /* accumulate now to avoid stack spills for single peel loop */
        res = ((r[0] + r[1]) + (r[2] + r[3])) +
              ((r[4] + r[5]) + (r[6] + r[7]));

        /* do non multiple of 8 rest */
        for (; i < n; i++) {
            res += (a[i * stride]);
        }
        return res;
    }
    else {
        /* divide by two but avoid non-multiples of unroll factor */
        npy_uintp n2 = n / 2;
        n2 -= n2 % 8;
        return pairwise_sum_DOUBLE(a, n2, stride) +
               pairwise_sum_DOUBLE(a + n2 * stride, n - n2, stride);
    }
}


 类似资料:
  • 问题内容: 我知道nvarchar上的连接速度较慢,因为索引比nvarchar大,每个字符使用2个字节,而int始终为4个字节。联接性能差异是否显着?有什么强烈的理由要避免加入nvarchar?我找不到有关该主题的任何MSDN文章。 问题答案: 至少8x CPU。这是将nvarchar与varchar进行比较时可测量的增加:与纯正varchar相比,unicode排序和比较规则更加复杂。 因此,假

  • 问题内容: 我正在计算稀疏自动编码器的算法。我已经使用和在python中实现了它。代码几乎相同,但是性能却大不相同。matlab完成任务所需的时间为0.252454秒,而numpy为0.973672151566,几乎是原来的四倍。在最小化问题中,我将在以后多次调用此代码,因此这种差异会导致实现之间的延迟几分钟。这是正常行为吗?如何提高numpy的性能? numpy实现: Sparse.rho是调整

  • 问题内容: 我试图找出如果将主键更改为BIGINT(20)时表的性能是否会下降。目前,我正在使用INT(7),并且已经有大约 300.000个条目具有大ID(7或8位数字) 。我已经搜索了很多东西,但只发现它使用了更多的磁盘空间(这很明显)。 我所有的ID现在都有7位数字,但是我的客户希望更改为8位数字。将来我将无法轻松更改软件,因此我考虑现在使用BIGINT(20)以防万一。即使我不需要使用BI

  • 问题内容: 我在我的python程序中使用cython进行相关计算。我有两个音频数据集,我需要知道它们之间的时差。根据开始时间切割第二组,然后在第一组上滑动。有两个for循环:一个滑动集合,而内部循环则计算该点的相关性。此方法效果很好,并且足够准确。 问题在于,使用纯python会花费超过一分钟的时间。使用我的cython代码,大约需要17秒。这仍然太多了。您是否有任何提示可以加快此代码的速度:

  • 看起来“ignoring()”和“permitAll()”都是在请求Web资源时绕过Spring Security的方法。使用这两种方法的性能差异是什么,为什么一种方法比另一种方法更快/可扩展?