Profilage du code du gestionnaire de procédure Python

Vous pouvez découvrir combien de temps ou de mémoire a été consacré à l’exécution du code de votre gestionnaire en utilisant le profileur de code intégré. Le profileur génère des informations décrivant le temps ou la mémoire consacrés à l’exécution de chaque ligne du gestionnaire de procédure.

À l’aide du profileur, vous pouvez générer des rapports axés sur l’un des éléments suivants à la fois :

  • Quantité de temps par ligne, dans lequel le rapport indique le nombre de fois qu’une ligne a été exécutée, la durée de l’exécution, etc.

  • Utilisation de la mémoire par ligne, dans lequel le rapport indique la quantité de mémoire consommée par ligne.

Le profileur enregistre le rapport généré dans une zone de préparation interne à l’utilisateur de Snowflake que vous spécifiez. Vous pouvez lire la sortie du profileur à l’aide de la fonction système GET_PYTHON_PROFILER_OUTPUT (SNOWFLAKE.CORE).

Note

Le profilage introduit une surcharge de performance lors de l’exécution de Python et peut affecter les performances de la requête. Il est destiné au développement, aux tests et au dépannage et ne doit pas être activé sur des charges de travail de production continue.

Privilèges requis

La définition du paramètre au niveau de la session ne déclenche pas de vérification des privilèges, mais lorsqu’une procédure stockée est exécutée avec le paramètre de session ACTIVE_PYTHON_PROFILER sur LINE ou MEMORY, Snowflake vérifie les privilèges suivants.

Limitations

  • Seules les procédures stockées sont prises en charge. Les UDFs ne sont pas encore prises en charge.

  • Le profilage récursif n’est pas pris en charge. Seules les fonctions de haut niveau des modules spécifiés sont profilées. Les fonctions définies à l’intérieur de fonctions ne le sont pas.

  • La prise en charge du profilage des procédures stockées créées côté client via l’API snowflake.snowpark n’est pas assurée (par exemple, les procédures stockées créées à partir de Session.sproc.register).

  • Les fonctions Python exécutées en parallèle sur joblib ne seront pas profilées.

  • Les procédures stockées définies par le système ne peuvent pas être profilées. Elles ne produiront aucune sortie.

Utilisation

Une fois l’ensemble des paramètres définis, vous pouvez utiliser le profileur en appelant simplement la procédure stockée pour générer la sortie du profileur. Une fois l’exécution de la procédure terminée, la sortie du profileur est écrite dans un fichier sur la zone de préparation que vous avez spécifiée. Vous pouvez récupérer la sortie du profileur à l’aide d’une fonction système.

Suivez les étapes suivantes pour configurer et utiliser le profileur :

  1. Indiquez la zone de préparation de Snowflake où la sortie du profil doit être écrite.

    Définissez le paramètre PYTHON_PROFILER_TARGET_STAGE sur le nom complet de la zone de préparation.

  2. Activez le profileur et indiquez sur quoi le profil doit se concentrer.

    Définissez les paramètres de session ACTIVE_PYTHON_PROFILER.

  3. Appelez la procédure stockée.

    Une fois le profil activé, appelez votre procédure stockée.

  4. Vue de la sortie du profilage.

    À la fin de l’exécution, la sortie de profilage sera importée sous forme d’un fichier dans la zone de préparation de sortie avec le modèle de dénomination <query_id>_<sproc_name>.lprof ou <query_id>_<sproc_name>.mprof.

Spécifiez la zone de préparation de Snowflake dans laquelle la sortie du profil doit être écrite

Avant d’exécuter le profileur, vous devez spécifier une zone de préparation dans laquelle son rapport sera enregistré. Pour spécifier la zone de préparation, définissez le paramètre PYTHON_PROFILER_TARGET_STAGE sur le nom complet de la zone de préparation.

  • Utilisez une zone de préparation temporaire pour stocker les sorties uniquement pour la durée de la session.

  • Utilisez une zone de préparation permanente pour conserver la sortie du profilage en dehors du champ d’application d’une session.

Le code de l’exemple suivant crée une zone de préparation temporaire profiler_output pour recevoir la sortie du profileur.

USE DATABASE my_database;
USE SCHEMA my_schema;

CREATE TEMPORARY STAGE profiler_output;
ALTER SESSION SET PYTHON_PROFILER_TARGET_STAGE = "my_database.my_schema.profiler_output";
Copy

Activer le profileur et spécifier sur quoi le profil doit se concentrer

Définissez le paramètre de session ACTIVE_PYTHON_PROFILER sur une valeur spécifiant le type de rapport de profil que vous souhaitez générer.

  • Pour que le profil se concentre sur l’activité d’utilisation de la ligne, définissez le paramètre sur la valeur LINE (insensible à la casse), comme indiqué ci-dessous :

    ALTER SESSION SET ACTIVE_PYTHON_PROFILER = 'LINE';
    
    Copy
  • Pour que le profil se concentre sur l’activité d’utilisation de la mémoire, définissez le paramètre sur la valeur MEMORY (insensible à la casse), comme indiqué ci-dessous :

    ALTER SESSION SET ACTIVE_PYTHON_PROFILER = 'MEMORY';
    
    Copy

