在检查两个字符串是否不区分大小写相等时,为什么是这样的代码
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的文档,但是有两件事是清楚的
rangeV3WithProj
编译为与 cstyle
相同的程序集,这意味着编译器可以 100% 看透抽象;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
,或类似的东西?
这是因为你的基准是错误的。
如果启用“无操作”栏,您会发现
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
组件略有不同)
鉴于生成完全相同的代码,运行时不应有差异。