pip 基础架构不支持这种粒度。
我认为更好的方法是编译两个版本的 Cython 扩展:使用 -march=native 和不使用 -march=native,安装两者并在运行时决定应该加载哪个版本。
这是一个概念证明。
第一个跳转:如何在运行时检查 CPU/OS 组合支持哪些指令。为简单起见,我们将检查 AVX(此 SO-post 有更多详细信息),我仅提供 gcc 特定(另请参阅 this)解决方案 - 称为 impl_picker.pyx:
cdef extern from *:
"""
int cpu_supports_avx(void){
return __builtin_cpu_supports("avx");
}
"""
int cpu_supports_avx()
def cpu_has_avx_support():
return cpu_supports_avx() != 0
第二个问题:pyx-file和module必须同名。为避免代码重复,实际代码在 pxi 文件中:
# worker.pxi
cdef extern from *:
"""
int compiled_with_avx(void){
#ifdef __AVX__
return 1;
#else
return 0;
#endif
}
"""
int compiled_with_avx()
def compiled_with_avx_support():
return compiled_with_avx() != 0
可以看到,函数compiled_with_avx_support 将yield different results,这取决于它是否使用-march=native 编译。
现在我们可以通过包含 *.pxi 文件中的实际代码来定义模块的两个版本。一个叫做worker_native.pyx的模块:
# distutils: extra_compile_args=["-march=native"]
include "worker.pxi"
和worker_fallback.pyx:
include "worker.pxi"
构建一切,例如通过cythonize -i -3 *.pyx,可以这样使用:
from impl_picker import cpu_has_avx_support
# overhead once when imported:
if cpu_has_avx_support():
import worker_native as worker
else:
print("using fallback worker")
import worker_fallback as worker
print("compiled_with_avx_support:", worker.compiled_with_avx_support())
在我的机器上,以上将导致compiled_with_avx_support: True,在旧机器上,将使用“较慢”的worker_fallback,结果将是compiled_with_avx_support: False。
这篇文章的目的不是提供一个有效的setup.py,而只是概述如何实现在运行时选择正确版本的目标。显然,setup.py 可能要复杂得多:例如需要使用不同的编译器设置编译多个 c 文件(参见 SO-post,如何实现)。