【问题标题】:Why is there no optimization for checking for empty string via "" comparison?为什么没有通过“”比较检查空字符串的优化?
【发布时间】:2020-09-16 18:54:51
【问题描述】:

Quick Bench 上检查的这个 google-benchmark 代码表明,string::empty() 的运行速度比与空字符串文字相比要快得多。但是,创建"" 的名称字符串实际上会使编译器优化检查:

bool compareWithNamedConst(const std::string& target) {
    const std::string emptyString = "";
    return emptyString == target;
}

bool compareWithLiteral(const std::string& target) {
    return "" == target;
}

bool compareWithRvalue(const std::string& target) {
    return std::string{""} == target;
}

bool checkForEmpty(const std::string& target) {
    return target.empty();
}

每个调用的性能如下所示:

如您所见,与所有其他选项相比,与 "" 的比较速度非常慢。我想知道为什么会这样?它一定与未在const char* 上应用 SSO 有某种关系,因为测试:

bool compareWithLiteral(const std::string& target) {
    return "test with a longer string not optimized" == target;
}

bool compareWithRvalue(const std::string& target) {
    return std::string{"test with a longer string not optimized"} == target;
}

与文字比较的结果实际上更快:

我发现检查字符串是否为空,最容易阅读的语法是"" == myVariable,因为它清楚地表明myVariablestd::string,没有不必要的混乱。为什么我们不能像其他所有情况一样对其进行优化?

【问题讨论】:

  • 您在运行这些示例时采用了哪种优化方式?
  • -O3 关于 gcc 和 clang
  • compareWithLiteral 是比较不同类型对象的唯一情况。我猜这就是根本原因。我不太确定允许编译器“优化”隐式转换。
  • fwiw,使用lenght() == 0 代替empty() 也是一个很好的提示,它是一个字符串,不需要使用字符串文字,虽然这更易读当然是意见跨度>
  • 似乎"" == std::string() 调用strcmp,而std::string() == std::string() 调用memcmp。我的猜测是,提前知道字符串的大小可以让std::string::operator==(std::string) 立即比较大小。 this comparethis compare

标签: c++ gcc clang c++14


【解决方案1】:

对于空性检查,如果您真的喜欢文字语法,您可以通过using namespace std::string_literals; 帮助您的编译器,并将"" 替换为""s。在那种情况下,compareWithLiteralcompareWithRvaluecheckForEmpty 将基本相同,因为const std::string& 之间的operator== 通常会在内容之前检查大小,而""s 的大小是微不足道的。

bool compareWithLiteral(const std::string& target) {
    using namespace std::string_literals;
    return ""s == target;
}

对于不属于 SSO 的字符串,您应该尝试使用 std::string_view 及其 operator""sv,即使不幸的是仅从 C++17 开始可用。 Boost 有boost::string_view。没有为编译时创建提供用户字符串文字(这是必不可少的,因为测试字符串长度将在二进制文件中硬编码),但您可以轻松定义一个:

#include <boost/utility/string_view.hpp>

constexpr auto operator""_sv(const char* str, std::size_t len) noexcept {
    return boost::basic_string_view<char>{str, len};
}

bool compareWithLiteral(const std::string& target) {
    return "test with a longer string not optimized"_sv == target;
}

这个版本比你的compareWithLiteral 版本运行得快得多,至少当target 大小不同时。

【讨论】:

  • 虽然是真的(恕我直言,这是个好建议),但这根本不能回答 OP 的问题,这不是 如何 有效地执行检查,而是 为什么没有优化与 C 风格字符串的比较。
  • @KonradRudolph 同意,我的问题是什么阻止编译器看到大小为零的短 c 样式字符串,并且完全跳过循环遍历其内容直到 \0,因为它是空的。跨度>
  • 我明白了,但是当语言几乎免费提供预测行为的工具时,为什么还要将代码的性能依赖于编译器优化?
【解决方案2】:

我认为主要区别在于涉及std::string 对象的比较首先只检查它们的大小。如果它们不相等,比较立即返回。 (这也可以解释为什么 @KamilCuk 指出,如果两个字符串的长度相等,则可以使用 memcmp。)

相反,对于文字,它们被视为 C 字符串/字符数组,其中不提供有关其长度的信息。因此,strcmp 函数在其内部使用 更大的开销,即使其中一个字符串为空(至少,由于 strcmp 的函数调用的开销似乎没有被内联在你的程序集中)。

至于最后一个问题,compareWithRvalue,那里的字符串对于 SSO 来说太大了,因此,动态内存分配(和释放)被执行(并且可能没有在这里优化),这在这里表示相对较大的开销。

【讨论】:

  • 假设我有一个简单的实现,可以通过循环直到看到\0 并检查字符来与const char* 进行比较。编译器可以在编译时知道它将读取的第一件事将是\0。为什么它仍然没有优化检查?
  • @ÁdámHunyadi 首先,您可以在 quick-bench 中尝试此实现。其次,这是编译器编写者的问题。优化能力在现代编译器中非常强大,但并不是万能的。
  • @ÁdámHunyadi 我尝试了您提出的自定义实现和it was just slightly slower than the other options。请注意while (*p1 &amp;&amp; ...)。如果它切换到while (*p2 &amp;&amp; ...,那么它的运行速度会慢得多。编译器在编译时显然知道*p1 为零。
猜你喜欢
  • 1970-01-01
  • 2013-01-26
  • 1970-01-01
  • 1970-01-01
  • 2013-08-12
  • 2011-06-21
  • 2012-02-09
  • 2016-08-26
  • 2023-03-16
相关资源
最近更新 更多