2

いくつかの Common Lisp コードを最適化する助けを借りることができます。ログ ファイルからデータをクエリしようとしています。14.5k 行を超える行から最初の 50 行を引き出すのに 1 秒以上かかります。それを推定すると、ログ ファイルからデータを読み取るだけで 5 分近くかかることになります。さらに、ファイル全体が 14MB しかない場合、現在の実装では最初の 50 行で最大 50MB が割り当てられます。これで行きたいのは、データを 1 回読み取り、最小数のメモリ割り当てで解析することです。

私が目にしているパフォーマンス ヒットは、私のコードが原因であることを知っています。頭を悩ませているのは、発生している問題を最小限に抑えるためにコードをリファクタリングする方法です。WITH-INPUT-FROM-STRING を使用してストリームとして文字列にアクセスしようとしましたが、パフォーマンスは目立って変化しませんでした。

これは IIS ログであるため、一貫した構造になります。最初の 2 つのフィールドは日付と時刻です。必要に応じてデータの範囲を制限できるように、これを解析して数値にします。その後、ほとんどのフィールドは可変サイズになりますが、すべてスペースで区切られます。

マイ コードを使用: 8 つの使用可能な CPU コアで実行するのに 1,138,000 マイクロ秒 (1.138000 秒) かかりました。その期間中、1,138,807 マイクロ秒 (1.138807 秒) がユーザー モードで費やされ、0 マイクロ秒 (0.000000 秒) がシステム モードで費やされ、19,004 マイクロ秒 (0.019004 秒) が GC で費やされました。49,249,040 バイトのメモリが割り当てられています。

マイ コードなし: 8 つの使用可能な CPU コアで実行するのに 64,000 マイクロ秒 (0.064000 秒) かかりました。その期間中、62,401 マイクロ秒 (0.062401 秒) がユーザー モードで費やされ、0 マイクロ秒 (0.000000 秒) がシステム モードで費やされ、834,512 バイトのメモリが割り当てられました。

(defun read-date-time (hit)
  (let ((date-time (chronicity:parse (subseq hit 0 20))))
    (encode-universal-time (chronicity:sec-of date-time)
               (chronicity:minute-of date-time)
               (chronicity:hour-of date-time)
               (chronicity:day-of date-time)
               (chronicity:month-of date-time)
               (chronicity:year-of date-time))))

(defun parse-hit (hit)
  (unless (eq hit :eof)
    (cons (read-date-time hit)
          (split-sequence:split-sequence #\Space (subseq hit 20)))))


(time (gzip-stream:with-open-gzip-file (ins "C:\\temp\\test.log.gz") 
  (read-line ins nil :eof)
  (loop for i upto 50 
     do (parse-hit (read-line ins nil :eof)))))

私の最初の試みは非常に素朴なアプローチであり、コードを改善できる可能性があることを認識しているため、何らかの方向性を求めています。チュートリアルがこの質問に答えるより適切な方法である場合は、リンクを投稿してください。楽しむ

4

3 に答える 3

3

問題は Chronicity パッケージであり、内部で Local Time パッケージを使用しています。

これ:

   (encode-universal-time (chronicity:sec-of date-time)
           (chronicity:minute-of date-time)
           (chronicity:hour-of date-time)
           (chronicity:day-of date-time)
           (chronicity:month-of date-time)
           (chronicity:year-of date-time))))

あなたを押しつぶしています。

chronicity:month-ofを呼び出しますlocal-time:timestamp-month。そのコードを見ると:

 (nth-value 1
         (%timestamp-decode-date
          (nth-value 1 (%adjust-to-timezone timestamp timezone))))

したがって、ここでは基本的な日付 (整数のように見えます) を 2 回 (タイム ゾーンに対して 1 回、月に対して 1 回) デコードしています。

したがって、同じ日付をデコードし、同じ作業を各日付に対して 6 回行っています。そして、それらのルーチンは嵐を巻き起こしています。

また、subseq を 2 回呼び出しています。

したがって、この場合、日付の解析ロジックに集中する必要があるようです。一般的ではないものを使用してください。日付を検証する必要はなく (ログは正確であると推定されます)、エポックからの日数/秒/ミリ秒に変換する必要はありません。必要なのは個々の MDY、HMS データだけです。現在のパッケージでそのすべての作業を行っていますが、世界時を作成すると冗長になります。

また、おそらく、タイムゾーンは気にしません。

とにかく、それが問題の始まりです。まだ I/O の問題ではありません。

于 2013-10-19T22:34:47.873 に答える
0

コードにいくつかの変更を加え、実行にかかる時間と割り当てられるバイト数を大幅に削減しました。

新しいコード: 8 つの使用可能な CPU コアで実行するのに 65,000 マイクロ秒 (0.065000 秒) かかりました。その期間中、62,400 マイクロ秒 (0.062400 秒) がユーザー モードで費やされ、0 マイクロ秒 (0.000000 秒) がシステム モードで費やされ、2,001 マイクロ秒 (0.002001 秒) が GC で費やされました。1,029,024 バイトのメモリが割り当てられています。

(let ((date-time-string (make-array 20 :initial-element nil)))
  (defun read-date-time (hit)
    (read-sequence date-time-string hit :start 0 :end 20)
    (local-time:timestamp-to-universal (local-time:parse-timestring (map 'string #'code-char date-time-string) :date-time-separator #\Space))))

(defun parse-hit (hit)
  (cons (read-date-time hit) (split-sequence:split-sequence #\Space (read-line hit))))

(defun timeparse (lines)
  (time (gzip-stream:with-open-gzip-file (ins "C:\\temp\\test.log.gz")
      (read-line ins nil :eof) 
      (loop for i upto lines
         do (parse-hit ins)))))

解凍されたファイルは 14MB で、その中に約 14.5k 行あります。

ファイルの 10k 行を解析すると、8.5 秒で 178MB が割り当てられます。これは、gzip-stream ライブラリまたはその依存関係の 1 つに関係していると思います。

解析せずに、圧縮ファイルから 10,000 行の読み取り行を実行するだけで 8 秒かかり、140MB のメモリが割り当てられます。

解析せずに、圧縮されていないファイルから同じ 10k 行で行の読み取りを実行すると、約 1/10 秒かかり、28MB しか割り当てられません。

現在、gzip-stream のパフォーマンスについて私にできることはあまりないので、パフォーマンスが許容範囲を超えるまで我慢する必要があります。

ヘルプと推奨事項をありがとうございました。

于 2013-10-21T18:33:14.883 に答える