誰が私の種族を殺しましたか?または、csv列の固有値を効率的に計算する方法

誰が私の種族を殺しましたか?または、csv列の固有値を効率的に計算する方法

私は160,353,104行を含むファイルにいくつかの一意の行があるかどうかを調べるためにいくつかの処理を行っています。これは私のパイプとstderr出力です。

$ tail -n+2 2022_place_canvas_history.csv | cut -d, -f2 | tqdm --total=160353104 |\
  sort -T. -S1G | tqdm --total=160353104 | uniq -c | sort -hr > users

100%|████████████████████████████| 160353104/160353104 [0:15:00<00:00, 178051.54it/s]
 79%|██████████████████████      | 126822838/160353104 [1:16:28<20:13, 027636.40it/s]

zsh: done tail -n+2 2022_place_canvas_history.csv | cut -d, -f2 | tqdm --total=160353104 | 
zsh: killed sort -T. -S1G | 
zsh: done tqdm --total=160353104 | uniq -c | sort -hr > users

私のコマンドラインPS1またはPS2は、パイプ内のすべてのプロセスの戻りコードを印刷します。✔ 0|0|0|KILL|0|0|0最初の文字は緑色のチェックマークで、最後のプロセスが0(成功)を返したことを示します。他の数字は、同じ順序の各パイプラインプロセスの戻りコードです。それで、4番目のコマンドがステータスを取得したKILLことを確認しました。これはsort -T. -S1G、ローカルディレクトリを一時記憶域に設定し、最大1GiBまでバッファリングするソートコマンドです。

問題は、KILLを返す理由が何であるかです。これはKILL SIGN何かが送信されたことを意味しますか? 「誰が殺したのか」知る方法はありますか?

修正する

読んだ後マーカス・ミュラーの回答、まずSqliteにデータをロードしてみました。

そのため、CSVベースのデータストリームを使用しないでください。シンプル

sqlite3 place.sqlite

そしてそのシェルで(CSVはSQLiteが列を決定するために使用できるヘッダー行を持っていると仮定します)(もちろん$ second_column_nameを列名に置き換えます)

.import 022_place_canvas_history.csv canvas_history --csv
SELECT $second_column_name, count($second_column_name)   FROM canvas_history 
GROUP BY $second_column_name;

時間がかかり、処理に任せて別のことに行きました。他の段落についてもっと考えましたが、マーカス・ミュラーの回答:

各値が2番目の列にどのくらいの頻度で表示されるかを知りたいだけです。以前のソートは、ツール(uniq -c)が不都合であり、以前に行をソートする必要があるためです(実際には妥当な理由はありません。値とその頻度のマップを保持して増やすことができることは実装されていません)。

だから私はそれを実現できると思いました。私のコンピュータに戻ったとき、SqliteのインポートプロセスがSSH Broken Pipのために長い間データを転送していないと考え、接続を閉じて停止しました。まあ、これはdict / map / hashtableを使ってカウンタを実装するのに最適な機会です。だから、次のdistinctファイルを書きました。

#!/usr/bin/env python3
import sys

conter = dict()

# Create a key for each distinct line and increment according it shows up. 
for l in sys.stdin:
    conter[l] = conter.setdefault(l, 0) + 1 # After Update2 note: don't do this, do just `couter[l] = conter.get(l, 0) + 1`

# Print entries sorting by tuple second item ( value ), in reverse order
for e in sorted(conter.items(), key=lambda i: i[1], reverse=True):
    k, v = e
    print(f'{v}\t{k}')

だから私は次のコマンドパイプラインを使っていました。

tail -n+1 2022_place_canvas_history.csv | cut -d, -f2 | tqdm --total=160353104 | ./distinct > users2

非常に迅速に進行し、tqdm30分未満になると予想されますが、99%に入ると徐々に遅くなります。このプロセスは、約1.7GIB程度の多くのRAMを使用します。私がこのデータを処理しているマシン、つまり十分なストレージがあるマシンは、RAMが2GiBで、ストレージが約1TiBに過ぎないVPSです。私の考えでは、この膨大な量のメモリを処理する必要があるか、スワッピングなどの作業を実行する必要があるため、速度が遅すぎるようです。とにかく待ち続け、ついにtqdmで100%に達したとき、すべてのデータがプロセスに./distinct送信され、数秒後に次のような出力が得られました。

160353105it [30:21, 88056.97it/s]                                                                                            
zsh: done       tail -n+1 2022_place_canvas_history.csv | cut -d, -f2 | tqdm --total=160353104 | 
zsh: killed     ./distinct > users2

今回はメモリ不足キラーによるものがほぼ確実です。マーカス・ミュラーの回答TLDRセクション。

だから、私はちょうど確認しましたが、このコンピュータではスワップが有効になっていません。詳細については、dmcryptとLVMを使用して設定を完了してから無効にしてください。私の答え

だから私の考えは、LVMスワップパーティションを有効にしてもう一度実行することでした。また、ある時点で10GiB RAMを使用するtqdmを見たようです。ただし、エラーが表示されるか、出力が10MiBしか表示されないため、出力が混乱していると確信していますbtop。 tqdmがnewを読み込んでいるので、それほど多くのメモリを使用しているとは思わないでください\n

