变换视图而不是投影仪会降低检查两个字符串是否不区分大小写相等时的 C 风格方法的性能

问题描述 投票:0回答:1

tl;博士

在检查两个字符串是否不区分大小写相等时,为什么是这样的代码

bool rangeV3WithTransformViews(std::string_view str1, std::string_view str2) {
    return equal(str1 | transform(tolowercase),
                 str2 | transform(tolowercase));
}

比这个慢很多吗?

bool rangeV3WithProj(std::string_view str1, std::string_view str2) {
    return equal(str1, str2, std::equal_to{}, tolowercase, tolowercase);
}

从后面的代码中,带有

-O3
的 GCC 13.2 设法生成与低级 C 风格解决方案相同的汇编代码:

#define to_lower_ascii(c) (((c) >= 'A' && (c) <= 'Z') ? (c) + 32 : (c))

int cstyle(const char* str1, const char* str2) {
    while (*str1 != '\0' && *str2 != '\0' && to_lower_ascii(*str1) == to_lower_ascii(*str2)) {
        str1++;
        str2++;
    }
    return to_lower_ascii(*str1) - to_lower_ascii(*str2);
}

更多资料

我的问题的根源是当我意识到编写如此冗长、低级且不可读的代码是为了实现不区分大小写的字符串相等这样简单的事情时,我感到沮丧。

所以我开始编写一个基于范围的解决方案。最初,我最终得到了上面的第一个解决方案,认为这是我能写的最好的解决方案,但quick-bench.com证明我错了。是的,也许我没有仔细写测试,也没有阅读Google Benchmark的文档,但是有两件事是清楚的

  1. GCC(至少是 QuickBench 上的版本,并带有这些选项)设法将
    rangeV3WithProj
    编译为与
    cstyle
    相同的程序集,这意味着编译器可以 100% 看透抽象;
  2. rangeV3WithProj
    rangeV3WithTransformViews
    更简单并且使用更少的抽象,所以我看不出有任何理由认为前者会比后者慢。

我希望编译器也能看穿

rangeV3WithTransformViews
,并生成相同的汇编代码,但事实并非如此。

所以我的问题是:

  • 是什么(如果有的话)使得第一个片段本质上比第二个片段慢?
  • 什么(如果有的话)使得编译器更难为第一个片段计算出与第二个和第三个片段相同的程序集?

我认为问题在于

transform
,这是
rangeV3WithTransformViews
rangeV3WithProj
之间的唯一区别,但为什么它会产生这样的效果(不同的组装,可能性能更差)?毕竟,
str1
str2
的大小在O(1)的函数中是已知的,因为它们是
std::string_view
,因此它们满足
sized_range
,并且
transform
返回一个
 sized_range
用于
sized_range
输入,所以不像存在未知长度的循环或类似的东西,不是吗?

注意事项

我真的不知道 QuickBench 是做什么的,但让我紧张的一件事是 Compiler Explorer 不会生成相应相同的汇编代码 for

rangeV3WithProj
for
cstyle
,即使我通过了
-O3 -std=c++20 
就像在 QuickBench 上一样。


(1) 你是否注意到了,如果我通过将一些

!=
更改为
==
,将
32
更改为
34
来修改代码,那么最后一个
str2
应该是
str1
,或类似的东西?

c++ performance c++20 benchmarking range-v3
1个回答
0
投票

这是因为你的基准是错误的。

如果启用“无操作”栏,您会发现

rangeV3WithProj
cstyle
的速度比不执行任何操作快约 1 倍:它们已被优化。

您实际上想确保

b
没有被优化,而不是函数指针:
benchmark::DoNotOptimize(b);
而不是
benchmark::DoNotOptimize(<function name>);

即便如此,时间安排仍然不公平,因为

rangeV3WithProj
rangeV3WithTransformViews
必须调用
std::tolower
,后者必须查看当前的语言环境。公平地说,请将
std::tolower
与所有三个函数一起使用,或将宏与
constexpr auto tolowercase = [](char c)->char{ return to_lower_ascii(c); };
一起用于所有函数(以确保所有函数具有相同的结果)。

此外,您的第三个函数略有不同:它需要一个以 null 结尾的 C 字符串,而不是大小连续的字符范围。您还必须使用

!cstyle(str1, str2)
才能获得相同的结果。您可以使用以nul结尾的C字符串的范围算法,但不能开箱即用(https://godbolt.org/z/TKfM5Ph4o


使用

std::ranges
,具有范围的两种方法编译为完全相同的程序集:https://godbolt.org/z/GbTcKTP4x

与 Range-v3 相同:https://godbolt.org/z/Th4oT8f7c(尽管与

std::ranges
组件略有不同)

鉴于生成完全相同的代码,运行时不应有差异。

© www.soinside.com 2019 - 2024. All rights reserved.