2009-05-01 14 views
24

Ambos deberían ejecutarse en O (n log n), pero en general el ordenamiento es más rápido que stable_sort. ¿Qué tan grande es la brecha de rendimiento en la práctica? ¿Tienes algo de experiencia sobre eso?¿Cuán grande es la brecha de rendimiento entre std :: sort y std :: stable_sort en la práctica?

Quiero ordenar una gran cantidad de estructuras que tienen un tamaño de aproximadamente 20 bytes. La estabilidad del resultado sería agradable en mi caso, pero no es obligatorio. En el momento en que el contenedor subyacente es una matriz simple, quizás podría cambiarse a std :: deque más adelante.

Respuesta

15

Hay buenas respuestas que compararon teóricamente los algoritmos. Calculé std::sort y std::stable_sort con google/benchmark por curiosidad.

Es útil señalar con anticipación que;

  • máquina de Benchmark tiene 1 X 2500 MHz CPU y 1 GB RAM
  • OS Benchmark Arch Linux 2015.08 x86-64
  • Benchmark compilado con g++ 5.3.0 y clang++ 3.7.0 (-std=c++11, -O3 y -pthread)
  • BM_Base* referencia trata de medir el tiempo de poblar std::vector<>. Ese tiempo se debe restar de los resultados de clasificación para una mejor comparación.

Primer tipo de referencia std::vector<int> con 512k tamaño.

[ g++ ]# benchmark_sorts --benchmark_repetitions=10 
Run on (1 X 2500 MHz CPU) 
2016-01-08 01:37:43 
Benchmark       Time(ns) CPU(ns) Iterations 
---------------------------------------------------------------- 
... 
BM_BaseInt/512k_mean    24730499 24726189   28 
BM_BaseInt/512k_stddev    293107  310668   0 
... 
BM_SortInt/512k_mean    70967679 70799990   10 
BM_SortInt/512k_stddev    1300811 1301295   0 
... 
BM_StableSortInt/512k_mean  73487904 73481467   9 
BM_StableSortInt/512k_stddev  979966  925172   0 
[ clang++ ]# benchmark_sorts --benchmark_repetitions=10 
Run on (1 X 2500 MHz CPU) 
2016-01-08 01:39:07 
Benchmark       Time(ns) CPU(ns) Iterations 
---------------------------------------------------------------- 
... 
BM_BaseInt/512k_mean    26198558 26197526   27 
BM_BaseInt/512k_stddev    320971  348314   0 
... 
BM_SortInt/512k_mean    70648019 70666660   10 
BM_SortInt/512k_stddev    2030727 2033062   0 
... 
BM_StableSortInt/512k_mean  82004375 81999989   9 
BM_StableSortInt/512k_stddev  197309  181453   0 

segundo punto de referencia ordena std::vector<S> con 512k tamaño (sizeof(Struct S) = 20).

[ g++ ]# benchmark_sorts --benchmark_repetitions=10 
Run on (1 X 2500 MHz CPU) 
2016-01-08 01:49:32 
Benchmark       Time(ns) CPU(ns) Iterations 
---------------------------------------------------------------- 
... 
BM_BaseStruct/512k_mean   26485063 26410254   26 
BM_BaseStruct/512k_stddev   270355  128200   0 
... 
BM_SortStruct/512k_mean   81844178 81833325   8 
BM_SortStruct/512k_stddev   240868  204088   0 
... 
BM_StableSortStruct/512k_mean 106945879 106857114   7 
BM_StableSortStruct/512k_stddev 10446119 10341548   0 
[ clang++ ]# benchmark_sorts --benchmark_repetitions=10 
Run on (1 X 2500 MHz CPU) 
2016-01-08 01:53:01 
Benchmark       Time(ns) CPU(ns) Iterations 
---------------------------------------------------------------- 
... 
BM_BaseStruct/512k_mean   27327329 27280000   25 
BM_BaseStruct/512k_stddev   488318  333059   0 
... 
BM_SortStruct/512k_mean   78611207 78407400   9 
BM_SortStruct/512k_stddev   690207  372230   0 
... 
BM_StableSortStruct/512k_mean 109477231 109333325   8 
BM_StableSortStruct/512k_stddev 11697084 11506626   0 

Cualquier persona que le gusta realizar la prueba, aquí está el código,

#include <vector> 
#include <random> 
#include <algorithm> 

#include "benchmark/benchmark_api.h" 

#define SIZE 1024 << 9 

