给出以下基准:
const char* payload = "abcdefghijk";
const std::size_t payload_len = 11;
const std::size_t payload_count = 1000;
static void StringAppend(benchmark::State& state) {
for (auto _ : state) {
std::string created_string;
created_string.reserve(payload_len * payload_count + 1);
for(int i = 0 ; i < payload_count; ++i) {
created_string.append(payload, payload_len);
}
benchmark::DoNotOptimize(created_string);
}
}
BENCHMARK(StringAppend);
static void StringBackInsert(benchmark::State& state) {
for (auto _ : state) {
std::string created_string;
created_string.reserve(payload_len * payload_count + 1);
auto inserter = std::back_inserter(created_string);
for(int i = 0 ; i < payload_count; ++i) {
for(std::size_t i = 0; i < payload_len; ++i) {
*inserter = payload[i];
++inserter;
}
}
benchmark::DoNotOptimize(created_string);
}
}
BENCHMARK(StringBackInsert);
static void StringPushBack(benchmark::State& state) {
for (auto _ : state) {
std::string created_string;
created_string.reserve(payload_len * payload_count + 1);
for(int i = 0 ; i < payload_count; ++i) {
for(std::size_t i = 0; i < payload_len; ++i) {
created_string.push_back(payload[i]);
}
}
benchmark::DoNotOptimize(created_string);
}
}
BENCHMARK(StringPushBack);
我在quickbench上获得了以下内容,它们显示出非常显着的差异:
考虑到所有必需的内存是提前分配的,我很难接受这样的想法,即仅进行大小与容量检查实际上就代表了这里的所有成本,除非可能有大量的负载-hit-store或分支的错误预测。
http://quick-bench.com/XQ9kepYFE1_dZD8vVaQwOUSSVoE
我想了解的是:
编译器并非总能成功地将一个字节的循环优化成一个不可怕的东西。您正在将已知长度的append
与调用push_back
的整个内部循环进行比较。
[push_back
包括大小检查,因此以这种方式使用它检查并在压入任何字节之后有条件地重新分配+副本,这可能会破坏编译器对其进行优化的能力。
但是append
只需要在整个块之间进行检查,我认为clang可以内联11字节memcpy以使用几个加载/存储而不是11字节加载/字节存储。