Python プロシージャハンドラーコードのプロファイリング¶
組み込みのコード・プロファイラーを使用することで、ハンドラーコードの実行に費やされた時間やメモリを調べることができます。プロファイラーは、プロシージャ・ハンドラーの各行の実行にどれだけの時間やメモリが費やされたかを示す情報を生成します。
プロファイラーを使用すると、以下のいずれかに焦点を当てたレポートを一度に作成できます。
1行あたりの実行時間 には、このレポートでは、1行の実行回数、実行にかかった時間などが表示されます。
1行あたりのメモリ使用量 には、このレポートでは1行あたりのメモリ使用量が表示されます。
プロファイラによって生成されたレポートは、指定したSnowflake 内部ユーザーステージ に保存されます。 GET_PYTHON_PROFILER_OUTPUT (SNOWFLAKE.CORE) システム関数を使用して、 プロファイラ出力 を読むことができます。
注釈
プロファイルはPythonの実行にパフォーマンスオーバーヘッドをもたらし、クエリのパフォーマンスに影響を与える可能性があります。これは開発、テスト、トラブルシューティングを目的としており、継続的な本番ワークロードでは有効にしないでください。
必要な権限¶
セッションレベルのパラメーターを設定しても権限チェックはトリガーされませんが、 ACTIVE_PYTHON_PROFILER セッションパラメーターを LINE または MEMORY のいずれかに設定してストアドプロシージャを実行すると、Snowflake は以下の権限をチェックします。
プロファイリング出力ステージの読み取り/書き込み権限が必要です。
プロファイルされたストアドプロシージャが 発呼者権限ストアドプロシージャ の場合、ストアドプロシージャで USAGE 権限を持つロールを使用する必要があります。
プロファイルされたストアドプロシージャが 所有者の権限のストアドプロシージャ の場合、そのストアドプロシージャで OWNERSHIP 権限のロールを使用する必要があります。
制限事項¶
ストアドプロシージャのみがサポートされています。UDFs のサポートはまだ可用性はありません。
再帰プロファイリングは非対応です。指定したモジュールのトップレベル関数のみがプロファイルされます。関数の内部で定義された関数はそうではありません。
snowflake.snowpark
API を介してクライアント側で作成されたストアドプロシージャのプロファイルはサポートされていません (たとえば、Session.sproc.register
から作成されたストアドプロシージャなど)。joblib
を通して並列に実行される Python 関数はプロファイルされません。システム定義のストアドプロシージャはプロファイルできません。出力はゼロです。
使用状況¶
プロファイラーを使用するようにセットアップしたら、ストアドプロシージャをコールしてプロファイラー出力を生成するだけで使用できます。プロシージャの実行が終了すると、プロファイラーの出力は指定したステージのファイルに書き出されます。システム関数を使用してプロファイル出力を取得できます。
以下の手順に従って、プロファイルをセットアップし、使用してください.
プロファイル出力を書き込む Snowflakeステージを指定します。
パラメーター PYTHON_PROFILER_TARGET_STAGE にステージの完全修飾名をセットします。
プロファイラーを有効にし、プロファイルの対象を指定します。
ACTIVE_PYTHON_PROFILER セッションパラメーターをセットします。
-
プロファイラーが有効になったら、ストアドプロシージャを呼び出します。
-
実行終了後、プロファイリング出力は
<query_id>_<sproc_name>.lprof
または<query_id>_<sproc_name>.mprof
というネーミングパターンで出力ステージにファイルとしてアップロードされます。
プロファイル出力を書き込むSnowflakeステージを指定します。¶
プロファイラーを実行する前に、レポートを保存するステージを指定する必要があります。ステージを指定するには、 PYTHON_PROFILER_TARGET_STAGE パラメーターにステージの完全修飾名をセットします。
セッションの間のみに出力を保存する仮ステージを使用します。
永続ステージを使用して、セッションの範囲外でプロファイル出力を保存します。
以下の例のコードは、プロファイラー出力を受け取るための仮の profiler_output
ステージ を作成します。
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";
プロファイラーを有効にし、プロファイルの対象を指定します。¶
ACTIVE_PYTHON_PROFILER セッション・パラメーターに、生成したいプロファイル・レポートの種類を指定する値をセットします。
プロファイルをライン使用アクティビティにフォーカスさせるには、以下のようにパラメーターを
LINE
(大文字と小文字を区別しない)値にセットします。ALTER SESSION SET ACTIVE_PYTHON_PROFILER = 'LINE';
プロファイルでメモリ使用アクティビティに焦点を当てるには、以下のようにパラメーターを
MEMORY
の値(大文字と小文字は区別されない)にセットします。ALTER SESSION SET ACTIVE_PYTHON_PROFILER = 'MEMORY';
ストアドプロシージャを呼び出します。¶
プロファイラーが有効になったら、ストアドプロシージャを呼び出します。
CALL YOUR_STORED_PROCEDURE();
デフォルトでは、プロファイラーがプロファイルするのはユーザーモジュールで定義されているメソッドです。プロファイルには他のモジュールも登録できます。詳細情報については、 プロファイル追加モジュール を参照してください。
プロファイル出力表示¶
実行終了後、プロファイリング出力は <query_id>_<sproc_name>.lprof
または <query_id>_<sproc_name>.mprof
というネーミングパターンで出力ステージにファイルとしてアップロードされます。
出力は、 SNOWFLAKE データベース のシステム関数 GET_PYTHON_PROFILER_OUTPUT からアクセスできます。
システム関数の署名の形式は以下の通りです。
SELECT SNOWFLAKE.CORE.GET_PYTHON_PROFILER_OUTPUT(<query_id>);
<query_id>
を、プロファイリングを有効にしたストアドプロシージャ・クエリのクエリ ID に置き換えてください。
出力ステージの出力ファイルに直接アクセスすることもできます。詳細情報については、 ステージングされたファイルの表示 を参照してください。
注釈
システム関数は、 PYTHON_PROFILER_TARGET_STAGE パラメーターで指定されたステージからプロファイリング出力ファイルを探します。
子ストアドプロシージャのプロファイル出力は親プロシージャの出力に追加されません。子ストアドプロシージャの出力を表示するには、子プロシージャのクエリ ID のシステム関数を明示的に呼び出します。
プロファイル用追加モジュールの追加¶
デフォルトではインクルードされていないモジュールを、プロファイルのためにインクルードすることができます。追加モジュールをプロファイルに含めるには、 PYTHON_PROFILER_MODULES パラメーターに含めたいモジュール名をセットします。
デフォルトでは、モジュールで定義されたメソッドがプロファイルされます。これらの方法には以下のようなものがあります。
ハンドラーメソッド
モジュールで定義されているメソッド
パッケージや他のモジュールからインポートされたメソッド。
以下の例では、 handler
、 helper
、 some_method
がデフォルトでプロファイルされます。
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):
...
$$;
PYTHON_PROFILER_MODULES パラメーターを持つモジュールのインクルード¶
PYTHON_PROFILER_MODULES パラメーターを使って、デフォルトでは含まれないようなプロファイルモジュールを含めることができます。この方法でモジュールをインクルードすると、そのモジュールから使用されるすべての関数がプロファイラの出力に含まれます。デフォルト値では、 PYTHON_PROFILER_MODULES パラメーターの値は空文字列 (''
) になります。この場合、プロファイルはインラインハンドラーコード (もしあれば) のみをプロファイルします。
モジュールをプロファイリングに含めるには、パラメーターの値としてモジュール名をカンマ区切りのリストで指定します。
ALTER SESSION SET PYTHON_PROFILER_MODULES = 'module_a, my_module';
ステージングハンドラーコードのプロファイル¶
インラインではなくステージングされたハンドラーコード (ヘルパー関数を含む) をプロファイルするには、 PYTHON_PROFILER_MODULES パラメーターを使用して、ステージングされたハンドラーを明示的に指定する必要があります。
デフォルトでは、 インラインではなく、 ステージングされたハンドラーコードはプロファイルされません --- つまり、ハンドラーモジュールが IMPORTS 句で指定されている場合です。
例えば、デフォルトでは、このプロシージャは詳細なプロファイリング出力を生成しません。
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');
ステージング・コードをプロファイリングに含めるには、 PYTHON_PROFILER_MODULES パラメーターの値として、ステージング・モジュール名をカンマ区切りのリストで指定します。
ALTER SESSION SET PYTHON_PROFILER_MODULES = 'test_python_import_main, test_python_import_module';
例¶
この例のコードは、プロファイラーを使用して行の使用状況のレポートを生成し、取得する方法を示しています。
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());
ライン・プロファイラーの出力は次のようになります。
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
メモリプロファイラーの出力は次のようになります。
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