C++: Самый быстрый способ сортировки списка чисел и их индекса

У меня есть вопрос, который может показаться очень простым, но он находится в контексте, где "каждый тик процессора" (это часть большего алгоритма, который будет использоваться на суперкомпьютерах).

проблема довольно проста: каков самый быстрый способ сортировки списка беззнаковых длинных длинных чисел int и их исходных индексов ? (В начале беззнаковые длинные длинные числа int находятся в совершенно случайном порядке.)

Example :
Before
Numbers: 32 91 11 72
Indexes: 0 1 2 3
After
Numbers: 11 32 72 91
Indexes: 2 0 3 1 

"самым быстрым способом", я имею в виду: какой алгоритм использовать: std:: sort, C qsort или другой алгоритм сортировки, доступный в интернете ? Какой контейнер использовать (C array, std::vector, std:: map...) ? Как сортировать индексы одновременно (используйте structures, std::pair, std:: map...) ?

большое спасибо !

EDIT: сколько элементов для сортировки ? - >обычно 4Go чисел

8 ответов


очевидной отправной точкой будет структура с operator< определено:

struct data { 
    unsigned long long int number;
    size_t index;
};

struct by_number { 
    bool operator()(data const &left, data const &right) { 
        return left.number < right.number;
    }
};

...и std:: вектор для хранения данных:

 std::vector<data> items;

и std::sort для сортировки:

 std::sort(items.begin(), items.end(), by_number());

простой факт заключается в том, что обычные контейнеры (и такие) достаточно эффективны, что их использование не делает ваш код существенно менее эффективным. Вы может можно сделать лучше, написав какую-то часть по-другому, но вы можете примерно так же легко сделать хуже. Начните с твердого и читаемого, и проверьте -- не (попытайтесь) оптимизировать преждевременно.

Edit: конечно, в C++11, вы можете использовать лямбда-выражение:

std::sort(items.begin(), items.end(), 
          [](data const &a, data const &b) { return a.number < b.number; });

это, как правило, немного удобнее писать. Читаемость зависит-для чего-то простого, как это, я бы сказал sort ... by_number довольно читабельно, но это зависит (сильно) от имени, которое вы даете оператору сравнения. Lambda упрощает фактические критерии сортировки найдите, поэтому вам не нужно тщательно выбирать имя, чтобы код был читаемым.


std::pair и std::sort идеально подходит для ваших требований: если вы поместите значение в pair.first и индекс в pair.second, вы можете просто позвонить sort на вектор pairs, Вот так:

// This is your original data. It does not need to be in a vector
vector<long> orig;
orig.push_back(10);
orig.push_back(3);
orig.push_back(6);
orig.push_back(11);
orig.push_back(2);
orig.push_back(19);
orig.push_back(7);
// This is a vector of {value,index} pairs
vector<pair<long,size_t> > vp;
vp.reserve(orig.size());
for (size_t i = 0 ; i != orig.size() ; i++) {
    vp.push_back(make_pair(orig[i], i));
}
// Sorting will put lower values ahead of larger ones,
// resolving ties using the original index
sort(vp.begin(), vp.end());
for (size_t i = 0 ; i != vp.size() ; i++) {
    cout << vp[i].first << " " << vp[i].second << endl;
}

std::sort оказался быстрее, чем старый qsort из-за отсутствия косвенности и возможности встраивания критических операций.

реализации std::sort могут быть оптимизированы, и их трудно победить, но не невозможно. Если ваши данные фиксированной длины и короткие Вы можете найти Radix сортировать будет быстрее. Timsort является относительно новым и принес хорошие результаты для Python.

вы можете сохранить индекс массив отдельно от массива значений, но я думаю, что дополнительный уровень косвенности окажется убийцей скорости. Лучше держать их вместе в строении или std::pair.

как всегда с любым критическим приложением скорости, вы должны попробовать некоторые фактические реализации и сравнить их, чтобы точно знать, что быстрее.


Это может стоит разделить числа и индексы, а затем просто сортировка индексов, например:

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

void PrintElements(const std::vector<unsigned long long>& numbers, const std::vector<size_t>& indexes) {

    std::cout << "\tNumbers:";
    for (auto i = indexes.begin(); i != indexes.end(); ++i)
        std::cout << '\t' << numbers[*i];
    std::cout << std::endl;

    std::cout << "\tIndexes:";
    for (auto i = indexes.begin(); i != indexes.end(); ++i)
        std::cout << '\t' << *i;
    std::cout << std::endl;

}

int main() {

    std::vector<unsigned long long> numbers;
    std::vector<size_t> indexes;

    numbers.reserve(4); // An overkill for this few elements, but important for billions.
    numbers.push_back(32);
    numbers.push_back(91);
    numbers.push_back(11);
    numbers.push_back(72);

    indexes.reserve(numbers.capacity());
    indexes.push_back(0);
    indexes.push_back(1);
    indexes.push_back(2);
    indexes.push_back(3);

    std::cout << "BEFORE:" << std::endl;
    PrintElements(numbers, indexes);

    std::sort(
        indexes.begin(),
        indexes.end(),
        [&numbers](size_t i1, size_t i2) {
            return numbers[i1] < numbers[i2];
        }
    );

    std::cout << "AFTER:" << std::endl;
    PrintElements(numbers, indexes);

    return EXIT_SUCCESS;

}

печатается:

BEFORE:
        Numbers:        32      91      11      72
        Indexes:        0       1       2       3
AFTER:
        Numbers:        11      32      72      91
        Indexes:        2       0       3       1

идея заключается в том, что сортируемые элементы малы и поэтому быстро перемещаются во время сортировки. Однако на современных процессорах влияние косвенного доступа к numbers кэширование может испортить эти выгоды, поэтому я рекомендую бенчмаркинг реалистичных объемов данных перед принятием окончательного решения об использовании он.


struct SomeValue
{
    unsigned long long val;
    size_t index;
    bool operator<(const SomeValue& rhs)const
    { 
       return val < rhs.val;
    }
}

 #include <algorithm>
 std::vector<SomeValue> somevec;
 //fill it...
 std::sort(somevec.begin(),somevec.end());

использовать std::vector и std::sort. Это должно обеспечить самый быстрый метод сортировки. Чтобы найти исходный индекс, создайте структуру.

struct A {
    int num;
    int index;
}

затем сделайте свой собственный предикат сравнения для сортировки, который сравнивает num в структуре.

struct Predicate {
    bool operator()(const A first, const A second) {
        return first.num < second.num;
    }
}

std::sort(vec.begin(), vec.end(), Predicate())


Это будет использоваться на суперкомпьютерах?

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


вы можете найти этой будет интересно читать. Я бы начал с сортировки STL, а затем попытался бы улучшить ее, если бы мог. Я не уверен, что у вас есть доступ к компилятору c++11 (например, gcc4.7) на этом суперкомпьютере, но я бы предложил, чтобы std::sort с std::futures и std::threads получили бы вас довольно много пути туда в отношении распараллеливания проблемы ремонтопригодным способом.

здесь еще вопрос что сравнивает std:: сортировка с помощью qsort.

наконец,в этой статье в доктор Доббс, что сравнить производительность параллельных алгоритмов.