如何仅为一个Python/C扩展名源文件指定不同的编译器标志?

2024-10-03 09:07:10 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个Python扩展,它使用CPU特有的特性, 如果有的话。这是通过运行时检查完成的。如果 硬件支持POPCNT指令,然后选择一条 实现我的内部循环,如果SSSE3可用,那么 它会选择另一个版本,否则会返回到通用版本 我的性能关键内核。(大约95%以上的时间是 在这个内核中花费的时间。)

不幸的是,有一种我没有预料到的失败模式。我 使用-mssse3-O3编译所有C代码,即使 只有一个文件需要-mssse3选项。因此,编译其他文件时预期SSSE3将存在。这会导致线路出现SEG故障:

start_target_popcount = (int)(query_popcount * threshold);

因为编译器使用了fisttpl,这是一条SSSE3指令。 毕竟,我告诉它假设SSSE3存在

我的软件包的Debian打包程序最近遇到了这个问题, 因为测试机器有一个理解-mssse3和 生成代码时考虑到这一点,但机器本身有一个 没有这些指令的旧CPU

我想要一个解决方案,使相同的二进制文件可以在较旧的机器上工作 在较新版本上,Debian维护人员可以将其用于该发行版

理想情况下,我想说只编译一个文件 使用-mssse3选项。因为我的CPU特定选择器代码 不是此文件的一部分,将不会执行任何SSSE3代码 除非CPU支持它

然而,我想不出任何方法来告诉distutils 一组编译器选项特定于单个文件。
这可能吗


Tags: 文件代码版本机器编译器选项指令时间
3条回答

一个非常丑陋的解决方案是创建两个(或更多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 *

你当然不需要我来写代码!我知道这不是干的,我期待着阅读更好的答案

已经5年了,但我找到了一个比我的“CC”包装更喜欢的解决方案

“build_ext”命令创建一个self.compiler实例。compile.compile()方法获取要编译的所有源文件的列表。基类进行一些设置,然后有一个编译器。_compile()钩子用于具体的编译器子类,以实现实际的每个文件编译步骤

我觉得这是足够稳定的,我可以拦截代码在那一点上

我从distutils.command.build\u ext.build\u ext派生了一个新命令,该命令调整了self.compiler.\u 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}
)

不幸的是,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/​链接器选项

相关问题 更多 >