【问题标题】:Surprising Benchmark Result令人惊讶的基准测试结果
【发布时间】:2018-01-18 02:19:14
【问题描述】:

在观看了 Titus Winters 的 "Live at Head" 演讲后,他提到 StrCat() 是人们最喜欢的功能之一,我决定尝试实现类似的东西,看看我是否可以击败 std::string::append(或就运行时性能而言,operator+,我认为它在内部使用 append 。我的理由是,作为可变参数模板实现的 strcat() 函数将能够确定其所有类似字符串的参数的组合大小,并进行一次分配来存储最终结果,而不必在以下情况下不断重新分配operator+,它不知道调用它的总体上下文。

但是,当我将我的自定义实现与 quick-bench 上的 operator+ 进行比较时,我发现我的 strcat() 实现在使用 -std=c++17 -O3 编译的最新版本的 clang 和 gcc 上比 operator+ 慢约 4 倍。我在下面包含了快速工作台代码以供参考。

有人知道是什么原因导致这里速度变慢吗?

#include <cstring>
#include <iostream>
#include <string>

// Get the size of string-like args
int getsize(const std::string& s) { return s.size(); }
int getsize(const char* s) { return strlen(s); }
template <typename S>
int strcat_size(const S& s) {
  return getsize(s);
}
template <typename S, typename... Strings>
int strcat_size(const S& first, Strings... rest) {
  if (sizeof...(Strings) == 0) {
    return 0;
  } else {
    return getsize(first) + strcat_size(rest...);
  }
}

// Populate a pre-allocated string with content from another string-like object
template <typename S>
void strcat_fill(std::string& res, const S& first) {
  res += first;
}
template <typename S, typename... Strings>
void strcat_fill(std::string& res, const S& first, Strings... rest) {
  res += first;
  strcat_fill(res, rest...);
}

template <typename S, typename... Strings>
std::string strcat(const S& first, Strings... rest) {
  int totalsize = strcat_size(first, rest...);

  std::string res;
  res.reserve(totalsize);

  strcat_fill(res, first, rest...);

  return res;
}

const char* s1 = "Hello World! ";
std::string s2 = "Here is a string to concatenate. ";
std::string s3 = "Here is a longer string to concatenate that avoids small string optimization";
const char* s4 = "How about some more strings? ";
std::string s5 = "And more strings? ";
std::string s6 = "And even more strings to use!";

static void strcat_bench(benchmark::State& state) {
  // Code inside this loop is measured repeatedly
  for (auto _ : state) {
    std::string s = strcat(s1, s2, s3, s4, s5, s6);

    benchmark::DoNotOptimize(s);
  }
}
BENCHMARK(strcat_bench);

static void append_bench(benchmark::State& state) {
  for (auto _ : state) {
    std::string s = s1 + s2 + s3 + s4 + s5 + s6;

    benchmark::DoNotOptimize(s);
  }
}
BENCHMARK(append_bench);

【问题讨论】:

  • 我可能是错的,但我相信可变参数模板(正如你所写的那样)会产生副本。您需要通过Strings&amp;&amp;... rest 接受并使用std::forward,例如this answer
  • @vu1p3n0x 你是对的。完美的转发使它更快。在这种情况下,虽然它似乎与传递 const 引用一样好。

标签: c++ performance benchmarking string-concatenation abseil


【解决方案1】:

这是因为按值传递参数。

我将代码改为使用折叠表达式(看起来更简洁)
并删除了不必要的副本(Strings... rest 应该是参考)。

int getsize(const std::string& s) { return s.size(); }
int getsize(const char* s) { return strlen(s); }

template <typename ...P>
std::string strcat(const P &... params)
{
  std::string res;
  res.reserve((getsize(params) + ...));
  (res += ... += params);
  return res;
}

This solution beats append by approximately 30%.


在这种情况下,通过const 引用和进行完美转发之间似乎没有区别。这是有道理的,因为std::string += 不会移动它的参数,即使它们是右值。


如果您无法访问新的花哨的折叠表达式但仍想要性能,请改用“虚拟数组”技巧(在这种情况下似乎具有完全相同的性能)。

template <typename ...P>
std::string strcat(const P &... params)
{
  using dummy_array = int[]; // This is necessary because `int[]{blah}` doesn't compile.
  std::string res;
  std::size_t size = 0;
  dummy_array{(void(size += getsize(params)), 0)..., 0};
  res.reserve(size);
  dummy_array{(void(res += params), 0)..., 0};
  return res;
}

【讨论】:

  • 哇,你的版本更简洁了!我无法访问 C++17 功能,但只需将原始代码中的 Strings... 参数更改为 const ref 确实确实大大提高了性能,尽管我看到了更接近 50% 的改进跨度>
  • @AUD_FOR_IUV 也许我当时搞砸了我的基准;我编辑了答案。同样正如我所说,您可以使用虚拟数组而不是折叠表达式。
猜你喜欢
  • 2018-01-09
  • 2022-06-11
  • 2013-06-29
  • 1970-01-01
  • 1970-01-01
  • 2013-07-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多