Le défi est de filtrer rapidement un gros fichier.
- Entrée: chaque ligne a trois entiers positifs séparés par des espaces.
Sortie: Toutes les lignes d'entrée
A
B
,T
qui satisfont soit du critère suivant.- Il existe une autre ligne d'entrée
C
,D
,U
oùD = A
et0 <= T - U < 100
. - Il existe une autre ligne d'entrée
C
,D
,U
oùB = C
et0 <= U - T < 100
.
- Il existe une autre ligne d'entrée
Pour créer un fichier de test, utilisez le script python suivant qui sera également utilisé pour les tests. Il fera un fichier 1.3G. Vous pouvez bien sûr réduire les nolines pour les tests.
import random
nolines = 50000000 # 50 million
for i in xrange(nolines):
print random.randint(0,nolines-1), random.randint(0,nolines-1), random.randint(0,nolines-1)
Règles. Le code le plus rapide lorsqu'il est testé sur un fichier d'entrée que je crée en utilisant le script ci-dessus sur mon ordinateur gagne. Le délai est d'une semaine à compter de la première inscription correcte.
Ma machine Les horaires seront exécutés sur ma machine. Il s'agit d'une installation ubuntu standard de 8 Go de RAM sur un processeur AMD FX-8350 à huit cœurs. Cela signifie également que je dois pouvoir exécuter votre code.
Quelques informations de timing pertinentes
Timings mis à jour pour exécuter les éléments suivants avant chaque test.
sync && sudo bash -c 'echo 3 > /proc/sys/vm/drop_caches'
time wc test.file
real 0m26.835s
user 0m18.363s
sys 0m0.495s
time sort -n largefile.file > /dev/null
real 1m32.344s
user 2m9.530s
sys 0m6.543s
Statut des entrées
J'exécute la ligne suivante avant chaque test.
sync && sudo bash -c 'echo 3 > /proc/sys/vm/drop_caches'
- Perl (en attente d'une correction de bogue.)
- Scala 1 minutes 37 secondes par @James_pic. (Utilisation de scala -J-Xmx6g Filterer largefile.file output.txt)
- Java . 1 minute 23 secondes par @Geobits. (Utilisation de java -Xmx6g Filter_26643)
- C . 2 minutes 21 secondes par @ScottLeadley.
- C . 28 secondes par @James_pic.
- Python + pandas . Peut-être existe-t-il une solution "groupby" simple?
- C . 28 secondes par @KeithRandall.
Les gagnants sont Keith Randall et James_pic.
Je ne pouvais pas distinguer leurs temps de course et les deux sont presque aussi rapides que les wc!
1 < n < 2147483647
?Réponses:
C, ~
74,1 secondesRadix trie sur T, puis parcourt le tableau à la recherche de correspondances.
Il est rapide car il est compatible avec le cache. Le radix trie raisonnablement ainsi, et la marche finale très. Je dois comparer chaque ligne à environ 100 autres, mais elles sont toutes adjacentes dans le cache.
Ajouté: je n'ai plus à vérifier chaque ligne par rapport à une analyse de 100 autres lignes. Un petit tableau de décomptes de bits de poids faible de b dans la fenêtre suffit pour éliminer la plupart des instances de cette analyse.
Maintenant environ 1/2 analyse de temps, 1/3 de tri, 1/6 de temps pour faire la correspondance réelle.
la source
filter.c
pour faire la même chose, suis venu à la question et l' ai trouvé. +1Scala 2.10 - 0:41
Le problème est fondamentalement:
La plupart des SGBDR remarqueront que la jointure de
x.a
ày.b
a la spécificité la plus élevée et la planifieront comme une jointure de hachage.Voilà donc ce que nous allons faire. Nous créons une table de hachage des données
a
, nous les joignons à la même tableb
et filtrons la différencet
.Compiler avec:
Et courir avec:
Sur ma machine, cela fonctionne en 2 minutes 27.
Pourtant, il pourrait être intéressant d'essayer l'approche de la réponse de @ Lembik, mais dans un langage plus rapide. Cela correspond à quelque chose comme une jointure de fusion
t
. Sur le papier, il devrait être plus lent, mais il a une meilleure localité de cache, ce qui peut le pousser en avant.Mise à jour
J'ai réussi à me raser une grande partie du temps avec un changement étonnamment petit - un meilleur mélangeur de hachage. La carte de hachage est très sensible à l'agrégation de hachage, donc ce changement la ramène à 1:45 sur ma machine.
La plupart de ce temps est consacré à la lecture des données dans un tableau.
Je suis curieux de savoir pourquoi mon code de lecture de données est tellement plus lent que @Geobits. Il faut 70 secondes à mon code pour lire les données - plus longtemps que le programme entier @Geobits, une fois le
Thread.start
bogue corrigé. Je suis tenté de voler l'approche @Geobits pour lire les données, mais je ne suis pas sûr de ce que les dieux de Stack Exchange ressentiraient à ce sujet.Update 2
J'ai apporté d'autres améliorations, cette fois au lecteur de données. L'utilisation de correspondances de modèles et d'opérations monades à l'intérieur de la boucle a nui aux performances, donc je l'ai simplifié. Je pense que
scala.io.Source
c'est le prochain goulot d'étranglement à résoudre.Il est maintenant 1:26 sur ma machine.
Mise à jour 3
Débarrassé d'
probe
OpenHashMultiMap. Le code est désormais plus java-ish et s'exécute en 1:15.Mise à jour 4
J'utilise maintenant un FSM pour analyser l'entrée. Le temps d'exécution est à 0:41
la source
StringTokenizer
, mais quand je le fais, je parse des millions de chaînes.String.split
c'est actuellement un goulot d'étranglement, mais ceStringTokenizer
n'est pas beaucoup mieux en ce moment - l'allocation dans une boucle interne serrée nuit à mon GC déjà tendu. Je travaille sur un FSM qui semble prometteur (tout en étant surpuissant)Java: 1m54s
(Sur mon i7)
Étant donné que chaque match sera à moins de 100
t
de son partenaire, j'ai décidé de regrouper les entréest
. Il y a un seau pour 100, donc pour vérifier un nombre, il suffit de comparer avec +/- 1 seaux.En moyenne, chaque compartiment ne contient que 100 entrées, il ne faut donc pas longtemps pour analyser quelques compartiments pour chacun. Bien plus de la moitié du temps est consacré à la lecture et au regroupement, l'appariement ne prend que 40 secondes environ.
Remarque: Selon votre configuration JVM, vous devrez peut-être augmenter la taille du segment de mémoire. Cela suppose également le nom de fichier de
test.file
. Modifiez-le simplement à la ligne 24 si ce n'est pas le cas.la source
Thread::run
, nonThread.start
, donc tout fonctionne sur lemain
fil. AvecThread::start
, le temps d'exécution passe de 1:38 à 0:46 sur ma machine.sort
temps. J'ai grimpé le tas jusqu'à 6G, le même que le mien (vous avez dit que vous aviez 8G, donc cela semblait une supposition raisonnable).C - 12 secondes
J'ai décidé de porter ma réponse Scala sur C, pour voir combien plus de performances je pourrais obtenir.
C'est plus ou moins la même approche (construire une table de hachage ouverte
a
), sauf que je saute l'étape où je construis le tableau initial et que j'itère directement à partir de la table de hachage (pour une raison quelconque, je ne pourrais jamais faire exécuter cette approche dans Scala - Je soupçonne que la JVM inlining était à blâmer).Je ne me suis pas soucié des fils, car c'est pénible de le faire de façon portable.
Le code est:
Compiler avec:
Et courir avec:
L'emplacement du fichier de test est codé en dur comme "test.file".
Encore une fois, la lecture des données prend la plupart du temps (un peu moins de 9 secondes). L'appariement prend le reste du temps.
Encore une fois, il sera intéressant de voir comment cela se compare à la réponse de Scott Leadley, qui utilise le même langage mais une stratégie différente. Scott se joint à T, ce qui signifie en principe qu'il aura plus à se battre contre, mais encore une fois, se joindre à T donne une meilleure localité de cache.
la source
diff <(sort -n James_pic-c.out) <(sort -n James_pic-scala.out)
a
valeur donnée se produitn
fois oùn >= BUFFER_SIZE + 2
perl, 17m46s sur un core i7 w / 8GB mem
Tout d'abord, nous utilisons sort -n -k3 pour mettre de l'ordre dans le champ le plus important, en profitant du parallélisme intégré sur les versions modernes de sort (1). Puis, comme Perl est fortement gêné par le fait qu'un simple scalaire prend de l'ordre de 80 octets chacun (50 millions * 3 * 80 c'est trop - au moins 12 Go), nous slurpons la sortie à 50 millions * 12 octets tableau (12 octets par ligne, chaque ligne contient 3 entiers qui peuvent être représentés comme un entier 32 bits). Ensuite, nous tirons 8 threads couvrant (environ) 1/8 des données chacun (+ un certain chevauchement).
Sortie non triée:
Je suis sûr que ce serait un ordre de grandeur plus rapide en C, mais je ne prendrai probablement pas le temps de le faire.
la source
A = D = 8455767
maisU = 50175
,T = 50130
et ainsiT - U = -45
C # - 30 secondes
Une approche différente de la plupart si je lis bien - je n'utilise aucune structure basée sur le hachage.
J'ai tendance à ne pas obtenir de résultats, je ne sais pas s'il s'agit d'une anomalie statistique ou d'une erreur de raisonnement.Corrigé, la comparaison pour le tri binaire était défectueuse.la source
x.A
cela viendrasortedA
etx.B
viendrasortedB
, alors qu'en fait les deux proviendrontsortedB
, et celaComparer
produira résultats absurdes.A
etB
, il y a un algorithme plus rapide que l'itération surA
et la recherche binaire surB
laquelle estO(n log(n))
(et est en fait une table de hachage du pauvre). Vous pouvez à la place fusionner-joindre les deux listes, ce qui estO(n)
.B
seront uniformément réparties dans une plage spécifique, serait d'échanger la recherche binaire pour la recherche par interpolation, ce qui réduit le temps de recherche deO(log(n))
àO(log(log(n))
.C
Brutal, force brute, laid en face C. Sur une refonte, je choisirais à peu près n'importe quel autre langage compilé.
Compilez avec "gcc -m64 -pthreads -O". Attend une entrée sur stdin. Exécute plusieurs threads par défaut. Utilisez l'option "-s" pour utiliser un seul thread.
la source
J'ai finalement eu la chance de construire un système Ubuntu 14.04 physique similaire à celui de Lembik et de faire un post-mortem sur ma solution à ce puzzle. Dans mon choix d'importance:
aspiréeétaient non performantes:Plutôt que de vous ennuyer avec un autre analyseur FSM, la solution ci-dessous utilise fgets () et un remplacement local strtol () [recherchez s2i ()].
Une implémentation de référence dans Ruby:
C'est un chien, environ 50 fois plus lent qu'une solution C, mais perl est tout aussi lent et moins concis.
La solution C:
Compilez avec "gcc -O3 -std = c99 -Wall -m64".
la source