Удивительный результат теста

После просмотра Тита Винтерса «Живи головой» В разговоре, где он упоминает, что StrCat () является одной из любимых функций людей, я решил попробовать реализовать что-то похожее, чтобы посмотреть, смогу ли я превзойти std :: string :: append (или оператор +, который, как я полагал, использует внутреннее добавление) в терминах производительности во время выполнения. Я рассуждал, что функция strcat (), реализованная как шаблон с переменным числом аргументов, сможет определить объединенный размер всех своих строковых аргументов и сделать одно выделение для сохранения конечного результата, вместо того, чтобы постоянно перераспределять в случае оператор +, который не знает всеобъемлющего контекста, в котором он вызывается.

Однако, когда я сравнил свою пользовательскую реализацию с оператором + на быстро скамья, Я обнаружил, что моя реализация strcat () примерно в 4 раза медленнее, чем operator + в последних версиях clang и gcc, скомпилированных с -std=c++17 -O3, Я включил код быстрой проверки ниже для справки.

Кто-нибудь знает, что может быть причиной замедления здесь?

#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);

4

Решение

Это из-за передачи аргументов по значению.

Я изменил код, чтобы вместо этого использовать складные выражения (что выглядит намного чище)
и избавился от ненужных копий (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;
}

Это решение бьет append примерно на 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;
}
6

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]