Estoy resolviendo un problema e implica ordenar 10 números (int32) muy rápidamente. Mi aplicación necesita ordenar 10 números millones de veces lo más rápido posible. Estoy muestreando un conjunto de datos de miles de millones de elementos y cada vez que necesito elegir 10 números (simplificado) y ordenarlos (y sacar conclusiones de la lista ordenada de 10 elementos).
Actualmente estoy usando la ordenación por inserción, pero imagino que podría implementar un algoritmo de ordenación personalizado muy rápido para mi problema específico de 10 números que superaría la ordenación por inserción.
¿Alguien tiene alguna idea sobre cómo abordar este problema?
algorithm
sorting
insertion-sort
sorting-network
bodacydo
fuente
fuente
if
declaraciones anidadas debería funcionar mejor. Evitar bucles.Respuestas:
(Siguiendo la sugerencia de HelloWorld para buscar redes de clasificación).
Parece que una red de comparación / intercambio de 29 es la forma más rápida de hacer una clasificación de 10 entradas. Utilicé la red descubierta por Waksman en 1969 para este ejemplo en Javascript, que debería traducirse directamente a C, ya que es solo una lista de
if
declaraciones, comparaciones e intercambios.Aquí hay una representación gráfica de la red, dividida en fases independientes. Para aprovechar el procesamiento en paralelo, la agrupación 5-4-3-4-4-4-3-2 se puede cambiar a una agrupación 4-4-4-4-4-4-3-2.
fuente
#define SORTPAIR(data, i1, i2) if (data[i1] > data[i2]) { int swap = data[i1]... }
Cuando trate con este tamaño fijo, eche un vistazo a Ordenar redes . Estos algoritmos tienen un tiempo de ejecución fijo y son independientes de su entrada. Para su caso de uso, no tiene la sobrecarga que tienen algunos algoritmos de clasificación.
La ordenación bitónica es una implementación de dicha red. Este funciona mejor con len (n) <= 32 en una CPU. En entradas más grandes podría pensar en pasar a una GPU. https://en.wikipedia.org/wiki/Sorting_network
Por cierto, una buena página para comparar algoritmos de clasificación es esta aquí (aunque le falta el
bitonic sort
.http://www.sorting-algorithms.com
fuente
Use una red de clasificación que tenga comparaciones en grupos de 4, para que pueda hacerlo en registros SIMD. Un par de instrucciones min / max empaquetadas implementa una función de comparador empacado. Lo siento, no tengo tiempo en este momento para buscar una página que recuerdo haber visto sobre esto, pero espero que buscar en redes de clasificación SIMD o SSE arroje algo.
x86 SSE tiene instrucciones mínimas y máximas de entero de 32 bits para vectores de cuatro entradas de 32 bits. AVX2 (Haswell y posterior) tienen lo mismo pero para vectores de 256b de 8 ints. También hay instrucciones de barajado eficientes.
Si tiene muchas clases pequeñas independientes, es posible hacer 4 u 8 clases en paralelo usando vectores. Esp. si está eligiendo elementos al azar (de modo que los datos que se ordenarán no estarán contiguos en la memoria de todos modos), puede evitar barajar y simplemente comparar en el orden que necesita. 10 registros para contener todos los datos de 4 (AVX2: 8) listas de 10 entradas todavía dejan 6 registros para el espacio de memoria virtual.
Las redes de clasificación de vectores son menos eficientes si también necesita clasificar los datos asociados. En ese caso, la forma más eficiente parece ser usar una comparación empaquetada para obtener una máscara de qué elementos cambiaron, y usar esa máscara para combinar vectores de (referencias a) datos asociados.
fuente
¿Qué pasa con un tipo de selección desenrollado y sin rama?
http://coliru.stacked-crooked.com/a/71e18bc4f7fa18c6
Las únicas líneas relevantes son las dos primeras
#define
.Utiliza dos listas y vuelve a verificar por completo la primera diez veces, lo que sería un tipo de selección mal implementado, sin embargo, evita ramas y bucles de longitud variable, que pueden compensar con procesadores modernos y un conjunto de datos tan pequeño.
Punto de referencia
Comparé la red de clasificación y mi código parece ser más lento. Sin embargo, intenté eliminar el desenrollado y la copia. Ejecutando este código:
Constantemente obtengo mejores resultados para la selección sin sucursales en comparación con la red de clasificación.
fuente
for ( ; i<10; i++) (m > a[i]) && (m = a[i], indx = i );
esté excepcionalmente bien optimizada. (el cortocircuito generalmente es una forma de ramificación)std::shuffle
confor (int n = 0; n<10; n++) a[n]=g();
. El tiempo de ejecución se reduce a la mitad y la red es más rápida ahora.std::sort
?std::sort
pero funcionó tan mal que ni siquiera lo incluí en el punto de referencia. Supongo que con pequeños conjuntos de datos hay bastante sobrecarga.La pregunta no dice que se trata de una aplicación basada en la web. Lo único que me llamó la atención fue:
Como ingeniero de software y hardware, esto me grita absolutamente "FPGA" . No sé qué tipo de conclusiones necesita sacar del conjunto ordenado de números o de dónde provienen los datos, pero sé que sería casi trivial procesar entre cien millones y mil millones de estos "ordenar y ... analizar "operaciones por segundo . He realizado trabajos de secuenciación de ADN asistidos por FPGA en el pasado. Es casi imposible superar el poder de procesamiento masivo de FPGA cuando el problema es adecuado para ese tipo de solución.
En algún nivel, el único factor limitante se convierte en la rapidez con la que puede insertar datos en un FPGA y la rapidez con que puede sacarlos.
Como punto de referencia, diseñé un procesador de imagen en tiempo real de alto rendimiento que recibió datos de imagen RGB de 32 bits a una velocidad de aproximadamente 300 millones de píxeles por segundo. Los datos se transmitieron a través de filtros FIR, multiplicadores de matriz, tablas de búsqueda, bloques de detección de bordes espaciales y una serie de otras operaciones antes de salir por el otro extremo. Todo esto en un FPGA Xilinx Virtex2 relativamente pequeño con un reloj interno que abarca desde aproximadamente 33MHz a, si mal no recuerdo, 400MHz. Ah, sí, también tenía una implementación de controlador DDR2 y ejecutaba dos bancos de memoria DDR2.
Un FPGA puede generar una especie de diez números de 32 bits en cada transición de reloj mientras funciona a cientos de MHz. Habrá un breve retraso al comienzo de la operación a medida que los datos llenen la / s tubería / s de procesamiento. Después de eso, debería poder obtener un resultado por reloj. O más si el procesamiento se puede paralelizar mediante la replicación de la tubería de clasificación y análisis. La solución, en principio, es casi trivial.
El punto es: si la aplicación no está vinculada a la PC y el flujo de datos y el procesamiento son "compatibles" con una solución FPGA (ya sea de forma independiente o como una tarjeta de coprocesador en la máquina), no hay forma de que vaya para poder superar el nivel de rendimiento alcanzable con software escrito en cualquier idioma, independientemente del algoritmo.
EDITAR:
Simplemente realicé una búsqueda rápida y encontré un documento que podría serle útil. Parece que se remonta a 2012. Puede mejorar mucho su rendimiento hoy (e incluso en aquel entonces). Aquí está:
Clasificación de redes en FPGA
fuente
Recientemente escribí una pequeña clase que usa el algoritmo Bose-Nelson para generar una red de clasificación en tiempo de compilación.
Se puede usar para crear una clasificación muy rápida para 10 números.
Tenga en cuenta que en lugar de una
if (compare) swap
declaración, codificamos explícitamente los operadores ternarios para min y max. Esto es para ayudar a empujar al compilador a usar código sin ramificación.Puntos de referencia
Los siguientes puntos de referencia están compilados con clang -O3 y se ejecutaron en mi MacBook Air de mediados de 2012.
Ordenar datos aleatorios
Comparándolo con el código de DarioP, aquí está el número de milisegundos necesarios para clasificar 1 millón de arreglos int de 32 bits de tamaño 10:
Sorted Codificado 10: 88.774 ms Templado
Bose-Nelson sort 10: 27.815 ms
Usando este enfoque con plantillas, también podemos generar redes de clasificación en tiempo de compilación para otro número de elementos.
Tiempo (en milisegundos) para clasificar 1 millón de matrices de varios tamaños.
El número de milisegundos para matrices de tamaño 2, 4, 8 es 1.943, 8.655, 20.246 respectivamente.
Créditos a Glenn Teitelbaum para el tipo de inserción desenrollada.
Aquí están los relojes promedio por tipo para pequeñas matrices de 6 elementos. El código de referencia y los ejemplos se pueden encontrar en esta pregunta:
tipo más rápido de longitud fija de 6 int array
Funciona tan rápido como el ejemplo más rápido en la pregunta para 6 elementos.
Rendimiento para ordenar datos ordenados
A menudo, los arreglos de entrada pueden estar ya ordenados o en su mayoría ordenados.
En tales casos, la ordenación por inserción puede ser una mejor opción.
Es posible que desee elegir un algoritmo de clasificación apropiado según los datos.
El código utilizado para los puntos de referencia se puede encontrar aquí .
fuente
v1 = v0 < v1 ? v1 : v0; // Max
aún puede ramificarse, en ese caso puede reemplazarsev1 += v0 - t
porque sit
es así,v0
entoncesv1 + v0 -t == v1 + v0 - v0 == v1
lot
esv1
yv1 + v0 -t == v1 + v0 - v1 == v0
maxss
ominss
en compiladores modernos. Pero en los casos en que no funciona, se pueden usar otras formas de intercambio. :)Aunque una ordenación de red tiene buenas probabilidades de ser rápida en matrices pequeñas, a veces no se puede superar la ordenación por inserción si se optimiza correctamente. Por ejemplo, inserción por lotes con 2 elementos:
fuente
in[y+2]= in[y];
, error tipográfico?Puedes desenrollar completamente
insertion sort
Para hacerlo más fácil,
template
se pueden usar s recursivos sin sobrecarga de funciones. Como ya es untemplate
, tambiénint
puede ser untemplate
parámetro. Esto también hace que la codificación de tamaños de matriz que no sean 10 sea trivial para crear.Tenga en cuenta que para ordenar
int x[10]
la llamada se debe ainsert_sort<int, 9>::sort(x);
que la clase utiliza el índice del último elemento. Esto podría envolverse, pero sería más código para leer.En mis pruebas, esto fue más rápido que los ejemplos de red de clasificación.
fuente
Por razones similares a las que describí aquí , las siguientes funciones de clasificación
sort6_iterator()
ysort10_iterator_local()
, deberían funcionar bien, de dónde se tomó la red de clasificación desde aquí :Para llamar a esta función, le pasé un
std::vector
iterador.fuente
Un ordenamiento por inserción requiere en promedio 29,6 comparaciones para ordenar 10 entradas con un mejor caso de 9 y un peor de 45 (entrada dada que está en orden inverso).
Un {9,6,1} shellsort requerirá en promedio 25.5 comparaciones para clasificar 10 entradas. El mejor caso es 14 comparaciones, el peor es 34 y ordenar una entrada invertida requiere 22.
Por lo tanto, el uso de shellsort en lugar de inserción reduce el promedio de casos en un 14%. Aunque el mejor caso se incrementa en un 56%, el peor caso se reduce en un 24%, lo cual es significativo en aplicaciones donde es importante mantener el rendimiento del peor de los casos bajo control. El caso inverso se reduce en un 51%.
Como parece estar familiarizado con la ordenación por inserción, puede implementar el algoritmo como una red de clasificación para {9,6} y luego agregar el ordenamiento por inserción ({1}) después de eso:
fuente