经过一些性能实验后,似乎使用char16_t数组有时可以将性能提升到40-50%,但似乎在没有任何复制和分配的情况下使用std :: u16string应该和C数组一样快。然而,基准却显示出相反的结果。
以下是我为基准编写的代码(它使用Google Benchmark lib):
#include "benchmark/benchmark.h"
#include <string>
static std::u16string str;
static char16_t *str2;
static void BM_Strings(benchmark::State &state) {
while (state.KeepRunning()) {
for (size_t i = 0; i < str.size(); i++){
benchmark::DoNotOptimize(str[i]);
}
}
}
static void BM_CharArray(benchmark::State &state) {
while (state.KeepRunning()) {
for (size_t i = 0; i < str.size(); i++){
benchmark::DoNotOptimize(str2[i]);
}
}
}
BENCHMARK(BM_Strings);
BENCHMARK(BM_CharArray);
static void init(){
str = u"Various applications of randomness have led to the development of several different methods ";
str2 = (char16_t *) str.c_str();
}
int main(int argc, char** argv) {
init();
::benchmark::Initialize(&argc, argv);
::benchmark::RunSpecifiedBenchmarks();
}
它显示以下结果:
Run on (8 X 2200 MHz CPU s)
2017-07-11 23:05:57
Benchmark Time CPU Iterations
---------------------------------------------------
BM_Strings 1832 ns 1830 ns 365938
BM_CharArray 928 ns 926 ns 712577
我在mac上使用了clang(Apple LLVM版本8.1.0(clang-802.0.42))。随着优化的开启,差距变小但仍然明显:
Benchmark Time CPU Iterations
---------------------------------------------------
BM_Strings 242 ns 241 ns 2906615
BM_CharArray 161 ns 161 ns 4552165
有人可以解释这里发生了什么以及为什么会有区别?
更新(混合订单并添加少量热身步骤):
Benchmark Time CPU Iterations
---------------------------------------------------
BM_CharArray 670 ns 665 ns 903168
BM_Strings 856 ns 854 ns 817776
BM_CharArray 166 ns 166 ns 4369997
BM_Strings 225 ns 225 ns 3149521
还包括我使用的编译标志:
/usr/bin/clang++ -I{some includes here} -O3 -std=c++14 -stdlib=libc++ -Wall -Wextra -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk -O3 -fsanitize=address -Werror -o CMakeFiles/BenchmarkString.dir/BenchmarkString.cpp.o -c test/benchmarks/BenchmarkString.cpp
答案 0 :(得分:27)
由于libc ++实现小字符串优化的方式,在每个取消引用时,它需要检查字符串内容是存储在字符串对象本身还是存储在堆上。由于索引包含在benchmark::DoNotOptimize
中,因此每次访问字符时都需要执行此检查。当通过指针访问字符串数据时,数据总是在外部,因此不需要检查。
答案 1 :(得分:1)
有趣的是,我无法重现您的结果。我几乎察觉不到两者之间的差异。
我使用的(不完整)代码如下所示:
hol::StdTimer timer;
using index_type = std::size_t;
index_type const N = 100'000'000;
index_type const SIZE = 1024;
static std::u16string s16;
static char16_t const* p16;
int main(int, char** argv)
{
std::generate_n(std::back_inserter(s16), SIZE,
[]{ return (char)hol::random_number((int)'A', (int)'Z'); });
p16 = s16.c_str();
unsigned sum;
{
sum = 0;
timer.start();
for(index_type n = 0; n < N; ++n)
for(index_type i = 0; i < SIZE; ++i)
sum += s16[i];
timer.stop();
RESULT("string", sum, timer);
}
{
sum = 0;
timer.start();
for(std::size_t n = 0; n < N; ++n)
for(std::size_t i = 0; i < SIZE; ++i)
sum += p16[i];
timer.stop();
RESULT("array ", sum, timer);
}
}
<强>输出:强>
string: (670240768) 17.575232 secs
array : (670240768) 17.546145 secs
<强>编译器:强>
GCC 7.1
g++ -std=c++14 -march=native -O3 -D NDEBUG
答案 2 :(得分:0)
在纯char16_t中,您可以直接访问数组,而在字符串中,您重载了operator []
reference
operator[](size_type __pos)
{
#ifdef _GLIBCXX_DEBUG_PEDANTIC
__glibcxx_check_subscript(__pos);
#else
// as an extension v3 allows s[s.size()] when s is non-const.
_GLIBCXX_DEBUG_VERIFY(__pos <= this->size(),
_M_message(__gnu_debug::__msg_subscript_oob)
._M_sequence(*this, "this")
._M_integer(__pos, "__pos")
._M_integer(this->size(), "size"));
#endif
return _M_base()[__pos];
}
和_M_base()是:
_Base& _M_base() { return *this; }
现在,我的猜测是:
或