【问题标题】:How do I specify different compiler flags for just one Python/C extension source file?如何为一个 Python/C 扩展源文件指定不同的编译器标志?
【发布时间】:2013-03-09 18:53:44
【问题描述】:

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

不幸的是,出现了我没想到的故障模式。一世 使用 -mssse3-O3 编译所有 C 代码,即使 只有一个文件需要-mssse3 选项。因此,其他文件的编译预期 SSSE3 将存在。这会导致该行出现段错误:

start_target_popcount = (int)(query_popcount * threshold);

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

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

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

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

但是,我想不出任何办法告诉distutils 一组编译器选项特定于单个文件。
这可能吗?

【问题讨论】:

  • 在考虑 danodonovan 的答案时,我意识到一个 hack 是有一个“CC”包装器,它为特定文件插入正确的标志。不优雅,但对 Debian 来说可能已经足够了。
  • 我最近发现了一些合并 CMake 和 distutils 的快乐,使用 CMake 生成与扩展链接的静态库。你可以做类似的事情。在此处查看我们的 setup.py:github.com/CoolProp/CoolProp/blob/master/wrappers/Python/…

标签: python setup.py distutils compiler-flags


【解决方案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,了解一般指定编译器/链接器选项的交叉编译器方式。

【讨论】:

    【解决方案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}
    )
    

    【讨论】:

    • 我刚刚花了一个小时阅读 distutils 源代码,这可能是 Unix 上唯一好的解决方案,所以 +1 给 Andrew Dalke。但是,我认为它不会在 Windows 上运行,因为 MSVC 编译器似乎没有 _compile 方法。
    • 如果你想访问new__compile中的Extension对象,你可以覆盖build_extension(self, extension),这是build_ext.build_extensions(self)self.distribution.ext_modules中的每个扩展所做的(即@987654329 @设置在setup().
    【解决方案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 *
    

    当然你不需要我写出那个代码!而且我知道这不是 DRY,我期待阅读更好的答案!

    【讨论】:

    • 不幸的是,决定运行哪个计算内核的是 C 代码,因此您的建议虽然可行,但变得相当困难。基本上,我需要实现 C 共享库,或者我必须有某种动态 API 来注册可用的计算内核。与指定每个文件标志的理想解决方案相比,两者都需要大量工作。
    猜你喜欢
    • 1970-01-01
    • 2015-02-25
    • 2018-06-01
    • 2020-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多