7

現在、515MBのプレーンテキストファイル(過去4年間の毎日のファイル)を読み取るログパーサーがあります。私のコードは現在次のようになっています:http://gist.github.com/12978。私は(コードに見られるように)psycoを使用し、それをコンパイルして、コンパイルされたバージョンを使用しています。0.3秒ごとに約100行を実行しています。マシンは標準の15インチMacBookPro(2.4ghz C2D、2GB RAM)です。

これをより速く進めることは可能ですか、それとも言語/データベースの制限ですか?

4

5 に答える 5

10

プロファイリングに時間を無駄にしないでください。時間は常にデータベース操作にあります。できるだけ少なくします。最小限の挿入数です。

三つのこと。

1。Date、Hostname、および Person ディメンションを一致させるために何度も SELECT を実行しないでください。すべてのデータを Python ディクショナリに 1 回フェッチし、メモリ内で使用します。シングルトン選択を繰り返さないでください。パイソンを使用します。

二。更新しないでください。

具体的には、これをしないでください。これは 2 つの理由で悪いコードです。

cursor.execute("UPDATE people SET chats_count = chats_count + 1 WHERE id = '%s'" % person_id)

単純な SELECT COUNT(*) FROM ... に置き換えてください。更新してカウントをインクリメントしないでください。そこにある行を SELECT ステートメントで数えるだけです。[単純な SELECT COUNT または SELECT COUNT(DISTINCT) でこれを行うことができない場合は、一部のデータが欠落しています。データ モデルは常に正しい完全なカウントを提供する必要があります。更新しないでください。]

と。文字列置換を使用して SQL を構築しないでください。完全にばか。

なんらかの理由でSELECT COUNT(*)十分に高速でない場合 (何もできないことを行う前にまずベンチマーク)、カウントの結果を別のテーブルにキャッシュできます。すべてのロードの後。aSELECT COUNT(*) FROM whatever GROUP BY whateverを実行して、これをカウントのテーブルに挿入します。更新しないでください。これまで。

三。バインド変数を使用します。いつも。

cursor.execute( "INSERT INTO ... VALUES( %(x)s, %(y)s, %(z)s )", {'x':person_id, 'y':time_to_string(time), 'z':channel,} )

SQL は変更されません。バインドされた値は変更されますが、SQL は決して変更されません。これははるかに高速です。SQL ステートメントを動的に作成しないでください。一度もない。

于 2008-09-26T01:50:23.767 に答える
3

sqlステートメントでリテラル値の代わりにバインド変数を使用し、一意のsqlステートメントごとにカーソルを作成して、ステートメントが次に使用されるときに再解析する必要がないようにします。python db api docから:

データベース操作(クエリまたはコマンド)を準備して実行します。パラメータはシーケンスまたはマッピングとして提供され、操作で変数にバインドされます。変数はデータベース固有の表記法で指定されます(詳細については、モジュールのparamstyle属性を参照してください)。[5]

操作への参照はカーソルによって保持されます。同じ操作オブジェクトが再度渡されると、カーソルはその動作を最適化できます。これは、同じ操作が使用されているが、異なるパラメーターが(多くの場合)それにバインドされているアルゴリズムに最も効果的です。

常に常に常にバインド変数を使用します。

于 2008-09-25T23:29:56.910 に答える
3

for ループでは、「chats」テーブルに繰り返し挿入しているため、異なる値で実行されるバインド変数を含む 1 つの SQL ステートメントのみが必要です。したがって、これを for ループの前に置くことができます。

insert_statement="""
    INSERT INTO chats(person_id, message_type, created_at, channel)
    VALUES(:person_id,:message_type,:created_at,:channel)
"""

次に、実行する各 sql ステートメントの代わりに、これを配置します。

cursor.execute(insert_statement, person_id='person',message_type='msg',created_at=some_date, channel=3)

これにより、次の理由により、処理が高速になります。

  1. カーソル オブジェクトは、毎回ステートメントを再解析する必要はありません。
  2. 以前に作成したものを使用できるため、db サーバーは新しい実行計画を生成する必要はありません。
  3. バインド変数の特殊文字は、実行される sql ステートメントの一部ではないため、santitize() を呼び出す必要はありません。

注: 私が使用したバインド変数の構文は、Oracle 固有のものです。正確な構文については、psycopg2 ライブラリのドキュメントを確認する必要があります。

その他の最適化:

  1. ループの反復ごとに「UPDATE people SET chatscount」を増やしています。ユーザーをchat_countにマッピングする辞書を保持してから、見た合計数のステートメントを実行します。これは、すべてのレコードの後に​​データベースにアクセスするよりも高速になります。
  2. すべてのクエリでバインド変数を使用します。挿入ステートメントだけでなく、例としてそれを選択します。
  3. データベース検索を行うすべての find_*() 関数を変更して、結果をキャッシュするようにします。これにより、毎回データベースにアクセスする必要がなくなります。
  4. サイコは、多数の数値演算を実行する python プログラムを最適化します。スクリプトは IO コストが高く、CPU コストが高くないため、最適化があったとしてもあまり期待できません。
于 2008-09-25T23:59:39.290 に答える
2

Markが提案したように、バインディング変数を使用します。データベースは、各ステートメントを1回準備してから、実行ごとに「空白を埋める」必要があります。良い副作用として、文字列引用の問題(プログラムが処理していない)を自動的に処理します。

トランザクションをオンにし(まだオンになっていない場合)、プログラムの最後に1回のコミットを実行します。すべてのデータをコミットする必要があるまで、データベースはディスクに何も書き込む必要はありません。また、プログラムでエラーが発生した場合、どの行もコミットされないため、問題が修正されたらプログラムを再実行するだけで済みます。

log_hostname、log_person、およびlog_date関数は、テーブルに対して不要なSELECTを実行しています。適切なテーブル属性PRIMARYKEYまたはUNIQUEを作成します。次に、INSERTを実行する前にキーの存在を確認する代わりに、INSERTを実行します。個人/日付/ホスト名がすでに存在する場合、INSERTは制約違反により失敗します。(上記のように、単一のコミットでトランザクションを使用する場合、これは機能しません。)

または、プログラムの実行中にテーブルにINSERTするのが自分だけであることがわかっている場合は、メモリ内に並列データ構造を作成し、INSERTを実行している間それらをメモリ内に保持します。たとえば、プログラムの開始時に、テーブルから連想配列にすべてのホスト名を読み込みます。INSERTを実行するかどうかを知りたい場合は、配列ルックアップを実行するだけです。エントリが見つからない場合は、INSERTを実行し、配列を適切に更新します。(この提案は、トランザクションおよび単一のコミットと互換性がありますが、より多くのプログラミングが必要です。ただし、非常に高速になります。)

于 2008-09-26T00:20:36.680 に答える
1

@Mark Roddyが提供した多くのすばらしい提案に加えて、次のことを行ってください。

  • 使用しないreadlinesでください。ファイルオブジェクトを反復処理できます
  • executemanyではなく使用してexecuteみてください:単一の挿入ではなくバッチ挿入を実行してみてください。オーバーヘッドが少ないため、これは高速になる傾向があります。また、コミットの数を減らします
  • str.rstrip正規表現で改行を削除する代わりに、問題なく機能します

挿入をバッチ処理すると、一時的により多くのメモリが使用されますが、ファイル全体をメモリに読み込まない場合は問題ありません。

于 2008-09-26T00:26:21.503 に答える