Profiling mémoire et transformation du pré-traitement
Pierre-Antoine Bouttier
Séminaire iWorms / Décembre 2017 - Chamonix
En résumé
- Architecture du pré-traitement
- Profiling mémoire
- Bénéfices d'une réécriture du pré-traitement
Architecture du prétraitement actuel
Le point d'entrée :
- Un script bash
- Récupérant toutes les traces selon un jeu de paramètres (e.g. période, station, réseau)
- Pour chaque trace, traitement effectué par un script python
wget --http-user="${HTTPUSER}" --http-password="${HTTPPASS}"
"http://ws.resif.fr/fdsnws/dataselect/1/queryauth?network=${net}&station=${sta}
&location=${loc}&channel=${chan}&starttime=${NTAGM1}T23:59:00.000000
&endtime=${NTAGP1}T00:01:00.000000" -O - |
python2.7 $PEXE $YEAR $JDAY $RAPATRIE_OPTION $net $sta $loc $chan
Architecture du prétraitement actuel
Traitement d'une trace :
- Interpolation (linéaire) à 100Hz
- Troncature entre 00:00 et 23:59
- Application d'un filtre passe-haut
- Remplissage des sauts avec des zéros
- Filtre passe-bande
- Décimation à 10Hz
- CQ du traitement (NaN ou +/-Inf)
- Blanchiment spectral
- Écriture dans HDF5
Architecture du prétraitement actuel
Quelques remarques
- Traitement purement séquentiel
- Accès RESIF simultanés limités à 3 (mais limite relevable)
- Sur le noeud luke4 : 5.3sec / 1day trace
- Incohérence d'occupation mémoire :
- En entrée : une trace journalière tableau ~70Mo
- En sortie, occupation de la mémoire ~753Mo
Profiling mémoire
Diagnostic du problème
Utilisation de la librairie python memory_profiler (v0.47)
from memory_profiler import profile
fp = open('/home/user/iworms_refact/memory_profile.log','w+')
@profile(stream=fp)
def get_input():
# Read stream from input files from input stream from wget -O -
#s = read('tmp.mseed')
try:
s = read(StringIO.StringIO(sys.stdin.read()))
return s
except Exception as e:
print e
sys.exit(0) # cigri should not abort
Profiling mémoire
Diagnostic du problème
Sortie memory_profile.log
Line # Mem usage Increment Line Contents
================================================
46 87.6 MiB 0.0 MiB @profile(stream=fp)
47 def get_input():
48 # Read stream from input files from input stream from wget -O -
49 #s = read('tmp.mseed')
50 87.6 MiB 0.0 MiB try:
51 #s = read(StringIO.StringIO(sys.stdin.read()))
52 172.5 MiB 84.9 MiB s = read("tmp.mseed")
53 172.5 MiB 0.0 MiB return s
54 except Exception as e:
55 print e
56 sys.exit(0) # cigri should not abort
Refactorisation obligatoire du script python
Text
Profiling mémoire
Diagnostic du problème
- Avec memory_profiler : usage mémoire totalement cohérent avec la taille des données (trace ~80Mo), occupation max ~200Mo
- Avec time :
bouttier@luke $ /usr/bin/time -f "%M" python2.7...
752556
So what?
Profiling mémoire
Numpy : sous le python, le C (ou le fortran)
- Interpolation, filtrage + python = numpy (à travers obspy)
- numpy ~ binding pythons vers routines en C ou fortran
- Besoin de transférer des tableaux python (numpy.ndarray) : allocation mémoire supplémentaire...
- Profondeur inaccessible pour memory_profiler
- Confirmation : dès que les routines numpy commentées, cohérence entre memory_profiler et time.
Profiling mémoire
Solution ?
- À part réécrire les algorithmes nécessaires en pur python, je ne vois pas (mais perte potentielle de performance CPU)
- Travail sur le garbage collector ?
- Est-ce une limitation ?
Réorganiser le code du pré-traitement ?
Limitations actuelles :
- Script bash pas forcément adapté (e.g. traitement des dates)
- Script python trop séquentiel : réécriture indispensable
- Tout séquentiel : seul un coeur CPU peut être exploité
- Faible modularité
- Sous-utilisation d'obspy (qui semble être fait pour ça)
Réorganiser le code du pré-traitement ?
Proposition : passer au "tout python"
- Traitement simplifié de la récupération des traces (langage + obspy)
- Lisibilité accrue (et donc maintenance et évolution plus faciles)
- Facile d'ajouter ou de modifier les traitements
- Possibilités de traitement parallèle de plusieurs traces
Réorganiser le code du pré-traitement ?
Petit exemple de parallélisation possible sur plusieurs traces
# Serial code
import numpy as np
def traces_interpolation(traces):
"""
Performs interpolation of an unique trace
- Input : List of 1D arrays (each corresponding to a trace)
- output : List of 1D arrays (interpolated traces)
"""
interp_traces = []
for trace in traces:
interp_traces.append(np.interp(traces[i],...))
return interp_traces
Réorganiser le code du pré-traitement ?
Petit exemple de parallélisation possible sur plusieurs traces
# Parallel code
import numpy as np
from joblib import Parallel, delayed
def parallel_traces_interpolation(traces):
"""
Performs parallel interpolation of multiple traces
- Input : List of 1D arrays (each corresponding to a trace)
- output : List of 1D arrays (interpolated traces)
"""
n_traces = len(traces)
parallelizer = Parallel(n_jobs=n_traces)
# Iterator which returns the function to execute for each task
tasks_iterator = (delayed(np.interp)(trace,...) for trace in traces)
result = parallelizer(tasks_iterator)
return result
Merci ! Questions ?
Séminaire iWorms
By pabouttier
Séminaire iWorms
- 891