Как заставить структуру C++ работать быстро с помощью функции Boost.Sort string_sort

Я вижу очень большую разницу в производительности при сортировке объектов std :: string по сравнению с парами указатель + длина, а std :: string работает намного быстрее

Я много занимаюсь сортировкой в ​​своем приложении и обнаружил, что узким местом производительности является сортировка больших массивов строк. Я знаю два хороших способа сделать такую ​​сортировку - использовать функции std :: sort и Boost.sort. Я сортирую части большого файла, используя указатели и информацию о длине строки.

Я попытался сравнить свою производительность с сортировкой объектов std :: string, и мои простые структуры указатель + длина намного медленнее. Не представляю - почему? Размер sizeof (std :: string) равен 32, а sizeof (my_struct) - 16 байтов. Оба они используют внутреннюю функцию :: memcmp для сравнения

Чтобы описать проблему, я сделал небольшое приложение, которое может показать разницу в производительности между сортировкой std :: string и объектами моей структуры:

#include <iostream>
#include <vector>
#include <chrono>
#include <algorithm>
#include <boost/sort/sort.hpp>

using namespace std;

#define LEN   4
#define COUNT 1000000
#define USE_BOOST_SORT

// simple structure that i think to be identical to std::string
struct test
{
    inline bool operator<(const test& a) const  noexcept
    {
        size_t minsize = len < a.len ? len : a.len;
        int res = ::memcmp(ptr, a.ptr, minsize);
        return res < 0 || (res == 0 && len < a.len);
    }
    inline size_t size() const noexcept
    {return len;}
    inline const char* data() const noexcept
    {return ptr;}
    inline const char& operator[](size_t index) const noexcept
    {return ptr[index];}

    const char* ptr = nullptr;
    size_t len = 0;
};


int main(int,char**)
{
    // stage 1.a - initialize string sorting data with randoms
    vector<string> strings;
    strings.resize(COUNT);
    int counter = 0;
    for (string& s : strings)
    {
        s.resize(LEN);
        for (char& c : s)
            c = (counter++) % 256;
    }
    // stage 1.b - make the copy of data to get deterministic results and initialize tests array
    vector<string> strings_copy = strings;
    vector<test> tests;
    tests.resize(strings_copy.size());
    for (size_t i = 0; i < tests.size(); ++i)
    {
        tests[i].ptr = strings_copy[i].data();
        tests[i].len = strings_copy[i].size();
    }

    //  stage 2. sorting
    for (size_t i = 0; i < 10; ++i)
    {
        // make the copy of data to keep order unchanged
        vector<string> to_be_sorted = strings;
        chrono::high_resolution_clock::time_point t1 = chrono::high_resolution_clock::now();
#ifdef USE_BOOST_SORT
        boost::sort::spreadsort::string_sort(to_be_sorted.begin(), to_be_sorted.end());
#else
        sort(to_be_sorted.begin(), to_be_sorted.end());
#endif
        chrono::high_resolution_clock::time_point t2 = chrono::high_resolution_clock::now();
        to_be_sorted.clear();
        // make the copy of tests for sorting
        vector<test> tests_for_sort = tests;
        chrono::high_resolution_clock::time_point t3 = chrono::high_resolution_clock::now();
#ifdef USE_BOOST_SORT
        boost::sort::spreadsort::string_sort(tests_for_sort.begin(), tests_for_sort.end());
#else
        sort(tests_for_sort.begin(), tests_for_sort.end());
#endif
        chrono::high_resolution_clock::time_point t4 = chrono::high_resolution_clock::now();

        cout << "String sort time: " << chrono::duration_cast<chrono::milliseconds>(t2-t1).count()
             << " msec" << endl;
        cout << "Test sort time: " << chrono::duration_cast<chrono::milliseconds>(t4-t3).count()
             << " msec" << endl;
    }
}

Вот тот же код в IDE One

И вывод программы

String sort time: 57 msec
Test sort time: 134 msec
String sort time: 51 msec
Test sort time: 130 msec
String sort time: 49 msec
Test sort time: 131 msec
String sort time: 51 msec
Test sort time: 130 msec
String sort time: 49 msec
Test sort time: 129 msec
String sort time: 51 msec
Test sort time: 130 msec
String sort time: 49 msec
Test sort time: 130 msec
String sort time: 51 msec
Test sort time: 132 msec
String sort time: 50 msec
Test sort time: 130 msec
String sort time: 50 msec
Test sort time: 131 msec

Проблема останется, если я буду использовать std :: sort вместо Boost.Sort.

НО, если я попытаюсь изменить параметр LEN на 16 и больше, мои структуры начнут сортировку с такая же скорость.