Appeler la procédure stockée

Une fois le profil activé, appelez votre procédure stockée.

CALL YOUR_STORED_PROCEDURE();
Copy

Par défaut, le profileur profile les méthodes définies dans le module de l’utilisateur. Vous pouvez également enregistrer d’autres modules dans le profil. Pour plus d’informations, voir Modules supplémentaires de profil.

Voir la sortie du profilage

À la fin de l’exécution, la sortie de profilage sera importée sous forme d’un fichier dans la zone de préparation de sortie avec le modèle de dénomination <query_id>_<sproc_name>.lprof ou <query_id>_<sproc_name>.mprof.

La sortie est accessible via une fonction système GET_PYTHON_PROFILER_OUTPUT dans la base de données SNOWFLAKE.

Le format de la signature de la fonction système est le suivant :

SELECT SNOWFLAKE.CORE.GET_PYTHON_PROFILER_OUTPUT(<query_id>);
Copy

Remplacez <query_id> par l’ID de requête de la procédure stockée pour laquelle le profilage a été activé.

Vous pouvez également accéder directement au fichier de sortie sur la zone de préparation de sortie. Pour plus d’informations, voir Voir les fichiers en zone de préparation.

Note

La fonction système recherche les fichiers de sortie de profilage de la zone de préparation spécifiée avec le paramètre PYTHON_PROFILER_TARGET_STAGE.

La sortie du profilage pour les procédures stockées enfants n’est pas ajoutée à la sortie de la procédure parent. Pour voir la sortie d’une procédure stockée enfant, appelez explicitement la fonction système sur l’ID de requête de la procédure enfant.

Inclure des modules supplémentaires pour le profilage

Vous pouvez inclure pour le profilage des modules qui ne sont pas inclus par défaut. Pour inclure des modules supplémentaires pour le profilage, définissez le paramètre PYTHON_PROFILER_MODULES sur les noms des modules que vous souhaitez inclure.

Par défaut, les méthodes définies dans votre module seront profilées. Ces méthodes incluent ce qui suit :

  • La méthode du gestionnaire

  • Méthodes définies dans le module

  • Méthodes importées de paquets ou d’autres modules.

Dans l’exemple ci-dessous, handler, helper et some_method seront tous profilés par défaut.

CREATE OR REPLACE PROCEDURE my_sproc()
RETURNS VARIANT
LANGUAGE PYTHON
RUNTIME_VERSION = 3.10
PACKAGES = ('snowflake-snowpark-python', 'other_package')
HANDLER='handler'
AS $$
from other_package import some_method

def helper():
...

def handler(session):
...
$$;
Copy

Inclure des modules avec le paramètre PYTHON_PROFILER_MODULES

Vous pouvez utiliser le paramètre PYTHON_PROFILER_MODULES pour inclure des modules de profilage qui ne seraient pas inclus par défaut. Lorsque vous incluez un module de cette manière, toutes les fonctions utilisées à partir de ce module seront incluses dans la sortie du profileur. Par défaut, la valeur du paramètre PYTHON_PROFILER_MODULES est une chaîne vide (''), dans laquelle le profil ne profile que le code du gestionnaire en ligne, le cas échéant.

Pour inclure des modules dans le profilage, indiquez leur nom comme valeur du paramètre dans une liste séparée par des virgules, comme illustré ci-dessous.

ALTER SESSION SET PYTHON_PROFILER_MODULES = 'module_a, my_module';
Copy

Profiler le code du gestionnaire en zone de préparation

Pour profiler le code du gestionnaire qui est en zone de préparation plutôt qu’en ligne — y compris les fonctions d’aide — vous devez explicitement spécifier le gestionnaire en zone de préparation pour le profilage à l’aide du paramètre PYTHON_PROFILER_MODULES.

Par défaut, le profileur ne profile pas le code du gestionnaire qui est en zone de préparation, plutôt qu’en ligne — c’est-à-dire, lorsque le module du gestionnaire est spécifié avec la clause IMPORTS.

Par exemple, par défaut, cette procédure ne génère aucune sortie de profilage détaillé.

CREATE OR REPLACE PROCEDURE test_udf_1()
RETURNS STRING
LANGUAGE PYTHON
RUNTIME_VERSION = '3.8'
PACKAGES=('snowflake-snowpark-python')
HANDLER = 'test_python_import_main.my_udf'
IMPORTS = ('@stage1/test_python_import_main.py', '@stage2/test_python_import_module.py');
Copy

Pour inclure le code en zone de préparation dans le profilage, spécifiez les noms des modules en zone de préparation comme valeur du paramètre PYTHON_PROFILER_MODULES dans une liste séparée par des virgules, comme illustré ci-dessous.

ALTER SESSION SET PYTHON_PROFILER_MODULES = 'test_python_import_main, test_python_import_module';
Copy

Exemple

Le code de cet exemple illustre comment utiliser le profileur pour générer et récupérer un rapport sur l’utilisation des lignes.

