我有一个Python扩展,它使用特定于CPU的功能, 如果可供使用的话。这是通过运行时检查完成的。如果 硬件支持POPCNT指令,然后选择一个 如果SSSE3可用,我的内循环的实现 它选择另一个,否则它回退到通用版本 我的性能关键内核。 (大约95%以上的时间是 花在这个内核上。)
不幸的是,有一种我没想到的失败模式。一世 尽管如此,使用-mssse3和-O3来编译所有C代码 只有一个文件需要-mssse3选项。
结果,其他文件按期望编译 SSSE3将存在。这会导致行
的段错误start_target_popcount = (int)(query_popcount * threshold);
因为编译器使用的是fisttpl,这是一条SSSE-3指令。 毕竟,我告诉它假设ssse3存在。
我的软件包的Debian打包器最近遇到了这个问题, 因为测试机器有一个理解-mssse3和gcc的gcc 考虑到这一点生成代码,但机器本身有一个 没有这些说明的旧CPU。
我想要一个解决方案,其中相同的二进制文件可以在旧机器上运行 而对于较新的,Debian维护者可以用于该发行版。
理想情况下,我想说只编译了一个文件 使用-mssse3选项。由于我的CPU特定选择器代码 不是此文件的一部分,不会执行任何SSSE-3代码 除非CPU支持。
然而,我无法想办法告诉distutils 一组编译器选项特定于单个文件。
这甚至可能吗?
答案 0 :(得分:5)
一个非常难看的解决方案是创建两个(或更多Extension
)类,一个用于保存SSSE3代码,另一个用于保存其他所有代码。然后你可以在python层中整理界面。
c_src = [f for f in my_files if f != 'ssse3_file.c']
c_gen = Extension('c_general', sources=c_src,
libraries=[], extra_compile_args=['-O3'])
c_ssse3 = Extension('c_ssse_three', sources=['ssse3_file.c'],
libraries=[], extra_compile_args=['-O3', '-mssse3'])
并在__init__.py
某处
from c_general import *
from c_ssse_three import *
当然你不需要我写出那些代码!而且我知道这不是干的,我期待着更好的回答!
答案 1 :(得分:2)
已经5年了,但我想出了一个比我的“CC”包装更好的解决方案。
“build_ext”命令创建一个self.compiler实例。 compiler.compile()方法获取要编译的所有源文件的列表。基类进行一些设置,然后为具体的编译器子类创建一个compiler._compile()钩子,以实现实际的每文件编译步骤。
我觉得这很稳定,我可以在那时拦截代码。
我从distutils.command.build_ext.build_ext派生了一个新命令,该命令调整self.compiler._compile以使用附加到实例的一次性函数包装绑定类方法:
class build_ext_subclass(build_ext):
def build_extensions(self):
original__compile = self.compiler._compile
def new__compile(obj, src, ext, cc_args, extra_postargs, pp_opts):
if src != "src/popcount_SSSE3.c":
extra_postargs = [s for s in extra_postargs if s != "-mssse3"]
return original__compile(obj, src, ext, cc_args, extra_postargs, pp_opts)
self.compiler._compile = new__compile
try:
build_ext.build_extensions(self)
finally:
del self.compiler._compile
然后我告诉setup()使用这个命令类:
setup(
...
cmdclass = {"build_ext": build_ext_subclass}
)
答案 2 :(得分:1)
不幸的是,OP 的解决方案仅适用于 Unix 编译器。这是一个交叉编译器:
(MSVC 不支持自动生成 SSSE3 代码,因此我将使用 AVX 为例)
from setuptools import setup, Extension
import distutils.ccompiler
filename = 'example_avx'
compiler_options = {
'unix': ('-mavx',),
'msvc': ('/arch:AVX',)
}
def spawn(self, cmd, **kwargs):
extra_options = compiler_options.get(self.compiler_type)
if extra_options is not None:
# filenames are closer to the end of command line
for argument in reversed(cmd):
# Check if argument contains a filename. We must check for all
# possible extensions; checking for target extension is faster.
if not argument.endswith(self.obj_extension):
continue
# check for a filename only to avoid building a new string
# with variable extension
off_end = -len(self.obj_extension)
off_start = -len(filename) + off_end
if argument.endswith(filename, off_start, off_end):
if self.compiler_type == 'bcpp':
# Borland accepts a source file name at the end,
# insert the options before it
cmd[-1:-1] = extra_options
else:
cmd += extra_options
# we're done, restore the original method
self.spawn = self.__spawn
# filename is found, no need to search any further
break
distutils.ccompiler.spawn(cmd, dry_run=self.dry_run, **kwargs)
distutils.ccompiler.CCompiler.__spawn = distutils.ccompiler.CCompiler.spawn
distutils.ccompiler.CCompiler.spawn = spawn
setup(
...
ext_modules = [
Extension('extension_name', ['example.c', 'example_avx.c'])
],
...
)
请参阅 my answer here 以了解一般指定编译器/链接器选项的交叉编译器方式。