Производительность qsort vs std :: sort? - PullRequest
68 голосов
/ 17 января 2011

По словам Скотта Мейерса, в своей книге Effective STL - пункт 46. Он утверждал, что std::sort примерно на 670% быстрее, чем std::qsort из-за факта встраивания.Я проверил себя и увидел, что qsort быстрее :(! Может ли кто-нибудь помочь мне объяснить это странное поведение?

#include <iostream>
#include <vector>
#include <algorithm>

#include <cstdlib>
#include <ctime>
#include <cstdio>

const size_t LARGE_SIZE = 100000;

struct rnd {
    int operator()() {
        return rand() % LARGE_SIZE;
    }
};

int comp( const void* a, const void* b ) {
    return ( *( int* )a - *( int* )b );
}

int main() {
    int ary[LARGE_SIZE];
    int ary_copy[LARGE_SIZE];
    // generate random data
    std::generate( ary, ary + LARGE_SIZE, rnd() );
    std::copy( ary, ary + LARGE_SIZE, ary_copy );
    // get time
    std::time_t start = std::clock();
    // perform quick sort C using function pointer
    std::qsort( ary, LARGE_SIZE, sizeof( int ), comp );
    std::cout << "C quick-sort time elapsed: " << static_cast<double>( clock() - start ) / CLOCKS_PER_SEC << "\n";
    // get time again
    start = std::clock();
    // perform quick sort C++ using function object
    std::sort( ary_copy, ary_copy + LARGE_SIZE );
    std::cout << "C++ quick-sort time elapsed: " << static_cast<double>( clock() - start ) / CLOCKS_PER_SEC << "\n";
}

Это мой результат:

C quick-sort time elapsed: 0.061
C++ quick-sort time elapsed: 0.086
Press any key to continue . . .

Обновление

Действующий STL, 3-е издание (2001)
Глава 7 Программирование с помощью STL
Пункт 46: Рассматривать функциональные объекты вместо функций в качестве параметров алгоритма.

С наилучшими пожеланиями,

Ответы [ 7 ]

94 голосов
/ 17 января 2011

std :: clock () не является жизнеспособным таймером.Вы должны использовать платформенный таймер с более высоким разрешением, такой как высокопроизводительный таймер Windows.Более того, способ, которым вы вызываете clock (), заключается в том, что сначала текст выводится на консоль, которая включена во время.Это определенно делает тест недействительным.Кроме того, убедитесь, что вы скомпилировали все оптимизации.

Наконец, я скопировал и вставил ваш код и получил 0,016 для qsort и 0,008 для std :: sort.

18 голосов
/ 25 марта 2011

Я удивлен, что никто не упоминает кэши.

В своем коде вы начинаете с прикосновения к ary и * ary_copy *, чтобы они находились в кэше во время qsort . Во время qsort , * ary_copy * может быть выселен. Во время std :: sort элементы должны были бы быть извлечены из памяти или большего (читай медленнее ) уровня кэша. Это, конечно, будет зависеть от размера вашего кэша.

Попробуйте отменить тест, т. Е. Начать с запуска std :: sort .

Как указали некоторые люди; увеличение массива сделает тест более справедливым. Причина в том, что большой массив с меньшей вероятностью помещается в кеш.

12 голосов
/ 17 января 2011

Два алгоритма сортировки без включенных оптимизаций должны иметь сопоставимую производительность. Причина, по которой C ++ sort имеет тенденцию заметно превосходить qsort, заключается в том, что компилятор может встроить выполняемые сравнения, поскольку у компилятора есть информация о типе того, какая функция используется для выполнения сравнения. Вы запускали эти тесты с включенной оптимизацией? Если нет, попробуйте включить его и снова запустить этот тест.

10 голосов
/ 17 января 2011

Другая причина того, что qsort может работать намного лучше, чем ожидалось, заключается в том, что новые компиляторы могут встроить и оптимизировать с помощью указателя функции.

Если заголовок C определяет встроенную реализацию qsort вместо реализации ее внутри библиотеки, и компилятор поддерживает косвенное встраивание функций, тогда qsort может быть таким же быстрым, как std :: sort.

4 голосов
/ 17 января 2011

На моей машине добавление мяса (создание массива 10 миллионов элементов и перемещение его в раздел данных) и компиляция с помощью

g++ -Wall -O2 -osortspeed sortspeed.cpp

В результате я получаю

C quick-sort time elapsed: 3.48
C++ quick-sort time elapsed: 1.26

BeТакже внимательно следите за современными «зелеными» процессорами, которые могут быть настроены на работу с переменной скоростью в зависимости от нагрузки системы.Когда бенчмаркинг такого рода поведения может свести вас с ума (на моей машине я установил два небольших скрипта normal и fast, которые я могу использовать при тестировании скорости).

3 голосов
/ 02 февраля 2016

Написание точных тестов сложно, поэтому давайте заставим Nonius сделать это для нас!Давайте проверим qsort, std::sort без вставки и std::sort со вставкой (по умолчанию) для вектора из миллиона случайных целых чисел.

// sort.cpp
#define NONIUS_RUNNER
#include <nonius.h++>
#include <random>
#include <algorithm>

// qsort
int comp(const void* a, const void* b) {
    const int arg1 = *static_cast<const int*>(a);
    const int arg2 = *static_cast<const int*>(b);

    // we can't simply return a - b, because that might under/overflow
    return (arg1 > arg2) - (arg1 < arg2);
}

// std::sort with no inlining
struct compare_noinline {
    __attribute__((noinline)) bool operator()(const int a, const int b) {
        return a < b;
    }
};

// std::sort with inlining
struct compare {
    // the compiler will automatically inline this
    bool operator()(const int a, const int b) {
        return a < b;
    }
};

std::vector<int> gen_random_vector(const size_t size) {

    std::random_device seed;
    std::default_random_engine engine{seed()};
    std::uniform_int_distribution<int> dist{std::numeric_limits<int>::min(), std::numeric_limits<int>::max()};

    std::vector<int> vec;
    for (size_t i = 0; i < size; i += 1) {
        const int rand_int = dist(engine);
        vec.push_back(rand_int);
    }

    return vec;
}

// generate a vector of a million random integers
constexpr size_t size = 1'000'000;
static const std::vector<int> rand_vec = gen_random_vector(size);

NONIUS_BENCHMARK("qsort", [](nonius::chronometer meter) {

    // Nonius does multiple runs of the benchmark, and each one needs a new
    // copy of the original vector, otherwise we'd just be sorting the same
    // one over and over
    const size_t runs = static_cast<size_t>(meter.runs());
    std::vector<std::vector<int>> vectors{runs};
    std::fill(vectors.begin(), vectors.end(), rand_vec);

    meter.measure([&](const size_t run) {

        std::vector<int>& current_vec = vectors[run];

        std::qsort(current_vec.data(), current_vec.size(), sizeof(int), comp);

        return current_vec;
    });
});

NONIUS_BENCHMARK("std::sort noinline", [](nonius::chronometer meter) {

    const size_t runs = static_cast<size_t>(meter.runs());
    std::vector<std::vector<int>> vectors{runs};
    std::fill(vectors.begin(), vectors.end(), rand_vec);

    meter.measure([&](const size_t run) {

        std::vector<int>& current_vec = vectors[run];

        std::sort(current_vec.begin(), current_vec.end(), compare_noinline{});

        return current_vec;

    });
});

NONIUS_BENCHMARK("std::sort inline", [](nonius::chronometer meter) {

    const size_t runs = static_cast<size_t>(meter.runs());
    std::vector<std::vector<int>> vectors{runs};
    std::fill(vectors.begin(), vectors.end(), rand_vec);

    meter.measure([&](const size_t run) {

        std::vector<int>& current_vec = vectors[run];

        std::sort(current_vec.begin(), current_vec.end(), compare{});

        return current_vec;

    });
});

Компиляция с помощью Apple Clang 7.3.0,

$ clang++ -std=c++14 -stdlib=libc++ -O3 -march=native sort.cpp -o sort
$ ./sort

и запустив его на моем i5 Macbook Air с частотой 1,7 ГГц, мы получаем

qsort                211 ms +/- 6 ms
std::sort noinline   127 ms +/- 5 ms
std::sort inline      87 ms +/- 4 ms

Так что std::sort без встраивания примерно в 1,7 раза быстрее, чем qsort (возможноразличные алгоритмы сортировки) и врезки ударов, которые примерно в 2,4 раза быстрее.Конечно, впечатляющее ускорение, но намного меньше, чем 670%.

0 голосов
/ 10 октября 2015

Не знаю, как std :: sort был реализован много лет назад.Но std :: sort может быть намного быстрее, потому что std :: sort является быстрой сортировкой с резервной сортировкой кучи.Heapsort - это линейно-динамический алгоритм, означающий, что если у вас есть дважды данные сортировки, время сортировки удваивается.На самом деле он более чем удваивается, потому что он не совсем линейный, но, тем не менее, qsort может быть квадратичным, поэтому для сортировки входных данных требуется больше экспоненциального времени.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...