1

私はPythonスクリプト(Python 3.2を使用)を作成しています。このスクリプトは、ある時点で、辞書のキーごとに約800.000行のファイルを処理する必要があります。キーの数は約150.000です。ファイルの行は、次の形式の辞書です。

{'url': 'http://address.com/document/42/1998', 'referrer': 'http://address.com/search?&q=query1', 'session': '1', 'rank': 2, 'time': 1338447254}
{'url': 'http://address.com/document/55/17', 'referrer': 'http://address.com/search&q=query2', 'session': '1', 'rank': 2, 'time': 13384462462}

このファイルの各行について、いくつかの計算を行い、結果を保存する必要があります。辞書を読んで作業できるようにするために、evalを使用します。これにより、evalへの呼び出しが120.000.000.000になり、長い時間がかかります。したがって、私はこれを最適化する方法を探しています。

最適化のためのすべての可能な提案を持って来ることを歓迎します。すべてのビットが影響を与える可能性がありますが、私は主に評価とファイルの読み取り方法に関心があります。Atm。evalがより高速に実行される可能性がある他の方法を考えていますが、JSONに形式を読み取らせることができず、splitの使用はまだうまくいきません。また、ファイルから読み取る方法が最適化されている可能性があります。次のコードのメソッドを「with」と一緒に試しました(はるかに低速でしたが、消費されるメモリが少なくなりました)。また、マップを使用してメモリ内のファイルを読み取ってみました。

f_chunk = map(eval, codecs.open(chunk_file, "r", encoding="utf-8").readlines())

しかし、これも実際には機能しません。

とにかく、スクリプトの次の部分は重いものです。複数のプロセスで実行されます。

def mine(id, tmp_sessions, chunk_file, work_q, result_q, init_qsize):
    #f_chunk = map(eval, codecs.open(chunk_file, "r", encoding="utf-8").readlines())
    f_chunk = codecs.open(chunk_file, "r", encoding="utf-8").readlines()

    while True:
        try:

            k = work_q.get()
            if k == 'STOP':

                work_q.task_done()
                break # reached end of queue

        except Queue.Empty:
            break

        #with codecs.open(chunk_file, "r", encoding="utf-8") as f_chunk:
        for line in f_chunk:
            #try:
            jlog_nest = dict()
            jlog_nest = eval(line)

            #jlog_nest = json.loads(line)
            #jlog_nest = line
            #jlog_nest = defaultdict(line)


            if jlog_nest["session"] == k: # If session is the same
                query_nest = prepare_test_cases_lib.extract_query(jlog_nest["referrer"])

                for q in tmp_sessions[k]:

                    if q[0] == query_nest:
                        url = jlog_nest["url"]
                        rank = jlog_nest["rank"]
                        doc_id = prepare_test_cases_lib.extract_document_id(url)

                        # Increase number of hits on that document, and save its rank
                        if doc_id in q[1]:
                            q[1][doc_id][0] += 1
                            q[1][doc_id][1].append(rank)
                        else:
                            q[1][doc_id] = [1, [rank]]
            #except:
            #    print ("error",k)

        result_q.put((k, tmp_sessions[k]))
        work_q.task_done()

上記のコードを実行する前に、何が起こっているかを理解するのに役立つ場合は、tmp_sessionが次のようになります。

tmp_sessions: {'39': [['q7', {}], ['q2', {}]], '40': [['q2', {}]]}

以降:

tmp_sessions: {'39': [['q7', {}], ['q2', {'133378': [1, [2]]}]], '40': [['q2', {'133378': [1, [2]]}]]}

実際のデータのサブセットで、ファイルに562個のキーと2232行があり、pstatsを実行し、時間の降順で並べ替えました(これは一番上です)。

1284892 function calls in 76.810 seconds

Ordered by: cumulative time

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     8    0.000    0.000   77.985    9.748 {built-in method exec}
     8    1.607    0.201   77.978    9.747 prepare_hard_test_cases.py:29(mine)
1254384   75.051    0.000   76.220    0.000 {built-in method eval}
   562    0.008    0.000    0.050    0.000 queues.py:99(put)
     8    0.000    0.000    0.029    0.004 codecs.py:685(readlines)

