wvi*_*ana 14 pipe python sort uniq
我正在做一些处理,试图获取包含 160,353,104 行的文件中有多少不同的行。这是我的管道和 stderr 输出。
\n$ tail -n+2 2022_place_canvas_history.csv | cut -d, -f2 | tqdm --total=160353104 |\\\n sort -T. -S1G | tqdm --total=160353104 | uniq -c | sort -hr > users\n\n100%|\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88| 160353104/160353104 [0:15:00<00:00, 178051.54it/s]\n 79%|\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 | 126822838/160353104 [1:16:28<20:13, 027636.40it/s]\n\nzsh: done tail -n+2 2022_place_canvas_history.csv | cut -d, -f2 | tqdm --total=160353104 | \nzsh: killed sort -T. -S1G | \nzsh: done tqdm --total=160353104 | uniq -c | sort -hr > users\n
Run Code Online (Sandbox Code Playgroud)\n我的命令行 PS1 或 PS2 打印了管道所有进程的返回码。\xe2\x9c\x94 0|0|0|KILL|0|0|0
第一个字符是绿色复选标记,表示最后一个进程返回 0(成功)。其他数字是每个管道进程的返回代码,顺序相同。所以我注意到我的第四个命令得到了KILL
状态,这是我的排序命令,sort -T. -S1G
将本地目录设置为临时存储并缓冲高达 1GiB。
问题是,为什么它返回了 KILL,这是否意味着KILL SIGN
向它发送了一些东西?\n有没有办法知道“谁杀了”它?
阅读Marcus M\xc3\xbcller Answer后,首先我尝试将数据加载到 Sqlite 中。
\n\n\n因此,也许现在是告诉您的好时机,不,不要使用基于 CSV 的数据流。一个简单的
\nRun Code Online (Sandbox Code Playgroud)\nsqlite3 place.sqlite\n
并在该 shell 中(假设您的 CSV 有一个标题行,SQLite 可以使用该标题行来确定列)(当然,将 $second_column_name\n 替换为该列的名称)
\nRun Code Online (Sandbox Code Playgroud)\n.import 022_place_canvas_history.csv canvas_history --csv\nSELECT $second_column_name, count($second_column_name) FROM canvas_history \nGROUP BY $second_column_name;\n
这花了很多时间,所以我把它留在处理中并去做其他事情。与此同时,我更多地思考了Marcus M\xc3\xbcller 回答中的另一段:
\n\n\n您只想知道每个值在第二列中出现的频率。之所以会发生这种情况,是因为您的工具(
\nuniq -c
)很糟糕,并且需要在之前对行进行排序(实际上没有充分的理由。它只是没有实现它可以保存值及其值的映射)频率并在出现时增加频率)。
所以我想,我可以实现它。当我回到计算机时,我的 Sqlite 导入过程已停止,原因是 SSH Broken Pip,认为它很长时间没有传输数据,因此关闭了连接。\n好吧,这是一个使用计数器实现计数器的好机会字典/地图/哈希表。所以我写了以下distinct
文件:
#!/usr/bin/env python3\nimport sys\n\nconter = dict()\n\n# Create a key for each distinct line and increment according it shows up. \nfor l in sys.stdin:\n conter[l] = conter.setdefault(l, 0) + 1 # After Update2 note: don\'t do this, do just `couter[l] = conter.get(l, 0) + 1`\n\n# Print entries sorting by tuple second item ( value ), in reverse order\nfor e in sorted(conter.items(), key=lambda i: i[1], reverse=True):\n k, v = e\n print(f\'{v}\\t{k}\')\n
Run Code Online (Sandbox Code Playgroud)\n所以我通过以下命令管道使用了它。
\n#!/usr/bin/env python3\nimport sys\n\nconter = dict()\n\n# Create a key for each distinct line and increment according it shows up. \nfor l in sys.stdin:\n conter[l] = conter.setdefault(l, 0) + 1 # After Update2 note: don\'t do this, do just `couter[l] = conter.get(l, 0) + 1`\n\n# Print entries sorting by tuple second item ( value ), in reverse order\nfor e in sorted(conter.items(), key=lambda i: i[1], reverse=True):\n k, v = e\n print(f\'{v}\\t{k}\')\n
Run Code Online (Sandbox Code Playgroud)\n它进展得非常非常快,预计tqdm
不到 30 分钟,但当进入 99% 时,它变得越来越慢。此过程使用了大量 RAM,大约 1.7GIB。我正在处理这些数据的机器,我有足够存储空间的机器,是一个只有 2GiB RAM 和 ~1TiB 存储空间的 VPS。我认为它可能变得如此缓慢,因为必须处理这些巨大的内存,也许要做一些交换或其他事情。 \n无论如何,我一直在等待,当它最终在 tqdm 中达到 100% 时,所有数据都已发送到进程中./distinct
,几秒钟后得到以下输出:
tail -n+1 2022_place_canvas_history.csv | cut -d, -f2 | tqdm --total=160353104 | ./distinct > users2\n
Run Code Online (Sandbox Code Playgroud)\n这次大部分肯定是由内存不足杀手造成的,如Marcus M\xc3\xbcller 答案TLDR 部分中所发现的那样。
\n所以我刚刚检查过,我没有在这台机器上启用交换。使用 dmcrypt 和 LVM 完成设置后将其禁用,因为您可能会在我的这个答案中获得更多信息。
\n所以我的想法是启用我的 LVM 交换分区并尝试再次运行它。另外,在某个时刻,我认为我已经看到 tqdm 使用 10GiB RAM。但我很确定我看到了错误或btop
输出混淆了,因为后者仅显示 10MiB,不认为 tqdm 会使用太多内存,因为它只是在读取新的\\n
.
在 St\xc3\xa9phane Chazelas 对这个问题的评论中,他们说:
\n\n\n系统日志可能会告诉你。
\n
我想了解更多,我应该在journalctl中找到一些东西吗?如果可以的话,该怎么办呢?
\n无论如何,正如Marcus M\xc3\xbcller Answer所说,将 csv 加载到 Sqlite 中可能是迄今为止最智能的解决方案,因为它将允许以多种方式对数据进行操作,并且可能有一些智能的方法来导入此数据而无需输出 -内存。
\n但现在我对如何找出进程被杀死的原因感到好奇,因为我想了解 mysort -T. -S1G
和 now about my ./distinct
,最后一个几乎可以肯定它与内存有关。那么如何检查日志来说明为什么这些进程被杀死呢?
因此,我启用了交换分区,并从此问题评论中采纳了Marcus M\xc3\xbcller的建议。使用 python 集合.Counter。所以我的新代码 ( distinct2
) 如下所示:
#!/usr/bin/env python3\nfrom collections import Counter\nimport sys\n\nprint(Counter(sys.stdin).most_common())\n
Run Code Online (Sandbox Code Playgroud)\n因此,我运行了Gnu Screen,即使管道再次损坏,我也可以恢复会话,而不是在以下管道中运行它:
\n160353105it [30:21, 88056.97it/s] \nzsh: done tail -n+1 2022_place_canvas_history.csv | cut -d, -f2 | tqdm --total=160353104 | \nzsh: killed ./distinct > users2\n
Run Code Online (Sandbox Code Playgroud)\n这让我得到了以下输出:
\n160Mit [1:07:24, 39.6kit/s]\n1.00it [7:08:56, 25.7ks/it]\n
Run Code Online (Sandbox Code Playgroud)\n正如您所看到的,对数据进行排序比对数据进行计数花费了更多的时间。\n您可能注意到的另一件事是 tqdm 第二行输出仅显示 1.00it,这意味着它只有一行。所以我使用 head 检查了 user5 文件:
\n#!/usr/bin/env python3\nfrom collections import Counter\nimport sys\n\nprint(Counter(sys.stdin).most_common())\n
Run Code Online (Sandbox Code Playgroud)\n正如您所看到的,它在一行中打印了整个元组列表。为了解决这个问题,我使用了很好的旧 sed ,如下所示sed \'s/),/)\\n/g\' users5 > users6
。之后,我使用 head 检查了 users6 内容,其输出如下:
tail -n+1 2022_place_canvas_history.csv | cut -d, -f2 | tqdm --total=160353104 --unit-scale=1 | ./distinct2 | tqdm --unit-scale=1 > users5\n
Run Code Online (Sandbox Code Playgroud)\n足够好,可以稍后工作。现在我想我应该在尝试使用 dmesg 或 Journalctl检查谁杀死了我的类型后添加更新。我还想知道是否有办法使这个脚本更快。也许创建一个线程池,但必须检查Python的字典行为,还考虑其他数据结构,因为我正在计算的列是固定宽度的字符串,也许使用列表来存储每个不同user_hash的频率。我还阅读了 Counter 的 python 实现,它只是一个字典,与我之前的实现几乎相同,但不是使用dict.setdefault
justused dict[key] = dict.get(key, 0) + 1
,而是在这种情况下没有真正需要的误用setdefault
。
所以我已经陷入了兔子洞的深渊,完全失去了目标的焦点。我开始寻找更快的排序,也许写一些 C 或 Rust,但意识到已经处理了我要处理的数据。所以我在这里展示 dmesg 输出以及关于 python 脚本的最后一个提示。提示是:使用 dict 或 Counter 进行计数可能比使用 gnu 排序工具对其输出进行排序更好。排序可能比 python 排序 buitin 函数更快。
\n关于 dmesg,查找内存不足非常简单,只需按一下即可sudo dmesg | less
一直G
向下,而不是?
向后搜索,而不是搜索Out
字符串。找到了其中两个,一个用于我的 python 脚本,另一个用于我的排序,即引发此问题的那个。这是这些输出:
[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\n[1306799.126218] oom_reaper: reaped process 1611241 (sort), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB\n[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\n[1365683.113366] oom_reaper: reaped process 1611945 (python3), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB\n
Run Code Online (Sandbox Code Playgroud)\n就是这样,非常感谢您到目前为止的帮助,希望它也能帮助其他人。
\nMar*_*ler 30
TL;DR:内存不足杀手或磁盘空间不足用于临时文件杀死sort
。建议:使用不同的工具。
sort.c
我现在已经浏览了 GNU coreutils \xc2\xb9。您的-S 1G
意思只是意味着该sort
进程尝试分配 1GB 的内存块,如果不可能的话,将回退到越来越小的大小。
耗尽该缓冲区后,它将创建一个临时文件来存储已排序的行\xc2\xb2,并对内存中的下一个输入块进行排序。
\n消耗完所有输入后,sort
会将两个临时文件合并/排序为一个临时文件(mergesort-style),并连续合并所有临时文件,直到合并产生总排序输出,然后将其输出到stdout
.
这很聪明,因为这意味着您可以对大于可用内存的输入进行排序。
\n或者,在这些临时文件本身不保存在 RAM 中的系统上(通常是/tmp/
a tmpfs
,仅 RAM 文件系统),这很聪明。因此,写入这些临时文件会占用您想要保存的 RAM,并且您的 RAM 即将耗尽:您的文件有 1.6 亿行,快速谷歌搜索后会显示它是 11GB 的未压缩数据。
sort
您可以通过更改它使用的临时目录来“帮助”解决这个问题。您已经这样做了,-T.
将临时文件放置在当前目录中。可能你那里的空间不够了?或者当前目录是否在tmpfs
或类似?
您有一个包含中等数据量的 CSV 文件(对于现代 PC 来说, 1.6 亿行并不是那么多数据)。您不是将其放入旨在处理大量数据的系统中,而是尝试使用 20 世纪 90 年代的工具(是的,我刚刚读过sort
git 历史)对其进行操作,当时 16 MB RAM 似乎相当慷慨。
CSV 是处理任何大量数据的错误数据格式,您的示例完美地说明了这一点。低效的工具以低效的方式处理低效的数据结构(带有行的文本文件),以低效的方式实现目标:
\n您只想知道每个值在第二列中出现的频率。之所以会发生这种情况,是因为您的工具(uniq -c
)很糟糕,并且需要在之前对行进行排序(实际上没有充分的理由。它只是没有实现它可以保存值及其值的映射)频率并在出现时增加频率)。
因此,也许现在是告诉您的好时机,不,不要使用基于 CSV 的数据流。一个简单的
\nsqlite3 place.sqlite\n
Run Code Online (Sandbox Code Playgroud)\n并在该 shell 中(假设您的 CSV 有一个标题行,SQLite 可以使用该标题行来确定列)(当然,替换$second_column_name
为该列的名称)
.import 022_place_canvas_history.csv canvas_history --csv\nSELECT $second_column_name, count($second_column_name)\n FROM canvas_history\n GROUP BY $second_column_name;\n
Run Code Online (Sandbox Code Playgroud)\n可能会一样快,而且额外的好处是,您会得到一个实际的数据库文件place.sqlite
。例如,您可以更灵活地使用 \xe2\x80\x93,创建一个在其中提取坐标的表,并将时间转换为数字时间戳,然后通过分析更快、更灵活。
\xc2\xb9 全局变量,以及何时使用的不一致。他们受伤了。对于 C 语言作者来说,这是一个不同的时代。它绝对不是糟糕的 C,只是……不是您在更现代的代码库中所习惯的。感谢 Jim Meyering 和 Paul Eggert 编写和维护此代码库!
\n\xc2\xb2 你可以尝试执行以下操作:对一个不太大的文件进行排序,比如说ls.c
有 5577 行,并记录打开的文件数:
strace -o /tmp/no-size.strace -e openat sort ls.c\nstrace -o /tmp/s1kB-size.strace -e openat sort -S 1 ls.c\nstrace -o /tmp/s100kB-size.strace -e openat sort -S 100 ls.c\nwc -l /tmp/*-size.strace\n
Run Code Online (Sandbox Code Playgroud)\n