Je résous un problème et cela implique de trier 10 numéros (int32) très rapidement. Mon application doit trier 10 numéros des millions de fois le plus rapidement possible. J'échantillonne un ensemble de données de milliards d'éléments et chaque fois que je dois en choisir 10 (simplifié) et les trier (et tirer des conclusions de la liste des 10 éléments triés).
Actuellement, j'utilise le tri par insertion, mais j'imagine que je pourrais implémenter un algorithme de tri personnalisé très rapide pour mon problème spécifique de 10 numéros qui battrait le tri par insertion.
Quelqu'un at-il une idée de la façon d'aborder ce problème?
if
puisse paraître, une série d' instructions imbriquées devrait fonctionner le mieux. Évitez les boucles.Réponses:
(Suite à la suggestion de HelloWorld de se pencher sur les réseaux de tri.)
Il semble qu'un réseau à 29 comparaisons / échanges soit le moyen le plus rapide de faire un tri à 10 entrées. J'ai utilisé le réseau découvert par Waksman en 1969 pour cet exemple en Javascript, qui devrait se traduire directement en C, car c'est juste une liste de
if
instructions, de comparaisons et de swaps.Voici une représentation graphique du réseau, divisée en phases indépendantes. Pour profiter du traitement parallèle, le groupement 5-4-3-4-4-4-3-2 peut être changé en groupement 4-4-4-4-4-4-4-3-2.
la source
#define SORTPAIR(data, i1, i2) if (data[i1] > data[i2]) { int swap = data[i1]... }
Lorsque vous traitez avec cette taille fixe, jetez un œil aux réseaux de tri . Ces algorithmes ont un temps d'exécution fixe et sont indépendants de leur entrée. Pour votre cas d'utilisation, vous n'avez pas un tel surcoût que certains algorithmes de tri ont.
Le tri bitonique est une implémentation d'un tel réseau. Celui-ci fonctionne mieux avec len (n) <= 32 sur un CPU. Sur de plus grandes entrées, vous pourriez penser à passer à un GPU. https://en.wikipedia.org/wiki/Sorting_network
Btw, une bonne page pour comparer les algorithmes de tri est celle-ci ici (bien qu'il lui manque le
bitonic sort
.http://www.sorting-algorithms.com
la source
Utilisez un réseau de tri qui a des comparaisons en groupes de 4, vous pouvez donc le faire dans les registres SIMD. Une paire d'instructions min / max compressées implémente une fonction de comparateur compacté. Désolé, je n'ai pas le temps pour le moment de rechercher une page que je me souviens avoir vue à ce sujet, mais j'espère que la recherche sur les réseaux de tri SIMD ou SSE se révélera quelque chose.
x86 SSE a des instructions min et max entières à 32 bits pour des vecteurs de quatre pouces 32 bits. AVX2 (Haswell et versions ultérieures) ont le même mais pour des vecteurs 256b de 8 pouces. Il existe également des instructions de lecture aléatoire efficaces.
Si vous avez beaucoup de petits tris indépendants, il peut être possible de faire 4 ou 8 tris en parallèle en utilisant des vecteurs. Esp. si vous choisissez des éléments de manière aléatoire (afin que les données à trier ne soient pas contiguës en mémoire de toute façon), vous pouvez éviter les mélanges et comparer simplement dans l'ordre dont vous avez besoin. 10 registres pour contenir toutes les données de 4 (AVX2: 8) listes de 10 pouces laisse encore 6 regs pour l'espace de travail.
Les réseaux de tri vectoriel sont moins efficaces si vous devez également trier les données associées. Dans ce cas, le moyen le plus efficace semble être d'utiliser une comparaison compactée pour obtenir un masque dont les éléments ont changé et d'utiliser ce masque pour mélanger des vecteurs de (références à) des données associées.
la source
Qu'en est-il d'un tri de sélection déroulé sans branche?
http://coliru.stacked-crooked.com/a/71e18bc4f7fa18c6
Les seules lignes pertinentes sont les deux premières
#define
.Il utilise deux listes et revérifie entièrement la première dix fois, ce qui serait un tri de sélection mal implémenté, mais il évite les branches et les boucles de longueur variable, ce qui peut compenser avec les processeurs modernes et un si petit ensemble de données.
Référence
J'ai comparé le réseau de tri et mon code semble être plus lent. Cependant, j'ai essayé de supprimer le déroulement et la copie. Exécution de ce code:
J'obtiens toujours de meilleurs résultats pour le tri de sélection sans succursale par rapport au réseau de tri.
la source
for ( ; i<10; i++) (m > a[i]) && (m = a[i], indx = i );
soit exceptionnellement bien optimisée. (le court-circuit est généralement une forme de branchement)std::shuffle
avecfor (int n = 0; n<10; n++) a[n]=g();
. Le temps d'exécution est divisé par deux et le réseau est désormais plus rapide.std::sort
?std::sort
, mais il fonctionnait si mal que je ne l'ai même pas inclus dans l'indice de référence. Je suppose qu'avec de minuscules ensembles de données, les frais généraux sont assez élevés.La question ne dit pas qu'il s'agit d'une sorte d'application Web. La seule chose qui a attiré mon attention était:
En tant qu'ingénieur logiciel et matériel, cela me crie absolument "FPGA" . Je ne sais pas quel genre de conclusions vous devez tirer de l'ensemble trié de nombres ou d'où proviennent les données, mais je sais qu'il serait presque trivial de traiter quelque part entre cent millions et un milliard de ces «tri et… analyser "les opérations par seconde . J'ai déjà fait du séquençage d'ADN assisté par FPGA. Il est presque impossible de battre la puissance de traitement massive des FPGA lorsque le problème est bien adapté à ce type de solution.
À un certain niveau, le seul facteur limitant est la vitesse à laquelle vous pouvez pelleter des données dans un FPGA et la vitesse à laquelle vous pouvez les extraire.
Comme point de référence, j'ai conçu un processeur d'image en temps réel haute performance qui a reçu des données d'image RVB 32 bits à un débit d'environ 300 millions de pixels par seconde. Les données diffusées à travers des filtres FIR, des multiplicateurs matriciels, des tables de recherche, des blocs de détection de bord spatial et un certain nombre d'autres opérations avant de sortir de l'autre côté. Tout cela sur un FPGA Xilinx Virtex2 relativement petit avec une synchronisation interne s'étendant d'environ 33 MHz à, si je me souviens bien, 400 MHz. Oh, oui, il avait également une implémentation de contrôleur DDR2 et fonctionnait avec deux banques de mémoire DDR2.
Un FPGA peut sortir une sorte de dix nombres 32 bits à chaque transition d'horloge tout en fonctionnant à des centaines de MHz. Il y aurait un court délai au début de l'opération car les données remplissent le (s) pipeline (s) de traitement. Après cela, vous devriez pouvoir obtenir un résultat par horloge. Ou plus si le traitement peut être parallélisé par la réplication du pipeline de tri et d'analyse. La solution, en principe, est presque triviale.
Le point est le suivant: si l'application n'est pas liée au PC et que le flux de données et le traitement sont "compatibles" avec une solution FPGA (autonome ou en tant que carte coprocesseur dans la machine), vous ne pouvez pas y aller pour être en mesure de battre le niveau de performance atteignable avec un logiciel écrit dans n'importe quelle langue, quel que soit l'algorithme.
ÉDITER:
Je viens de lancer une recherche rapide et de trouver un document qui pourrait vous être utile. Il semble que cela remonte à 2012. Vous pouvez faire beaucoup de performances aujourd'hui (et même à l'époque). C'est ici:
Tri des réseaux sur FPGA
la source
J'ai récemment écrit une petite classe qui utilise l'algorithme de Bose-Nelson pour générer un réseau de tri au moment de la compilation.
Il peut être utilisé pour créer un tri très rapide pour 10 numéros.
Notez qu'au lieu d'une
if (compare) swap
instruction, nous codons explicitement les opérateurs ternaires pour min et max. C'est pour aider le compilateur à utiliser du code sans branche.Repères
Les benchmarks suivants sont compilés avec clang -O3 et exécutés sur mon MacBook Air mi-2012.
Tri des données aléatoires
En le comparant avec le code de DarioP, voici le nombre de millisecondes nécessaires pour trier 1 million de tableaux int 32 bits de taille 10:
Réseau de tri codé en dur 10: 88,774 ms
Modèle de Bose-Nelson trié 10: 27,815 ms
En utilisant cette approche basée sur des modèles, nous pouvons également générer des réseaux de tri au moment de la compilation pour un autre nombre d'éléments.
Temps (en millisecondes) pour trier 1 million de tableaux de différentes tailles.
Le nombre de millisecondes pour les tableaux de taille 2, 4, 8 est respectivement de 1,943, 8,655 et 20,246.
Crédits à Glenn Teitelbaum pour le tri par insertion déroulée.
Voici les horloges moyennes par tri pour les petits tableaux de 6 éléments. Le code de référence et des exemples peuvent être trouvés à cette question:
Le type le plus rapide de tableau de 6 int de longueur fixe
Il fonctionne aussi vite que l'exemple le plus rapide de la question pour 6 éléments.
Performances pour trier les données triées
Souvent, les tableaux d'entrée peuvent être déjà triés ou principalement triés.
Dans de tels cas, le tri par insertion peut être un meilleur choix.
Vous pouvez choisir un algorithme de tri approprié en fonction des données.
Le code utilisé pour les benchmarks se trouve ici .
la source
v1 = v0 < v1 ? v1 : v0; // Max
peuvent encore branche, dans ce cas , il peut être remplacé parv1 += v0 - t
parce que sit
estv0
alors d'v1 + v0 -t == v1 + v0 - v0 == v1
autret
estv1
etv1 + v0 -t == v1 + v0 - v1 == v0
maxss
ouminss
instruction sur les compilateurs modernes. Mais dans les cas où cela ne fonctionne pas, d'autres méthodes d'échange peuvent être utilisées. :)Bien qu'un tri en réseau ait de bonnes chances d'être rapide sur de petites baies, vous ne pouvez parfois pas battre le tri par insertion s'il est correctement optimisé. Par exemple, insert de lot avec 2 éléments:
la source
in[y+2]= in[y];
, faute de frappe?Vous pouvez dérouler complètement
insertion sort
Pour faciliter cela, les récursifs
template
peuvent être utilisés sans surcharge de fonction. Puisqu'il s'agit déjà d'untemplate
,int
peut être untemplate
paramètre. Cela rend également les tailles de tableau de codage autres que 10 triviales à créer.Notez que trier
int x[10]
l'appel est dû au faitinsert_sort<int, 9>::sort(x);
que la classe utilise l'index du dernier élément. Cela pourrait être encapsulé, mais ce serait plus de code à lire.Lors de mes tests, cela a été plus rapide que les exemples de réseau de tri.
la source
Pour des raisons similaires à celles que j'ai décrites ici , les fonctions de tri suivantes,
sort6_iterator()
etsort10_iterator_local()
, devraient bien fonctionner, où le réseau de tri a été pris à partir d' ici :Pour appeler cette fonction, je lui ai passé un
std::vector
itérateur.la source
Un tri par insertion nécessite en moyenne 29,6 comparaisons pour trier 10 entrées avec un meilleur cas de 9 et un pire de 45 (entrée donnée qui est dans l'ordre inverse).
Un shellsort {9,6,1} nécessitera en moyenne 25,5 comparaisons pour trier 10 entrées. Le meilleur des cas est de 14 comparaisons, le pire est de 34 et le tri d'une entrée inversée nécessite 22.
Ainsi, l'utilisation de shellsort au lieu du tri par insertion réduit le cas moyen de 14%. Bien que le meilleur des cas soit augmenté de 56%, le pire des cas est réduit de 24%, ce qui est significatif dans les applications où il est important de contrôler les performances du pire des cas. Le cas inverse est réduit de 51%.
Étant donné que vous semblez familier avec le tri par insertion, vous pouvez implémenter l'algorithme en tant que réseau de tri pour {9,6}, puis clouer sur le tri par insertion ({1}) après cela:
la source