对 gcc 使用 -march 开关不会对 运行 时间速度产生影响

using -march switch for gcc does not make a difference in terms of run-time speed

我使用 GCC 11.1 构建了一个小程序(~1000 LOC),运行 它在启用和不启用 -march=native 的情况下进行了多次迭代,但总体而言 没有 程序执行时间的差异(以毫秒为单位)。但为什么?因为它是单线程的?还是我的石器时代硬件(第一代 i5,Westmere 微架构,没有 AVX 东西)功能不够?

我的 Makefile 中的几行:

CXX = g++
CXXFLAGS = -c -std=c++20 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -O3 -march=native -flto
LDFLAGS = -O3 -flto

Here (Compiler Explorer) 是 GCC 不为其生成 SSE 指令的程序的自由函数:

[[ nodiscard ]] size_t
tokenize_fast( const std::string_view inputStr, const std::span< std::string_view > foundTokens_OUT,
               const size_t expectedTokenCount ) noexcept
{
    size_t foundTokensCount { };

    if ( inputStr.empty( ) ) [[ unlikely ]]
    {
        return foundTokensCount = 0;
    }

    static constexpr std::string_view delimiter { " \t" };

    size_t start { inputStr.find_first_not_of( delimiter ) };
    size_t end { };

    for ( size_t idx { }; start != std::string_view::npos && foundTokensCount < expectedTokenCount; ++idx )
    {
        end = inputStr.find_first_of( delimiter, start );
        foundTokens_OUT[ idx ] = inputStr.substr( start, end - start );
        ++foundTokensCount;
        start = inputStr.find_first_not_of( delimiter, end );
    }

    if ( start != std::string_view::npos )
    {
        return std::numeric_limits<size_t>::max( );
    }

    return foundTokensCount;
}

我想知道为什么?可能是因为无法向量化此类代码?

此外,我想提及的另一件事是最终可执行文件的大小根本没有改变,我什至尝试了 -march=westmere-march=alderlake 来查看大小是否有任何差异但是GCC 以相同的大小生成它。

我认为您也应该将 -march=native 指定为 LDFLAGS 的一部分,因此 -flto 是针对同一台机器的。

但您的 code-gen 似乎尊重您指定的拱门,因为您说 -march=alderlake 使代码因 SIGILL 而崩溃,可能是在矢量指令的 AVX 编码上。


很有可能 -mtune=generic 做出与 -march=native 相同的调优决定,而且除了 SSE2 之外,没有什么能从中受益。您的 CPU 支持 SSE4.2 和 popcnt,但 x86-64 的基线已经是 SSE2,相同的矢量宽度只是缺少一些指令,特别是对于 dword 和 qword 元素大小(如打包 min/max)。

GCC/clang 无法 auto-vectorize 搜索循环(仅在第一次迭代之前在运行时已知 trip-count 的循环),因此 inputStr.find_first_of 要么编译为one-byte-at-a-time 搜索,或调用 memchr,它只受益于 SSE2,但可以基于 CPU 功能进行动态调度,因为它在共享库中。

(Glibc 使用“解析器”函数重载动态链接过程,该函数决定 memchr 的哪个实现在当前机器上最好,SSE2 或 AVX2。两个版本都是 hand-written asm ,例如 the SSE2 version's source。像 strstr 这样的几个函数有 SSE4.2 版本,你 CPU 可以利用,但这个选择不依赖于 -march compile-time设置,纯run-time动态链接器+glibc。)


如果你想查看你的程序大部分时间花在哪里,请使用 perf record ./a.out / perf report -Mintel(默认是 AT&T 语法反汇编;我更喜欢 Intel)。

如果它在库函数中,不同的调整选项和可用的新指令可能对您的主代码没有帮助。如果它在你的程序中,而不是库中,那么显然 x86-64 的基线 instruction-set 和“通用”调整选项很好,或者 GCC 不知道如何使用 SSSE3 / SSE4.x 用于您的代码。

我没有仔细查看您的代码在做什么,以了解可能进行哪些手动矢量化。