この問題に対するStéphane Chazelasのコメントでは、彼らは次のように述べています。

システムログを見ることができます。

もっと知りたいのですが、Journalctlで何かを探す必要がありますか?では、どうすればよいですか?

とにかく、マーカス・ミュラーの回答つまり、CSVをSqliteにロードするのは、おそらくこれまでで最も賢明なソリューションです。これは、データをさまざまな方法で操作でき、メモリ不足なしでこのデータをインポートするための賢明な方法がいくつかあるためです。

しかし、今プロセスが終了した理由を見つける方法が疑問に思います。私は私のプロセスを理解したいからです。sort -T. -S1G今私のプロセスを理解したいと思います./distinct。最後のことは、ほぼ確実にメモリに関連しているということです。それでは、ログを確認してこれらのプロセスが終了した理由を確認するにはどうすればよいですか?

アップデート2

だからSWAPパーティションを有効にしてマーカス・ミュラーこの質問に対するコメントは次のとおりです。 Python collection.Counterを使用してください。だから私の新しいコード(distinct2)は次のようになります。

#!/usr/bin/env python3
from collections import Counter
import sys

print(Counter(sys.stdin).most_common())

だから私は走った。Gnu画面パイプの破損が再発生しても、次のパイプで実行するのではなくセッションを再開できます。

tail -n+1 2022_place_canvas_history.csv | cut -d, -f2 | tqdm --total=160353104 --unit-scale=1 | ./distinct2 | tqdm --unit-scale=1 > users5

これにより、次のような結果が得られます。

160Mit [1:07:24, 39.6kit/s]
1.00it [7:08:56, 25.7ks/it]

ご覧のとおり、データの並べ替えにはデータを数えるよりも時間がかかります。もう1つは、tqdm出力の2行目に1.00itしか表示されないことです。これは1行しかないことを意味します。だからheadを使ってuser5ファイルを確認しました。

