还是我在测量其他东西?
在这段代码中,我有一堆标签(integers
)。每个标签都有一个字符串表示形式(const char*
或std::string_view
)。在循环中,堆栈值将转换为相应的字符串值。这些值将附加到预分配的字符串或分配给数组元素。
结果显示,带有std::string_view
的版本比具有的版本要快一些const char*
。
码:
#include <array>
#include <iostream>
#include <chrono>
#include <stack>
#include <string_view>
using namespace std;
int main()
{
enum Tag : int { TAG_A, TAG_B, TAG_C, TAG_D, TAG_E, TAG_F };
constexpr const char* tag_value[] =
{ "AAA", "BBB", "CCC", "DDD", "EEE", "FFF" };
constexpr std::string_view tag_values[] =
{ "AAA", "BBB", "CCC", "DDD", "EEE", "FFF" };
const size_t iterations = 10000;
std::stack<Tag> stack_tag;
std::string out;
std::chrono::steady_clock::time_point begin;
std::chrono::steady_clock::time_point end;
auto prepareForBecnhmark = [&stack_tag, &out](){
for(size_t i=0; i<iterations; i++)
stack_tag.push(static_cast<Tag>(i%6));
out.clear();
out.reserve(iterations*10);
};
// Append to string
prepareForBecnhmark();
begin = std::chrono::steady_clock::now();
for(size_t i=0; i<iterations; i++) {
out.append(tag_value[stack_tag.top()]);
stack_tag.pop();
}
end = std::chrono::steady_clock::now();
std::cout << out[100] << "append string const char* = " << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << "[µs]" << std::endl;
prepareForBecnhmark();
begin = std::chrono::steady_clock::now();
for(size_t i=0; i<iterations; i++) {
out.append(tag_values[stack_tag.top()]);
stack_tag.pop();
}
end = std::chrono::steady_clock::now();
std::cout << out[100] << "append string string_view= " << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << "[µs]" << std::endl;
// Add to array
prepareForBecnhmark();
std::array<const char*, iterations> cca;
begin = std::chrono::steady_clock::now();
for(size_t i=0; i<iterations; i++) {
cca[i] = tag_value[stack_tag.top()];
stack_tag.pop();
}
end = std::chrono::steady_clock::now();
std::cout << "fill array const char* = " << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << "[µs]" << std::endl;
prepareForBecnhmark();
std::array<std::string_view, iterations> ccsv;
begin = std::chrono::steady_clock::now();
for(size_t i=0; i<iterations; i++) {
ccsv[i] = tag_values[stack_tag.top()];
stack_tag.pop();
}
end = std::chrono::steady_clock::now();
std::cout << "fill array string_view = " << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << "[µs]" << std::endl;
std::cout << ccsv[ccsv.size()-1] << cca[cca.size()-1] << std::endl;
return 0;
}
我的机器上的结果是:
Aappend string const char* = 97[µs]
Aappend string string_view= 72[µs]
fill array const char* = 35[µs]
fill array string_view = 18[µs]
Godbolt编译器资源管理器URL:https ://godbolt.org/z/SMrevx
UPD:经过更精确的基准测试(500次运行300000次迭代)后的结果:
Caverage append string const char* = 2636[µs]
Caverage append string string_view= 2096[µs]
average fill array const char* = 526[µs]
average fill array string_view = 568[µs]
Godbolt网址:https://godbolt.org/z/aU7zL_
因此,第二种情况const char*
比预期的要快。答案中解释了第一种情况。
仅仅是因为std::string_view
您传递了长度,并且您不必在需要新字符串时插入空字符。每次char*
都必须搜索结尾,如果您想要一个子字符串,则可能必须复制,因为在子字符串的末尾需要一个空字符。
带有数组的becnhmark呢?不仅仅是复制到数组的指针。还是引用的字符串文字也被复制了?
@uni:如果您先对另一个进行基准测试,那么这些数字会发生变化吗?您的总基准测试过快了,以至于CPU可能随后才逐渐提升到最大加速。或者,第一个阵列比第二个阵列支付更多的页面错误成本。TL:DR:您的结果的一部分可能归因于幼稚的微基准测试方法。
@uni:或者它是真实的;我尝试将它们反转并在Godbolt上运行仍显示填充数组string_view = 19 [µs],而const char *为61 [µs]。 godbolt.org/z/MyUxqE。假设它们从不陷入调用的那一部分,那么这些循环基本上是等效的
operator delete
。(当然,字符串视图对象的宽度为16个字节,并使用movdqa
/ 获得副本movaps
)。IDK必须通过性能计数器在本地尝试,或者一步一步查看是否发生删除调用。增加迭代次数会减少一些差异比率:godbolt.org/z/jvM8Cr@PeterCordes我遵循了您的建议,并增加了迭代次数和基准运行次数,以使500次运行的时间平均。结果如下:迭代10000 50000 100000 200000 300000 string_view 17 87 183 368 588 const char * 17 88 177 353 526 delta 0 -1 6 15 62在这种情况下,差异是微小的,
const char*
并且现在更快@uni:这更有意义。
const char*
对于较大的迭代次数,可能会更快,因为它较小,并且这些较大的大小意味着更大的数组开始出现L1甚至L2高速缓存未命中。(现代x86-64可以像复制16字节对象一样快地复制8字节对象,尤其是当它们对齐时。)仍不知道是什么在没有重复循环的情况下降低了小尺寸。