2013-05-10 1 views
1

J'utilise cette méthode pour traiter un seul fichier texte d'environ 220 000 lignes. Il faut quelques minutes pour en traiter un, mais j'en ai beaucoup. Y a-t-il des recommandations pour accélérer ce processus?Comment améliorer les performances d'un processus en convertissant un fichier en tableau de hachages?

def parse_list(file_path,import=false) 
# Parse the fixed-length fields 
    if File.exist?(file_path) 
    result=[] 
    File.readlines(file_path)[5..-1].each do |rs| 
     if rs.length > 140 
      r=rs.strip 
      unless r=='' 
      filing={ 
        'name' => r[0..50].strip, 
        'form' => r[51..70].strip, 
        'type' => r[71..80].strip, 
        'date' => r[81..90].strip, 
        'location' => r[91..-1].strip 
        }  
       result.push(filing) 
      end 
     end 
    end 
    return result 
    else 
    return false 
    end 
end 

Mise à jour:

Au départ, je pensais qu'il y avait des économies de temps massives de l'utilisation Nex et les méthodes de thetinman alors je suis allé à les tester en gardant la méthode d'analyse cohérente.

En utilisant ma méthode originale r[].strip d'analyse syntaxique, mais avec la méthode des blocs de Nex each_line et foreach méthodes de thetinman:

Rehearsal --------------------------------------------- 
Nex   8.260000 0.130000 8.390000 ( 8.394067) 
Thetinman 9.740000 0.120000 9.860000 ( 9.862880) 
----------------------------------- total: 18.250000sec 

       user  system  total  real 
Nex  14.270000 0.140000 14.410000 (14.397286) 
Thetinman 19.030000 0.080000 19.110000 (19.118621) 

en cours en utilisant à nouveau unpack.map méthode d'analyse syntaxique de thetinman:

Rehearsal --------------------------------------------- 
Nex   9.580000 0.120000 9.700000 ( 9.694327) 
Thetinman 11.470000 0.090000 11.560000 (11.567294) 
----------------------------------- total: 21.260000sec 

       user  system  total  real 
Nex  15.480000 0.120000 15.600000 (15.599319) 
Thetinman 18.150000 0.070000 18.220000 (18.217744) 

unpack.map(&:strip) vs r[].strip: unpack avec map ne semble pas augmenter la vitesse, mais est une méthode intéressante à utiliser A l'avenir.

J'ai trouvé un problème différent: Avec les gains de temps substantiels trouvés, je pensais, j'ai continué à exécuter les méthodes de Nex et thetinman manuellement en utilisant pry. C'est là que j'ai trouvé mon ordinateur suspendu, tout comme mon code d'origine. Alors j'ai continué à tester, mais avec mon code d'origine.

Rehearsal --------------------------------------------- 
Original 7.980000 0.140000 8.120000 ( 8.118340) 
Nex   9.460000 0.080000 9.540000 ( 9.546889) 
Thetinman 10.980000 0.070000 11.050000 (11.042459) 
----------------------------------- total: 28.710000sec 

       user  system  total  real 
Original 16.280000 0.140000 16.420000 (16.414070) 
Nex  15.370000 0.080000 15.450000 (15.454174) 
Thetinman 20.100000 0.090000 20.190000 (20.195533) 

Mon code, Nex, et les méthodes de thetinman semblent comparables, avec Nex étant le plus rapide à l'aide de référence. Cependant, Benchmark ne semble pas raconter toute l'histoire, car l'utilisation de pry pour tester le code manuellement prend toutes les méthodes pour prendre beaucoup plus de temps, si longtemps que je m'annule avant d'obtenir le résultat.

J'ai quelques autres questions:

  1. Y at-il quelque chose de spécifique sur l'exécution quelque chose comme ceci dans IRB/Pry qui produirait ces résultats étranges, ce qui rend le code plus lent exécuter massivement?
  2. Si je cours original_method.count, nex_method.count, ou thetinmans_method.count, ils semblent tous revenir rapidement.
  3. En raison de problèmes de mémoire et d'évolutivité, thetinman et nex recommandent que la méthode d'origine ne soit pas utilisée. Cependant, à l'avenir, existe-t-il des moyens de tester l'utilisation de la mémoire avec quelque chose comme un benchmark?

Mise à jour pour NEX, en utilisant activerecord-import:

@nex, est-ce que vous voulez dire? Cela semble être lent pour moi encore, mais je ne suis pas sûr de ce que vous voulez dire quand vous dites:

importer un ensemble de données à l'intérieur de ce bloc.

Comment recommandez-vous de le modifier?

def parse_line(line) 
    filing={ 
    'name' => line[0..50].strip, 
    'form' => line[51..70].strip, 
    'type' => line[71..80].strip, 
    'date' => line[81..90].strip, 
    'location' => line[91..-1].strip 
    }  
end 

def import_files 
result=[] 
parse_list_nix(file_path){|line| 
    filing=parse_line(line)  
    result.push(Filing.new(filing)) 

} 
Filing.import result #result is an array of new records that are all imported at once 
end 

Les résultats de la méthode activerecord-import sont, comme vous pouvez le voir, beaucoup plus lent:

Rehearsal ------------------------------------------ 
import 534.840000 1.860000 536.700000 (553.507644) 
------------------------------- total: 536.700000sec 

      user  system  total  real 