このことから、確かに時間がかかっているのは評価のようです。

編集:提案されたように、私はliteral_evalで試しました。私は実際にこれが解決策を見つけようとしているのを見つけましたが、それはevalと同じだろうと思いました。私はそれを実行しました。同じ結果が得られますが、実行時間は非常に悪いです。

 50205868 function calls (37662028 primitive calls) in 121.494 seconds

 Ordered by: cumulative time

 ncalls  tottime  percall  cumtime  percall filename:lineno(function)
      8    0.001    0.000  121.494   15.187 {built-in method exec}
      8    0.008    0.001  121.493   15.187 <string>:1(<module>)
      8    4.935    0.617  121.485   15.186 prepare_hard_test_cases.py:29(mine)
1254384    5.088    0.000  116.425    0.000 ast.py:39(literal_eval)
1254384    1.098    0.000   71.432    0.000 ast.py:31(parse)
1254384   70.333    0.000   70.333    0.000 {built-in method compile}
13798224/1254384   22.996    0.000   39.336    0.000 ast.py:51(_convert)
7526304    8.539    0.000   23.042    0.000 ast.py:63(<genexpr>)
25087680    8.371    0.000    8.371    0.000 {built-in method isinstance}
    8    0.001    0.000    0.047    0.006 codecs.py:685(readlines)

編集2:私は今2つの新しいアプローチを試しました。1つ目は、各行からキーと値を手動で抽出し、作業する辞書を作成することです。これは私のテストセットで少し速く動作します:

51460252 function calls in 45.207 seconds

 Ordered by: cumulative time

 ncalls  tottime  percall  cumtime  percall filename:lineno(function)
      8    0.001    0.000   45.207    5.651 {built-in method exec}
      8    0.003    0.000   45.207    5.651 <string>:1(<module>)
      8    1.701    0.213   45.203    5.650 prepare_hard_test_cases.py:68(mine)
1254384    5.725    0.000   43.391    0.000 prepare_hard_test_cases.py:36(extractDict)
6271920   23.433    0.000   37.665    0.000 prepare_hard_test_cases.py:20(extractKeyValue)
18819074   11.308    0.000   11.308    0.000 {method 'find' of 'str' objects}
25092651    2.927    0.000    2.927    0.000 {built-in method len}

これは朗報ですが、ピクルスを使用した2番目のアプローチはさらに優れています。今私は得る:

30091 function calls in 5.285 seconds

Ordered by: cumulative time

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     8    0.000    0.000    5.285    0.661 {built-in method exec}
     8    0.003    0.000    5.285    0.661 <string>:1(<module>)
     8    0.173    0.022    5.281    0.660 prepare_hard_test_cases.py:68(mine)
   570    0.001    0.000    5.057    0.009 queues.py:113(get)
  2281    3.925    0.002    3.925    0.002 {method 'acquire' of '_multiprocessing.SemLock' objects}
   570    1.133    0.002    1.133    0.002 {method 'recv' of '_multiprocessing.PipeConnection' objects}
     8    0.029    0.004    0.029    0.004 {built-in method load}

時間が取れたら、このアプローチをフルセットに適用しようとします。

助言がありますか?

よろしく、キャスパー

4

1 に答える 1

3

試してみるべきですast.literal_eval()。それは仕事のために設計されており、おそらくより高速です。

eval()遅く、安全ではなく、一般的に悪い考えです。必要だと思われる場合は、周りを見回してください。99.99% の場合はそうではないことを保証します。

別の注意として:

f_chunk = codecs.open(chunk_file, "r", encoding="utf-8").readlines()
...

本当にあるべきです:

with open(chunk_file, "r", encoding="utf-8") as f_chunk:
    ...

ファイルは反復子であるため、使用readlines()するとプログラムのメモリ効率が低下します。を使用withすると、完了したらファイルが適切に閉じられます (3.x では、後者の追加機能をサポートするように更新されているため、open()代わりに使用できます)。codecs.open()

それ以外は、私が見る限り、データの各行は有効な JSON である必要があるため、jsonモジュールも機能するはずです。

于 2013-03-14T13:02:34.690 に答える