Profiling von Python-Handler-Code

Mit dem integrierten Code-Profiler können Sie herausfinden, wie viel Zeit oder Speicher für die Ausführung Ihres Handler-Codes aufgewendet wurde. Der Profiler generiert Informationen, die beschreiben, wie viel Zeit oder Speicher für die Ausführung jeder Zeile des Prozedur-Handlers aufgewendet wurde.

Mit dem Profiler können Sie Berichte erstellen, die sich jeweils auf einen der folgenden Punkte konzentrieren:

  • Zeitaufwand pro Zeile – hier zeigt der Bericht die Anzahl der Ausführungen einer Zeile, die Ausführungsdauer usw. an.

  • Speicherverbrauch pro Zeile – hier zeigt der Bericht den verbrauchten Speicher pro Zeile an.

Der Profiler speichert den erstellten Bericht in einem von Ihnen festgelegten internen Benutzer-Stagingbereich in Snowflake. Sie können die Profiler-Ausgabe mit der Systemfunktion GET_PYTHON_PROFILER_OUTPUT (SNOWFLAKE.CORE) lesen.

Bemerkung

Profiling führt zu einem Leistungsaufwand bei der Python-Ausführung und kann die Leistung der Abfrage beeinträchtigen. Es ist für die Entwicklung, das Testen und die Problembehandlung vorgesehen und sollte nicht für kontinuierliche Produktions-Workloads aktiviert werden.

Erforderliche Berechtigungen

Die Einstellung des Parameters auf Sitzungsebene löst keine Berechtigungsprüfung aus, aber wenn eine gespeicherte Prozedur mit dem Sitzungsparameter ACTIVE_PYTHON_PROFILER entweder auf LINE oder MEMORY ausgeführt wird, überprüft Snowflake die folgenden Berechtigungen.

  • Sie müssen über Lese-/Schreibberechtigungen für den Stagingbereich verfügen.

  • Wenn es sich bei der profilierten gespeicherten Prozedur um eine gespeicherte Prozedur mit den Aufruferrechten handelt, müssen Sie eine Rolle mit der Berechtigung USAGE für die gespeicherte Prozedur verwenden.

  • Wenn es sich bei der profilierten gespeicherten Prozedur um eine gespeicherte Prozedur mit Eigentümerreichten handelt, müssen Sie eine Rolle mit der Berechtigung OWNERSHIP für die gespeicherte Prozedur verwenden.

Einschränkungen

  • Es werden nur gespeicherte Prozeduren unterstützt. UDFs-Unterstützung ist noch nicht verfügbar.

  • Rekursive Profilerstellung wird nicht unterstützt. Nur die Top-Level-Funktionen der angegebenen Module werden profiliert. Bei Funktionen, die innerhalb von Funktionen definiert sind, ist dies nicht der Fall.

  • Profiling von gespeicherten Prozeduren, die auf der Client-Seite über snowflake.snowpark API erstellt wurden, wird nicht unterstützt (z. B. gespeicherte Prozeduren, die von Session.sproc.register erstellt wurden).

  • Python-Funktionen, die parallel über joblib laufen, werden nicht profiliert.

  • Systemdefinierte gespeicherte Prozeduren können nicht profiliert werden. Sie produzieren keine Ausgabe.

Verwendung

Sobald Sie den Profiler für die Verwendung eingerichtet haben, können Sie ihn verwenden, indem Sie einfach die gespeicherte Prozedur aufrufen, um die Profiler-Ausgabe zu generieren. Nachdem die Prozedur beendet ist, wird die Profiler-Ausgabe in eine Datei im Stagingbereich geschrieben, den Sie angeben. Sie können die Profiler-Ausgabe mit einer Systemfunktion abrufen.

Folgen Sie diesen Schritten, um den Profiler einzurichten und zu verwenden:

  1. Geben Sie den Snowflake Stagingbereich an, in den die Profilausgabe geschrieben werden soll.

    Setzen Sie den Parameter PYTHON_PROFILER_TARGET_STAGE auf den vollqualifizierten Namen des Stagingbereichs.

  2. Aktivieren Sie den Profiler und geben Sie an, worauf sich das Profil konzentrieren soll.

    Legen Sie den Sitzungsparameter ACTIVE_PYTHON_PROFILER fest.

  3. Rufen Sie die gespeicherte Prozedur auf.

    Nachdem der Profiler aktiviert ist, rufen Sie Ihre gespeicherte Prozedur auf.

  4. Profiling-Ausgabe anzeigen.

    Am Ende der Ausführung wird die Profiling-Ausgabe als Datei in den Stagingbereich hochgeladen, mit dem Namensmuster <query_id>_<sproc_name>.lprof oder <query_id>_<sproc_name>.mprof.

Geben Sie den Snowflake-Stagingbereich an, in den die Profilausgabe geschrieben werden soll

Bevor Sie den Profiler starten, müssen Sie einen Stagingbereich angeben, in dem der Bericht gespeichert werden soll. Um den Stagingbereich anzugeben, setzen Sie den Parameter PYTHON_PROFILER_TARGET_STAGE auf den vollqualifizierten Namen des Stagingbereichs.

  • Verwenden Sie einen temporären Stagingbereich, um die Ausgabe nur für die Dauer der Sitzung zu speichern.

  • Verwenden Sie einen permanenten Stagingbereich, um die Profiler-Ausgabe außerhalb einer Sitzung aufzubewahren.

Der Code im folgenden Beispiel erstellt einen temporären Stagingbereich profiler_output, der die Profiler-Ausgabe empfängt.

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

Aktivieren Sie den Profiler und legen Sie fest, worauf sich das Profil konzentrieren soll.

