私は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
非常に迅速に進行し、tqdm
30分未満になると予想されますが、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のメモリブロックを割り当てようとし、これが不可能な場合、ますます小さいサイズにフォールバックすることを意味します。
バッファが使い果たされたら、ソートされた行を保存するための一時ファイルを作成し、メモリ内の次の入力ブロックをソートします。
すべての入力が消費された後、sort
2つの一時ファイルが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
答え2
これ@MarcusMüllerの返信「誰が私の種族を殺したのか」という内容は非常に明白です。そして問題を確認しました。
しかし、2番目の部分はまだ多くの議論が行われていません。または、csv列の固有値を効率的に計算する方法。より良い/より速いソート方法を見つけるしようとする以外。
その理由はパイプライン(すべて)がuniq
。使用に基づいてuniq
ソートされたデータが必要なためです。
他の解決策がありますか?
はい。 2列のデータをキーとして配列を作成し、その値が見つかるたびに1ずつ増加します。これは awk がデータを処理する一般的な方法です。
$ awk -F, '{count[$2]++}END{for (i in count) {print i,count[i]}}'
ソートのようにファイル全体をメモリに保存する必要はありません。ただし、キーリストのみが表示されます('kgZoJz//JpfXgowLxOhcQlFYOCm8m6upa6Rpltcc63K6Cz0vEWJF/RYmlsaXsIQEbXrwz+Il3BkD8XZVx7YMLQ==\n'
表示されたキーと計算のためにフローティング)。
これにより、ファイル内の各行が表示される順序で1回ずつ処理され、並べ替えなしで一意のユーザー数が計算されます。しかし、そうです。数をソートするには最終的なソートが必要です。
したがって、ファイルを処理する時間はn
ソート時間に比例しn*log(n)
、メモリ使用量はユーザー数 "m"(2番目のフィールドuniqキー)に比例します。
ユーザーあたりの平均数が350の場合(最大値は〜795、最小値は1、数は2つの数の間で直線的に変化すると仮定)、使用されるメモリサイズは88(キーサイズ)に比例する必要があります。 ) 乗算 160353104/350 (固有キー数)、または 40MB 未満に若干のオーバーヘッドを加えた値です。