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 ?

Made with Slides.com