static void BM_BaseInt(benchmark::State &state) { 
    std::random_device rd; 
    std::mt19937 mt(rd()); 
    std::uniform_int_distribution<int> dist; 

    while (state.KeepRunning()) { 
    std::vector<int> v; 
    v.reserve(state.range_x()); 
    for (int i = 0; i < state.range_x(); i++) { 
     v.push_back(dist(mt)); 
    } 
    } 
} 
BENCHMARK(BM_BaseInt)->Arg(SIZE); 

static void BM_SortInt(benchmark::State &state) { 
    std::random_device rd; 
    std::mt19937 mt(rd()); 
    std::uniform_int_distribution<int> dist; 

    while (state.KeepRunning()) { 
    std::vector<int> v; 
    v.reserve(state.range_x()); 
    for (int i = 0; i < state.range_x(); i++) { 
     v.push_back(dist(mt)); 
    } 

    std::sort(v.begin(), v.end()); 
    } 
} 
BENCHMARK(BM_SortInt)->Arg(SIZE); 

static void BM_StableSortInt(benchmark::State &state) { 
    std::random_device rd; 
    std::mt19937 mt(rd()); 
    std::uniform_int_distribution<int> dist; 

    while (state.KeepRunning()) { 
    std::vector<int> v; 
    v.reserve(state.range_x()); 
    for (int i = 0; i < state.range_x(); i++) { 
     v.push_back(dist(mt)); 
    } 

    std::stable_sort(v.begin(), v.end()); 
    } 
} 
BENCHMARK(BM_StableSortInt)->Arg(SIZE); 


struct S { 
    int key; 
    int arr[4]; 
}; 

static void BM_BaseStruct(benchmark::State &state) { 
    std::random_device rd; 
    std::mt19937 mt(rd()); 
    std::uniform_int_distribution<int> dist; 

    while (state.KeepRunning()) { 
    std::vector<S> v; 
    v.reserve(state.range_x()); 
    for (int i = 0; i < state.range_x(); i++) { 
     v.push_back({dist(mt)}); 
    } 
    } 
} 
BENCHMARK(BM_BaseStruct)->Arg(SIZE); 

static void BM_SortStruct(benchmark::State &state) { 
    std::random_device rd; 
    std::mt19937 mt(rd()); 
    std::uniform_int_distribution<int> dist; 

    while (state.KeepRunning()) { 
    std::vector<S> v; 
    v.reserve(state.range_x()); 
    for (int i = 0; i < state.range_x(); i++) { 
     v.push_back({dist(mt)}); 
    } 

    std::sort(v.begin(), v.end(), 
       [](const S &a, const S &b) { return a.key < b.key; }); 
    } 
} 
BENCHMARK(BM_SortStruct)->Arg(SIZE); 

static void BM_StableSortStruct(benchmark::State &state) { 
    std::random_device rd; 
    std::mt19937 mt(rd()); 
    std::uniform_int_distribution<int> dist; 

    while (state.KeepRunning()) { 
    std::vector<S> v; 
    v.reserve(state.range_x()); 
    for (int i = 0; i < state.range_x(); i++) { 
     v.push_back({dist(mt)}); 
    } 

    std::stable_sort(v.begin(), v.end(), 
        [](const S &a, const S &b) { return a.key < b.key; }); 
    } 
} 
BENCHMARK(BM_StableSortStruct)->Arg(SIZE); 


BENCHMARK_MAIN(); 
+9

+1 para seguir adelante y realizar el benchmark. Tu trabajo se ve bien. Sin embargo, los resultados serían más accesibles si los resumiera en una forma más fácil de digerir, como dibujar un diagrama pequeño. – 5gon12eder

9

Suficientemente grande para garantizar una función separada que hace una clasificación estable y no tiene std::sort() hágalo de forma transparente.

+0

HAHAH. Bien, quizás no sea la respuesta más detallada desde el punto de vista técnico, pero sin duda es mi favorita. –

15

std::stable_sort realiza comparaciones NlogN cuando hay suficiente memoria disponible. Cuando no hay suficiente memoria disponible, se degrada a N ((logN)^2) comparaciones. Por lo tanto, es más o menos de la misma eficacia que std::sort (que realiza comparaciones O (NlogN) tanto en el promedio como en el peor de los casos) cuando la memoria está disponible.

Para los interesados, sort() usa un introsort (quicksort que cambia a heapsort cuando la recursión alcanza una cierta profundidad) y stable_sort() usa un merge sort.

