Почему std :: string_view быстрее чем const char *? - PullRequest
3 голосов
/ 01 февраля 2020

Или я измеряю что-то еще?

В этом коде у меня есть стек тегов (integers). Каждый тег имеет строковое представление (const char* или std::string_view). В стеке l oop значения преобразуются в соответствующие строковые значения. Эти значения добавляются к предварительно выделенной строке или присваиваются элементу массива.

Результаты показывают, что версия с 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]

URL-адрес проводника компилятора Godbolt: 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]

URL-адрес Годболта: https://godbolt.org/z/aU7zL_

Итак, во втором случае const char* равно быстрее, чем ожидалось. И первый случай был объяснен в ответах.

Ответы [ 3 ]

8 голосов
/ 01 февраля 2020

Просто потому, что с std::string_view вы передали длину, и вам не нужно вставлять нулевой символ, когда вы хотите новую строку. char* должен искать конец каждый раз , и если вы хотите подстроку, вам, вероятно, придется копировать, так как вам понадобится нулевой символ в конце подстроки.

4 голосов
/ 01 февраля 2020

std::string_view для практических целей сводится к:

{
  const char* __data_;
  size_t __size_;
}

Стандарт фактически указывает на se c. 24.4.2 что это указатель и размер. Он также указывает, как определенные операции работают с представлением строки. В частности, всякий раз, когда вы взаимодействуете с std::string, вы вызываете перегрузку, которая также принимает размер в качестве входных данных. Следовательно, когда вы вызываете append, это сводится к двум различным вызовам: str.append(sv) переводится в str.append(sv.data(), sv.size()).

Существенная разница в том, что теперь вы знаете размер строки после append, что означает, что вы также знаете, придется ли вам перераспределять ваш внутренний буфер, и насколько большой вы должны сделать это. Если вы не знаете размер заранее, вы можете начать копирование, но std::string дает надежную гарантию для append, поэтому для практических целей большинство библиотек, вероятно, предварительно вычисляют длину и требуемый буфер, хотя технически было бы также возможно просто вспомнить старый размер и стереть все после, если вы не завершите sh успешно (сомневаюсь, что кто-нибудь это сделает, хотя это может быть локальная оптимизация для строк, поскольку уничтожение тривиально).

3 голосов
/ 01 февраля 2020

Это может быть связано с тем, что string_view имеет размер строкового значения. "Const char *" не имеет информации о размере и должен его определять.

...