Почему std:: u16string медленнее, чем массив char16_t?
После некоторых экспериментов с производительностью казалось, что использование массивов 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
Я использую clang (Apple LLVM version 8.1.0 (clang-802.0.42)) на mac. При включенной оптимизации зазор меньше, но все же заметен:
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
Ответы
Ответ 1
Из-за того, как libС++ реализует небольшую оптимизацию строк, при каждом разыменовании необходимо проверить, хранится ли содержимое строки в самом объекте строки или в куче. Поскольку индексирование завернуто в benchmark::DoNotOptimize
, он должен выполнять эту проверку каждый раз, когда к персонажу обращаются. При доступе к строковым данным через указатель данные всегда являются внешними и поэтому не требуют проверки.
Ответ 2
В чистом char16_t вы напрямую обращаетесь к массиву, а в строке вы перегружаете оператор []
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; }
Теперь мои догадки заключаются в следующем:
- _M_base() может не получиться вложенным, и чем вы получите удар производительности, потому что каждое чтение требует дополнительной операции для чтения адреса функции.
или
- Выполняется одна из этих проверок индексов.
Ответ 3
Интересно, что я не могу воспроизвести ваши результаты. Я едва могу обнаружить разницу между ними.
Используемый здесь код (неполный) показан ниже:
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