+6

LogN^2 usualmente no significa log (N^2) pero (log N)^2, y especialmente si ocurre en notación de O grande.Esto sucede comúnmente con los algoritmos que toman los pasos O (N log N) con el trabajo O (log N) por paso y viceversa. Por lo tanto, no, no es una constante 2. – MSalters

+0

Tiene razón, cambió mi respuesta considerablemente como resultado. – marcog

+0

Dice 'std :: sort (que realiza comparaciones O (NlogN) tanto en el promedio como en el peor de los casos)'. Pero el enlace a std :: sort dice: O (N · log (N)) en todos los casos solo desde C++ 11. Antes de C++ 11 no había ningún requerimiento O (Nlog (N)) para el peor de los casos. –

2

¿Cuán grande es la brecha de rendimiento en la práctica ? ¿Tiene alguna experiencia al respecto?

Sí, pero no salió como usted esperaría.

Tomé una implementación C de la Transformación Burrows-Wheeler y la cifré. Resultó mucho más lento que el código C (aunque el código era más limpio). Así que puse la instrumentación de sincronización allí y parecía que el qsort estaba funcionando más rápido que el std :: sort. Esto se estaba ejecutando en VC6. Luego se volvió a compilar con stable_sort y las pruebas se ejecutaron más rápido que la versión C. Otras optimizaciones lograron impulsar la versión C++ ~ 25% más rápido que la versión C. Creo que era posible obtener más velocidad, pero la claridad del código estaba desapareciendo.

+0

Básicamente lo que estoy tratando de decir es probar en ambos sentidos. –

9

A veces std :: stable_sort() es necesario porque mantiene el orden de los elementos que son iguales.

Y el consejo convencional es que, si mantener el orden no es importante, debe usar std :: sort() en su lugar.

Sin embargo, depende de su contexto. Hay una gran cantidad de datos que se clasifican mejor con un orden estable incluso si no necesita mantener el orden:

Quicksort se convierte rápidamente en el peor de los casos si los datos tienen puntos de pivote consistentemente pobres.

El Burrows-Wheeler Transform es un algoritmo utilizado como parte de la compresión de datos, p. bzip2. Requiere clasificar todas las rotaciones del texto. Para la mayoría de los datos de texto, el tipo de combinación (como suele usar std :: stable_sort()) es sustancialmente más rápido que el método de ordenación rápida (como suele ser utilizado por std :: sort()).

bbb es una implementación de BWT que señala las ventajas de std :: stable_sort() sobre sort() para esta aplicación.

1

Si está ordenando una gran cantidad de estructuras, la velocidad IO de su memoria/disco comienza a ser más importante que el tiempo de ejecución asintótico. Además, el uso de la memoria también debe tenerse en cuenta.

Intenté std :: stable_sort en 2Gb de datos (64B structs), sin saber que std :: stable_sort crea una copia interna de los datos. El intercambio de basura que siguió casi encerró mi PC.

El uso de std :: sort inestable reduce el uso de memoria en un factor de 2, lo que es útil al ordenar grandes matrices. Terminé std :: stable_sort, por lo que no puedo determinar cuánto más lento fue. Sin embargo, si no es necesario un ordenamiento estable, entonces creo que es mejor usar el std :: sort inestable.

+0

Si bien esto es probablemente demasiado pequeño en las computadoras modernas, cuando se ordenan conjuntos de datos muy grandes, es mejor usar un algoritmo de clasificación externo que hace un buen uso del disco para almacenar resultados intermedios; como tipo de combinación externa. – Clearer

+0

(y, a veces, ordenar punteros es mucho más rápido que ordenar las estructuras, y viceversa, dependiendo del tamaño de las estructuras, la RAM disponible, etc.) –

1

Estaba buscando algo similar - pero se sorprendió nadie hablaba de espacio auxiliar.

Como creo, la implementación tanto de stable_sort como de sort se supone que garantiza O (NlogN) para todos los casos (Best, Average & Worst).

Sin embargo, la diferencia existe en el espacio auxiliar utilizado. stable_sort necesita un espacio auxiliar de O (N).

Puede ser la diferencia en el rendimiento radica en la adquisición de ese espacio. :)
De lo contrario, en teoría, se supone que tienen el mismo rendimiento w.r.t.

sort debería hacer lo que necesita salvo que lo necesite -> stable_sort conserva el orden relativo de los elementos con valores equivalentes.

Cuestiones relacionadas