Cython 发出 'unnecessary' 警告

Cython giving 'unnecessary' warning

以下 cython 脚本会导致 comparison between signed and unsigned integer expressions 警告。

%%cython
# distutils: language = c++

from libcpp.vector cimport vector as cpp_vec
import numpy as np

cdef cpp_vec[long] a  = np.array([1,2,3], np.int)
cdef Py_ssize_t i

for i in range(a.size()):        # Using a.size() as parameter of range() causes the warning
    pass

这个警告有必要吗?如果是这样,为什么?此外,是否可以消除这些未签名与已签名的比较警告?

此外,为什么只有第一个 for 循环导致警告?

%%cython
cdef:
    ssize_t i
    unsigned long m = 10
    unsigned int n = 10
    long o = 10

for i in range(m):
    pass
for i in range(n):
    pass
for i in range(o):
    pass

这是来自您的 g++ 编译器的警告,每个 compiler-warning 都应该认真对待。

您的 Cython 代码的 for-loop 被转换为 following/similar cpp-code:

 std::vector<long>::size_type __pyx_t_7;
 Py_ssize_t __pyx_t_8;
 __pyx_t_7 = __pyx_v_xxxx_a.size();
 for (__pyx_t_8 = 0; __pyx_t_8 < __pyx_t_7; __pyx_t_8+=1) {
     ....
 }

问题是,std::vector<long>::size_type 是 64 位系统上的 64 位无符号整数,而 Py_ssize_t 是 64 位有符号整数。

C++ 对 built-in 类型使用隐式转换,以便能够计算 __pyx_t_8 < __pyx_t_7,规则可以在 SO-post 中找到,例如。

有多种原因,为什么这个警告是有道理的 - 规则非常复杂,经验表明,程序员经常错误地处理它们。例如 -1<1U 的计算结果为 false(参见 live),但

signed char a =-1;
unsigned char b = 1;
std::cout<<(a<b)<<"\n";

打印 1,即 (a<b) 的计算结果为 true(参见 live)。这种怪癖很容易导致 hard-to-find 错误。

更重要的是,从历史上看,在标准建立之前,不同的编译器以不同的方式处理这些转换 - 当切换到不同的编译器时,很高兴看到所有可以改变行为的地方 - 但这不是现在真的很重要。

有不同的策略可以避免此警告。

1.顺其自然:

i 设为 size_t 而不是 Py_ssize_t 是不是更容易,即 cdef size_t i?

2。投射和检查:

您可以显式转换然后检查假设是否正确,例如

...
cdef Py_ssize_t i

cdef Py_ssize_t n = <Py_ssize_t>(a.size())
if n<0 :
    raise ValueError("too big");

for i in range(n):
...

以上变得很快,相当繁琐,所以通常将其提取到utility-function。

3。刚投:

可以问 "how probable it is that a vector has more than 2^63 entries?" 并跳过检查:

...
for i in range(<Py_ssize_t>(a.size())):        
    print(i) 

然后...看到代码在 30 年后失败了:)

4.禁用编译器警告:

也可以通过 setup-file 中的 extra_compile_args 或 IPython 中的 -c=-Wno-sign-compare-Wno-sign-compare 选项传递给编译器,这将关闭警告在整个代码中。使用 "Just cast" 解决方案可能更安全,它只在这个地方而不是所有地方使警告静音。


如果有符号整数的大小大于无符号整数,则不会发出警告:在这种情况下,无符号整数将转换为更大的有符号整数并适合较大类型的正数范围。

例如ssize_t有64位而unsigned int有32位,因此32位无符号位在比较之前转换为64位-ssize_t - 不会溢出,因为正数最多 63 的数字可以用“ssize_t”表示。