Setzen Sie den Sitzungsparameter ACTIVE_PYTHON_PROFILER auf einen Wert, der angibt, welche Art von Profilbericht Sie erstellen möchten.

  • Wenn Sie möchten, dass sich das Profil auf die Aktivität der Leitungsnutzung konzentriert, setzen Sie den Parameter auf den Wert LINE (unterscheidet nicht zwischen Groß-/Kleinschreibung), wie unten gezeigt:

    ALTER SESSION SET ACTIVE_PYTHON_PROFILER = 'LINE';
    
    Copy
  • Wenn Sie möchten, dass sich das Profil auf die Aktivität der Speichernutzung konzentriert, setzen Sie den Parameter auf den Wert MEMORY (unterscheidet nicht zwischen Groß-/Kleinschreibung), wie unten gezeigt:

    ALTER SESSION SET ACTIVE_PYTHON_PROFILER = 'MEMORY';
    
    Copy

Rufen Sie die gespeicherte Prozedur auf:

Nachdem der Profiler aktiviert ist, rufen Sie Ihre gespeicherte Prozedur auf.

CALL YOUR_STORED_PROCEDURE();
Copy

Standardmäßig erstellt der Profiler ein Profil der Methoden, die im Modul des Benutzers definiert sind. Sie können auch andere Module im Profil registrieren. Weitere Informationen finden Sie unter Profil Zusätzliche Module.

Profiling-Ausgabe ansehen

Am Ende der Ausführung wird die Profiling-Ausgabe als Datei in den Stagingbereich hochgeladen, mit dem Namensmuster <query_id>_<sproc_name>.lprof oder <query_id>_<sproc_name>.mprof.

Die Ausgabe kann über eine Systemfunktion GET_PYTHON_PROFILER_OUTPUT in der Datenbank SNOWFLAKE abgerufen werden.

Das Format der Signatur der Systemfunktion ist wie folgt:

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

Ersetzen Sie <query_id> durch die Abfrage-ID der Abfrage der gespeicherten Prozedur, für die Profiling aktiviert wurde.

Sie können auch direkt auf die Ausgabedatei im Stagingbereich zugreifen. Weitere Informationen finden Sie unter Anzeigen von Stagingdateien.

Bemerkung

Die Systemfunktion sucht nach Profiling-Ausgabedateien aus dem mit dem Parameter PYTHON_PROFILER_TARGET_STAGE angegebenen Stagingbereich.

Die Profiling-Ausgabe für untergeordnete gespeicherte Prozeduren wird nicht an die Ausgabe der übergeordneten Prozedur angehängt. Um die Ausgabe für eine untergeordnete gespeicherte Prozedur anzuzeigen, rufen Sie die Systemfunktion für die Abfrage-ID der untergeordneten Prozedur explizit auf.

Einschließlich zusätzlicher Module für die Profilerstellung

Sie können für die Profilerstellung Module einschließen, die nicht standardmäßig eingeschlossen sind. Um zusätzliche Module für die Profilerstellung einzubeziehen, setzen Sie den Parameter PYTHON_PROFILER_MODULES auf die Namen der Module, die Sie einbeziehen möchten.

Standardmäßig werden die in Ihrem Modul definierten Methoden profiliert. Dazu gehören die folgenden Methoden:

  • Der Methoden-Handler

  • Im Modul definierte Methoden

  • Aus Paketen oder anderen Modulen importierte Methoden

Im folgenden Beispiel werden die Profile handler, helper und some_method standardmäßig erstellt.

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

Einbindung von Modulen mit dem Parameter PYTHON_PROFILER_MODULES

Sie können den Parameter PYTHON_PROFILER_MODULES verwenden, um Module für die Profilerstellung einzubeziehen, die standardmäßig nicht einbezogen werden. Wenn Sie ein Modul auf diese Weise einbinden, werden alle Funktionen, die von diesem Modul verwendet werden, in die Profiler-Ausgabe aufgenommen. Standardmäßig ist der Wert des Parameters PYTHON_PROFILER_MODULES eine leere Zeichenfolge (''). In diesem Fall würde das Profil nur Inline-Handler-Code profilieren, falls vorhanden.

Um Module für das Profiling einzubeziehen, geben Sie deren Namen als Wert des Parameters in einer durch Kommas getrennten Liste an, wie unten dargestellt.

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

Profiling von Stagingbereich-Handler-Code

Um Handler-Code zu profilieren, der nicht inline, sondern im Stagingbereich liegt – einschließlich Hilfsfunktionen –, müssen Sie den Stagingbereich für das Profiling explizit mit dem Parameter PYTHON_PROFILER_MODULES angeben.

Standardmäßig erstellt der Profiler kein Profil für Handler-Code, der im Stagingbereich und nicht im Inlinebereich liegt –d. h. wenn das Handler-Modul mit der IMPORTS-Klausel angegeben ist.

Standardmäßig erzeugt diese Prozedur zum Beispiel keine detaillierten Profiling-Ausgaben.

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

Um Stagingcode für das Profiling einzubeziehen, geben Sie die Namen der Stagingmodule als Wert des Parameters PYTHON_PROFILER_MODULES in einer durch Komma getrennten Liste an, wie unten dargestellt.

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

Beispiel

Der Code in diesem Beispiel veranschaulicht, wie Sie den Profiler verwenden, um einen Bericht über die Zeilennutzung zu erstellen und abzurufen.

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

Die Ausgabe des Zeilen-Profilers sieht wie folgt aus:

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

Die Ausgabe des Speicher-Profilers sieht wie folgt aus:

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