【问题标题】:Cython: why is size_t faster than int?Cython:为什么 size_t 比 int 快?
【发布时间】:2016-10-22 07:17:48
【问题描述】:

将某些 Cython 变量从 int 类型更改为 size_t 类型可以显着减少某些函数时间 (~30%),但我不明白为什么。

例如:

cimport numpy as cnp
import numpy as np

def sum_int(cnp.int64_t[::1] A):
    cdef unsigned long s = 0
    cdef int k
    for k in xrange(A.shape[0]):
        s += A[k]
    return s

def sum_size_t(cnp.int64_t[::1] A):
    cdef unsigned long s = 0
    cdef size_t k
    for k in xrange(A.shape[0]):
        s += A[k]
    return s

a = np.array(range(1000000))

以及计时结果:

In [17]: %timeit sum_int(a)   
1000 loops, best of 3: 652 µs per loop

In [18]: %timeit sum_size_t(a)
1000 loops, best of 3: 427 µs per loop

我是 Cython 的新手,并且比 C 更了解 Fortran。请帮帮我。导致这种性能差异的这两种变量类型之间的重要区别是什么? Cython 有什么我不理解的?

【问题讨论】:

  • 你检查生成的程序集了吗?
  • @KarolyHorvath 我试图查看生成的 C 代码,但几乎失明了。我确信它已经过高度优化,但由于我更喜欢​​ Fortran,因此自动生成的 C 语言很难阅读。
  • 您正在获取int64_t 的数组。为什么不将int64_t 用于您的累加器?
  • @user2357112 哈哈。好吧,我想我认为添加一百万个数字可能会溢出 64 位整数。邋遢。但是,它不会改变结果。两个示例之间的累加器类型没有变化。
  • size_t 不是隐含无符号的吗?当您在循环中使用变量 k 时,我猜如果您使用 size_t 或 unsigned int 作为 cython doc 报告,则它是优化的 "range() 在 cdef 声明索引值时是 C 优化的,对于示例”。因此,众所周知,您稍后用作A 的索引器的无符号值永远不会为负(它应该允许您将wraparoundboundscheck 参数转换为False,因为您正在安全循环在您的数组范围内,并且可能具有更高的性能)?

标签: python performance types cython


【解决方案1】:

您可能必须逐行进行分析才能准确找到,但是从生成的 C 文件中对我来说有一点很突出:检查int 版本是否环绕到负数,假设size_t好的。

在 int 循环中:(t_3 是从 k 分配的,它们是同一类型)

if (__pyx_t_3 < 0) {
  __pyx_t_3 += __pyx_v_A.shape[0];
  if (unlikely(__pyx_t_3 < 0)) __pyx_t_4 = 0;
} else if (unlikely(__pyx_t_3 >= __pyx_v_A.shape[0])) __pyx_t_4 = 0;

在 size_t 循环中:

if (unlikely(__pyx_t_3 >= (size_t)__pyx_v_A.shape[0])) __pyx_t_4 = 0;

所以不需要回绕测试,因为size_t 是无符号的,并且保证在索引内存中的项目时不会回绕。其余的几乎相同。

更新:关于您的 unsigned int 结果 - 您的 int 和 size_t 大小是多少?有没有可能它们的大小不同,导致变化?在我的例子中,uint 和 size_t 的 C 代码是相同的。 (因为 size_t 在这个系统上是无符号的,特别是无符号的 int )

【讨论】:

  • 好的,我做了一些时间测试,发现如果我使用“unsigned int”并关闭带有装饰器的环绕测试,正如你上面建议的那样,我会恢复“size_t”的性能”。因此,如果您添加“size_t”未签名的注释,我会接受您的回答。
  • 总是关闭边界检查和环绕,一旦你知道你在做什么......我不知道使用 size_t 会自动暗示这一点。
【解决方案2】:

在 64 位系统上似乎有两个原因:

  1. 循环使用无符号整数:

    %%cython
    
    cimport numpy as cnp
    import numpy as np
    
    def sum_int_unsigned(cnp.int64_t[::1] A):
        cdef unsigned long s = 0
        cdef unsigned k
        for k in xrange(A.shape[0]):
            s += A[k]
        return s
    
  2. 使用long 而不是int

    %%cython
    
    cimport numpy as cnp
    import numpy as np
    
    def sum_int_unsigned_long(cnp.int64_t[::1] A):
        cdef unsigned long s = 0
        cdef unsigned long k
        for k in xrange(A.shape[0]):
            s += A[k]
        return s
    

时间安排:

%timeit sum_int(a)
1000 loops, best of 3: 1.52 ms per loop

%timeit sum_size_t(a)
1000 loops, best of 3: 671 µs per loop

使用unsigned 让我们成功了一半:

%timeit sum_int_unsigned(a) 
1000 loops, best of 3: 1.09 ms per loop

使用long 占其余部分:

%timeit sum_int_unsigned_long(a)
1000 loops, best of 3: 648 µs per loop

【讨论】:

    猜你喜欢
    • 2018-08-25
    • 2018-01-04
    • 2014-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-09
    • 1970-01-01
    • 2014-10-12
    相关资源
    最近更新 更多