import 263.220000 1.320000 264.540000 (282.751891) 

Est-ce que ce processus d'importation lent semble normal?

Cela me semble super lent. J'essaie de comprendre comment accélérer ça, mais je n'ai plus d'idées.

+0

Vous ne nous avez pas donné de données échantillon pour travailler à partir, donc nous n'avons pas une base pour tester avec. Dans vos tests, supprimez tout sauf votre méthode de lecture de fichier et modifiez-la jusqu'à ce qu'elle soit aussi rapide que vos données, en gardant à l'esprit que vous ne voulez PAS tout extraire dans la RAM parce que ce n'est pas évolutif. Puis ajoutez la division et importez et répétez les tests. Vous ne pouvez pas utiliser pry, ou IRB, ou un débogueur, avec Benchmark et attendre des résultats réalistes. Ils gênent la façon dont le script fonctionne comme un effet secondaire des commodités qu'ils offrent et qui se traduit par un code de fonctionnement plus lent. –

+1

Vous n'avez pas dit combien de fichiers vous chargez, mais 5-8 minutes pour charger un tas de fichiers 220K-ligne une fois par jour ne vous inquiétez pas. Si cela semble lent, vous pouvez utiliser des threads pour charger plusieurs fichiers à la fois, ou les traiter et les charger directement, car vous avez besoin de champs de largeur fixe, ce qui chargera probablement tout un chargement plus rapide car il est probablement compilé code spécifique à l'application. –

+0

@thetinman, Il faut environ 6-9 minutes par fichier pour importer (voir le test de performance ci-dessus). J'ai besoin d'importer une vingtaine de fichiers. Vous avez de bonnes suggestions. J'aime votre suggestion d'importer directement dans la base de données. Je vais chercher comment c'est fait. Je n'ai pas appris les processus de filetage, ou de forking, mais j'y penserai aussi. Si vous avez de bons liens, s'il vous plaît envoyez-les moi. Merci encore! – user2012677

Répondre

2

Sans les données de l'échantillon, il est difficile de le confirmer, mais, sur la base du code d'origine, je serais probablement écrire quelque chose comme ceci:

require 'english' 

# Parse the fixed-length fields 
def parse_list(file_path,import=false) 

    return false unless File.exist?(file_path) 

    result=[] 
    File.foreach(file_path) do |rs| 
    next unless $INPUT_LINE_NUMBER > 5 
    next unless rs.length > 140 

    r = rs.strip 
    if r > '' 
     name, form, type, date, location = r.unpack('A51 A20 A10 A10 A*').map(&:strip) 
     result << { 
     'name'  => name, 
     'form'  => form, 
     'type'  => type, 
     'date'  => date, 
     'location' => location 
     } 
    end 
    end 

    result 
end 

220.000 lignes ne sont pas un grand fichier où je viens. Nous obtenons des fichiers journaux 3x en milieu de matinée, donc en utilisant n'importe quel fichier E/S qui slurps le fichier est sorti. La classe IO de Ruby a deux méthodes d'E/S ligne par ligne et un nombre qui retourne des tableaux. Vous voulez le premier parce qu'ils sont évolutifs. À moins que vous ne puissiez garantir que le fichier en cours de lecture s'adaptera confortablement à la mémoire de Ruby, évitez le plus tard.

+0

pourquoi avoir .foreach (file_path) [5 ..- 1] et suivant sauf $ INPUT_LINE_NUMBER> 5? – user2012677

+0

C'est un vestige du code original. Le sortir. Merci. –

+0

Le processus d'importation lente vous semble-t-il normal? ou feriez-vous d'autres recommandations pour améliorer? – user2012677

2

Le problème est que vous remplissez votre mémoire. Qu'allez-vous faire avec ce résultat? Faut-il s'attarder dans votre mémoire dans son ensemble ou serait-ce une option de le traiter ligne par ligne avec un bloc?

De même, vous ne devez pas utiliser les lignes de lecture ici. Faites quelque chose comme ça à la place car il utilise un recenseur:

def parse_list(file_path, import=false) 
    i = 0 
    File.open(file_path,'r').each_line do |line| 
    line.strip! 
    next if (i+=1) < 5 || line.length < 141 
    filing = { 'name' => r[0..50].strip, 
       'form' => r[51..70].strip, 
       'type' => r[71..80].strip, 
       'date' => r[81..90].strip, 
       'location' => r[91..-1].strip } 
    yield(filling) if block_given? 
    end 
end 

# and calling it like this: 
parse_list('/tmp/foobar'){ |filling| 
    Filing.new(filing).import 
} 
+0

Je prévois d'utiliser activerecord-import (https://github.com/zdennis/activerecord-import) pour importer les enregistrements dans une base de données my Postgres. – user2012677

+0

Vous pouvez donc utiliser la version de bloc fournie et importer un ensemble de données à l'intérieur de ce bloc. De cette façon, vous n'avez pas besoin de garder tout le tableau en mémoire et l'algorithme fonctionnera beaucoup plus rapidement. Ceci n'est bien sûr pas perceptible avec quelques centaines de disques, mais avec plus de 200 000 disques, ça en vaut vraiment la peine. – nex

+0

voir ci-dessus modifier. – user2012677

Questions connexes