head -c 150 users5 
[('kgZoJz//JpfXgowLxOhcQlFYOCm8m6upa6Rpltcc63K6Cz0vEWJF/RYmlsaXsIQEbXrwz+Il3BkD8XZVx7YMLQ==\n', 795), ('JMlte6XKe+nnFvxcjT0hHDYYNgiDXZVOkhr6KT60EtJAGa

ご覧のとおり、タプル全体のリストを1行に印刷します。この問題を解決するために、以下のように古いsedを使用しましたsed 's/),/)\n/g' users5 > users6。その後、headを使用してusers6コンテンツを確認しましたが、出力は次のようになります。

$ head users6
[('kgZoJz/...c63K6Cz0vEWJF/RYmlsaXsIQEbXrwz+Il3BkD8XZVx7YMLQ==\n', 795)
 ('JMlte6X...0EtJAGaezxc4e/eah6JzTReWNdTH4fLueQ20A4drmfqbqsw==\n', 781)
 ('LNbGhj4...apR9YeabE3sAd3Rz1MbLFT5k14j0+grrVgqYO1/6BA/jBfQ==\n', 777)
 ('K54RRTU...NlENRfUyJTPJKBC47N/s2eh4iNdAKMKxa3gvL2XFqCc9AqQ==\n', 767)
 ('8USqGo1...1QSbQHE5GFdC2mIK/pMEC/qF1FQH912SDim3ptEFkYPrYMQ==\n', 767)
 ('DspItMb...abcd8Z1nYWWzGaFSj7UtRC0W75P7JfJ3W+4ne36EiBuo2YQ==\n', 766)
 ('6QK00ig...abcfLKMUNur4cedRmY9wX4vL6bBoV/JW/Gn6TRRZAJimeLw==\n', 765)
 ('VenbgVz...khkTwy/w5C6jodImdPn6bM8izTHI66HK17D4Bom33ZrwuGQ==\n', 758)
 ('jjtKU98...Ias+PeaHE9vWC4g7p2KJKLBdjKvo+699EgRouCbeFjWsjKA==\n', 730)
 ('VHg2OiSk...3c3cr2K8+0RW4ILyT1Bmot0bU3bOJyHRPW/w60Y5so4F1g==\n', 713)

後で作業しても十分です。さて、確認してからアップデートを追加する必要があるようです。誰が私の種族を殺しましたか?dmesg または Journalctl を使用します。また、このスクリプトをより迅速に作成する方法があるかどうかを知りたいです。スレッドプールを作成しますが、Pythonのdictの振る舞いをチェックする必要があるかもしれません、私が計算する列は固定幅の文字列なので、他のデータ構造も考慮する必要があり、リストを使用して他のuser_hashの頻度を保存することもできます。私は以前の実装とほぼ同じ辞書であるCounterのPython実装も読みましたが、justusedの代わりにdict.setdefaultこの場合実際にdict[key] = dict.get(key, 0) + 1誤用する必要はありません。setdefault

アップデート3

だから私はウサギの洞窟に陥り、目標に対する集中力を完全に失いました。 CやRustを書くなど、より速いソートを探し始めましたが、処理したいデータがすでに処理されていることに気づきました。ここでは、dmesg出力とPythonスクリプトの最後のヒントを紹介します。ヒント:出力をソートするためにgnuソートツールを使用するよりも、計算にdictまたはCounterを使用する方が良いかもしれません。並べ替えはPythonの並べ替え機能よりも速くなる可能性があります。

dmesgに関してメモリを見つけるのはとても簡単です。文字列を検索するのではなく、戻るのではなく最後までsudo dmesg | less押すだけです。そのうちの2つを見つけました。 1つは私のPythonスクリプト用で、もう1つは私の種類用ですが、この問題を引き起こすことです。出力は次のとおりです。G?Out

[1306799.058724] Out of memory: Killed process 1611241 (sort) total-vm:1131024kB, anon-rss:1049016kB, file-rss:0kB, shmem-rss:0kB, UID:1000 pgtables:2120kB oom_score_adj:0
[1306799.126218] oom_reaper: reaped process 1611241 (sort), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
[1365682.908896] Out of memory: Killed process 1611945 (python3) total-vm:1965788kB, anon-rss:1859264kB, file-rss:0kB, shmem-rss:0kB, UID:1000 pgtables:3748kB oom_score_adj:0
[1365683.113366] oom_reaper: reaped process 1611945 (python3), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB

それはすべてです。今まで助けてくれてありがとう。他の人にも役立つことを願っています。

ベストアンサー1

重要な要約:一時ファイルのメモリ不足キラーまたはディスク容量不足キラーsort。推奨事項:さまざまなツールを使用してください。


sort.cこれでGNU coreutils 'に移動しました。これは-S 1G、単にsortプロセスが1 GBのメモリブロックを割り当てようとし、これが不可能な場合、ますます小さいサイズにフォールバックすることを意味します。

バッファが使い果たされたら、ソートされた行を保存するための一時ファイルを作成し、メモリ内の次の入力ブロックをソートします。

すべての入力が消費された後、sort2つの一時ファイルが1つの一時ファイルにマージ/ソートされ(mergesortスタイル)、すべての一時ファイルはマージのために完全にソートされた出力が生成されるまでマージされ続けますstdout

これは、利用可能なメモリよりも大きな入力を並べ替えることができるという意味なので、賢いです。

/tmp/あるいは、これらの一時ファイル自体がRAM(通常はRAM専用ファイルシステム)に保存されていないシステムでは、tmpfsこれは賢明です。したがって、これらの一時ファイルの作成は、保存しようとしているRAMを占有し、RAMが不足します。ファイルには1億6000万行があり、Googleで検索した結果、11GBの非圧縮データが表示されます。

sort使用する一時ディレクトリを変更して、この問題を解決するのに「ヘルプ」を与えることができます。すでにこれを実行して、一時-T.ファイルを現在のディレクトリに配置しました。スペースが足りませんか?それとも現在のディレクトリはtmpfs似ていますか?

適切な量​​のデータを含むCSVファイルがあります(1億6000万行それ最新のPCはデータ容量が大きいです。大量のデータを処理するように設計されたシステムに入れるのではなく、16MBのRAMがかなり十分に見えた1990年代のツールを使ってsort作業しようとしています(例:gitの履歴を読んだだけ)。

CSVはただデータ型エラー大量のデータ操作の場合、あなたの例はこれを完全に示しています。非効率的なツールは、目標を達成するために非効率的なデータ構造(行を含むテキストファイル)を非効率的な方法で処理します。

各値が2番目の列にどのくらいの頻度で表示されるかを知りたいだけです。以前のソートは、ツール(uniq -c)が不都合であり、以前に行をソートする必要があるためです(実際には妥当な理由はありません。値とその頻度のマップを保持して増やすことができることは実装されていません)。


そのため、CSVベースのデータストリームを使用しないでください。シンプル

sqlite3 place.sqlite

そして、そのシェルで(CSVにSQLiteが列を決定するために使用できるヘッダー行があると仮定して)(もちろん、列$second_column_name名に置き換えます)

.import 022_place_canvas_history.csv canvas_history --csv
SELECT $second_column_name, count($second_column_name)
  FROM canvas_history
  GROUP BY $second_column_name;

おそらくそれほど速く、追加のボーナスは実際のデータベースファイルを取得できることですplace.sqlite。より柔軟に行うことができます。たとえば、座標を抽出して時間を数値タイムスタンプに変換するテーブルを作成し、分析をより迅速かつ柔軟に実行できます。


1グローバル変数とそれを一貫して使用しない場合。彼らはけがをしました。 C作家にとっては時期が異なります。確かに悪いCではありません。ただ…あなたに精通している最新のコードベースではありません。このコードベースを書いて維持してくれたJim MeyeringとPaul Eggertに感謝します!

² 次のことを試すことができます。たとえば、5577行など、大きすぎないファイルを並べ替え、開いているls.cファイルの数を記録します。

strace -o /tmp/no-size.strace -e openat sort ls.c
strace -o /tmp/s1kB-size.strace -e openat sort -S 1 ls.c
strace -o /tmp/s100kB-size.strace -e openat sort -S 100 ls.c
wc -l /tmp/*-size.strace

おすすめ記事