Итак, мой вопрос - как я могу улучшить свой код, чтобы он сортировался так же быстро, как объекты std :: string при использовании небольших строк?

Мой основной компилятор - MSVC 2015 update 3 / Win64. IDE one использует GCC внутри, поэтому это может быть не проблема компилятора

Еще один вариант при использовании Boost.Sort - создать объекты «Functor», которые будут обертывать мою структуру и реализовывать требуемый интерфейс. Но так работает еще медленнее, чем сейчас

Использование типа данных unsigned char вместо char ничего не меняет

Перечислите параметры оптимизации, которые вы использовали при сборке программы, в дополнение к используемым компиляторам.

PaulMcKenzie 09.04.2018 01:56
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
1
329
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проверьте, использует ли ваша библиотека SSO (оптимизация малых строк) для строковой реализации.

Если это так, то увеличение количества ссылок может легко объяснить разницу. Это также объясняет, что разница исчезает, когда строки становятся слишком большими, чтобы использовать SSO.

Подтверждение концепции: killSSO

Запуск этого теста со строкой killSSO с комментариями: Live On Coliru

String std::sort time:  193.334 msec
View std::sort time:    419.458 msec
String boost sort time: 63.2888 msec
View boost sort time:   154.191 msec
...

Раскомментируя строку, std::for_each(c.begin(), c.end(), kill_SSO{}); выводит: Live On Coliru

String std::sort time:  548.243 msec
View std::sort time:    422.26 msec
String boost sort time: 156.891 msec
View boost sort time:   154.163 msec

Тесты Nonius

Используя Фреймворк для микро-тестов Nonius, мы получаем:

#include <algorithm>
#include <boost/sort/sort.hpp>
#include <boost/utility/string_view.hpp>
#include <vector>
#define NONIUS_RUNNER
#include <nonius/benchmark.h++>
#include <nonius/main.h++>

extern std::vector<std::string> const testdata;

struct kill_SSO {
    void operator()(std::string& s) const { s.reserve(20); }
    template <typename Other> void operator()(Other&&) const   {} // not needed
};

struct std_sort          { template <typename It> static void run(It b, It e) { std::sort(b,                            e); } };
struct boost_spread_sort { template <typename It> static void run(It b, It e) { boost::sort::spreadsort::string_sort(b, e); } };

template <typename C, typename Sort, bool Kill = false> void bench(nonius::chronometer& cm) {
    C c {testdata.begin(), testdata.end()};
    if (Kill) std::for_each(c.begin(), c.end(), kill_SSO{});

    cm.measure([&]{ Sort::run(c.begin(), c.end()); });
}

using view = boost::string_view; // std::string_view, boost::string_ref, gsl::span etc.
NONIUS_BENCHMARK("SSO std::sort time:  ",    [](nonius::chronometer cm) { bench<std::vector<std::string>, std_sort, false>(cm); })
NONIUS_BENCHMARK("SSO boost sort time: ",    [](nonius::chronometer cm) { bench<std::vector<std::string>, boost_spread_sort, false>(cm); })
NONIUS_BENCHMARK("String std::sort time:  ", [](nonius::chronometer cm) { bench<std::vector<std::string>, std_sort, true>(cm); })
NONIUS_BENCHMARK("String boost sort time: ", [](nonius::chronometer cm) { bench<std::vector<std::string>, boost_spread_sort, true>(cm); })
NONIUS_BENCHMARK("View std::sort time:    ", [](nonius::chronometer cm) { bench<std::vector<view>       , std_sort>(cm); })
NONIUS_BENCHMARK("View boost sort time:   ", [](nonius::chronometer cm) { bench<std::vector<view>       , boost_spread_sort>(cm); })

std::vector<std::string> const testdata = [] {
    std::vector<std::string> generated(1000000);
    auto genchar = [count=0]() mutable { return static_cast<char>(static_cast<uint8_t>(count++ % 256)); };
    std::generate(generated.begin(), generated.end(), [&] { return std::string {genchar(), genchar(), genchar(), genchar()}; });
    return generated;
}();

Результаты Interactive On Plot.ly

Добавлен тестовый тест, показывающий, как убить SO. (Также показано, как использовать std::string_view, boost::string_view, boost::string_ref или gsl::span<> вместо катания собственного «тестового» типа).

sehe 09.04.2018 11:58
Нони использовал это, интерактивные графики тестов: plot.ly/~sehe5d88/1
sehe 09.04.2018 12:21

удивительное объяснение нетривиальной возможности оптимизации C++. Спасибо много!

Evgeniy 09.04.2018 17:44

Другие вопросы по теме