CREATE OR REPLACE PROCEDURE last_n_query_duration(last_n number, total number)
RETURNS string
LANGUAGE PYTHON
RUNTIME_VERSION=3.8
PACKAGES=('snowflake-snowpark-python')
HANDLER='main'
AS
$$
import snowflake.snowpark.functions as funcs

def main(session, last_n, total):
  # create sample dataset to emulate id + elapsed time
  session.sql('''
  CREATE OR REPLACE TABLE sample_query_history (query_id INT, elapsed_time FLOAT)
  ''').collect()
  session.sql('''
  INSERT INTO sample_query_history
  SELECT
  seq8() AS query_id,
  uniform(0::float, 100::float, random()) as elapsed_time
  FROM table(generator(rowCount => {0}));'''.format(total)).collect()

  # get the mean of the last n query elapsed time
  df = session.table('sample_query_history').select(
    funcs.col('query_id'),
    funcs.col('elapsed_time')).limit(last_n)

  pandas_df = df.to_pandas()
  mean_time = pandas_df.loc[:, 'ELAPSED_TIME'].mean()
  del pandas_df
  return mean_time
$$;

CREATE TEMPORARY STAGE profiler_output;
ALTER SESSION SET PYTHON_PROFILER_TARGET_STAGE = "my_database.my_schema.profiler_output";
ALTER SESSION SET ACTIVE_PYTHON_PROFILER = 'LINE';

-- Sample 1 million from 10 million records
CALL last_n_query_duration(1000000, 10000000);

SELECT SNOWFLAKE.CORE.GET_PYTHON_PROFILER_OUTPUT(last_query_id());
Copy

La sortie du profil de ligne ressemblera à ceci :

Handler Name: main
Python Runtime Version: 3.8
Modules Profiled: ['main_module']
Timer Unit: 0.001 s

Total Time: 8.96127 s
File: _udf_code.py
Function: main at line 4

Line #      Hits        Time  Per Hit   % Time  Line Contents
==============================================================
    4                                           def main(session, last_n, total):
    5                                               # create sample dataset to emulate id + elapsed time
    6         1        122.3    122.3      1.4      session.sql('''
    7                                                   CREATE OR REPLACE TABLE sample_query_history (query_id INT, elapsed_time FLOAT)''').collect()
    8         2       7248.4   3624.2     80.9      session.sql('''
    9                                               INSERT INTO sample_query_history
    10                                               SELECT
    11                                               seq8() AS query_id,
    12                                               uniform(0::float, 100::float, random()) as elapsed_time
    13         1          0.0      0.0      0.0      FROM table(generator(rowCount => {0}));'''.format(total)).collect()
    14
    15                                               # get the mean of the last n query elapsed time
    16         3         58.6     19.5      0.7      df = session.table('sample_query_history').select(
    17         1          0.0      0.0      0.0          funcs.col('query_id'),
    18         2          0.0      0.0      0.0          funcs.col('elapsed_time')).limit(last_n)
    19
    20         1       1528.4   1528.4     17.1      pandas_df = df.to_pandas()
    21         1          3.2      3.2      0.0      mean_time = pandas_df.loc[:, 'ELAPSED_TIME'].mean()
    22         1          0.3      0.3      0.0      del pandas_df
    23         1          0.0      0.0      0.0      return mean_time

La sortie du profileur de mémoire se présente comme suit :

ALTER SESSION SET ACTIVE_PYTHON_PROFILER = 'MEMORY';

Handler Name: main
Python Runtime Version: 3.8
Modules Profiled: ['main_module']
File: _udf_code.py
Function: main at line 4

Line #   Mem usage    Increment  Occurrences  Line Contents
=============================================================
    4    245.3 MiB    245.3 MiB           1   def main(session, last_n, total):
    5                                             # create sample dataset to emulate id + elapsed time
    6    245.8 MiB      0.5 MiB           1       session.sql('''
    7                                                 CREATE OR REPLACE TABLE sample_query_history (query_id INT, elapsed_time FLOAT)''').collect()
    8    245.8 MiB      0.0 MiB           2       session.sql('''
    9                                             INSERT INTO sample_query_history
    10                                             SELECT
    11                                             seq8() AS query_id,
    12                                             uniform(0::float, 100::float, random()) as elapsed_time
    13    245.8 MiB      0.0 MiB           1       FROM table(generator(rowCount => {0}));'''.format(total)).collect()
    14
    15                                             # get the mean of the last n query elapsed time
    16    245.8 MiB      0.0 MiB           3       df = session.table('sample_query_history').select(
    17    245.8 MiB      0.0 MiB           1           funcs.col('query_id'),
    18    245.8 MiB      0.0 MiB           2           funcs.col('elapsed_time')).limit(last_n)
    19
    20    327.9 MiB     82.1 MiB           1       pandas_df = df.to_pandas()
    21    328.9 MiB      1.0 MiB           1       mean_time = pandas_df.loc[:, 'ELAPSED_TIME'].mean()
    22    320.9 MiB     -8.0 MiB           1       del pandas_df
    23    320.9 MiB      0.0 MiB           1       return mean_time