13

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

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

start_target_popcount = (int)(query_popcount * threshold);

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

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

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

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

但是,我想不出任何方法来判断distutils一组编译器选项特定于单个文件。
这甚至可能吗?

4

3 回答 3

6

一个非常难看的解决方案是创建两个(或更多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,我期待阅读更好的答案!

于 2013-03-20T15:46:22.707 回答
3

已经 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}
)
于 2018-04-11T10:39:58.793 回答
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'])
    ],
    ...
)

请在此处查看我的答案,了解一般指定编译器/链接器选项的交叉编译器方式。

于 2021-07-24T09:42:48.060 回答