【问题标题】:Fastest way to count just the first X lines of output仅计算前 X 行输出的最快方法
【发布时间】:2020-05-15 07:37:27
【问题描述】:

我有一个来自tshark 过滤器的大型终端输出,我想检查行数(本例中的包数)是否达到阈值 X。

该操作是在许多大文件的循环中完成的,所以我想在这里将性能提升到最大。

我想知道的是wc -l是计算终端命令输出的最快方法。

我的行看起来是这样的:(所以 tshark 命令在这里无关紧要,所以我将其替换为可读性)

THRESHOLD=100
[[ $(tshark -r $file -Y "tcp.stream==${streamID}" | wc -l) -gt $THRESHOLD ]] || echo "not enough"

虽然这几乎可以正常工作,但我想知道是否有办法在阈值之后停止。只要达到(或未达到)阈值,确切的数字并不重要。

猜测是:

HEAD=$((THRESHOLD+1))
[[ $(tshark -r $file -Y "tcp.stream==${streamID}" | head -n $HEAD | wc -l) -gt $THRESHOLD ]] || echo "not enough"

但是通过管道传输到附加服务并增加阈值可能会更慢,不是吗?

编辑: 将示例代码更改为有效的 tshark sn-p

【问题讨论】:

  • 你真的试过计时吗?我不认为我可以模拟你的平台
  • 你的想法是正确的。避免产生额外的进程。你的问题是小鸡还是先有蛋的问题。在进行有效比较之前,您必须至少阅读 $THRESHOLD 行(或 EOF)。由于您正在管道tsharks output,因此该过程将在传递给wchead 之前完成。除非$THRESHOLD 和文件大小之间有数十万行+ 差异,否则我不知道您在wchead 之间节省任何时间。您只需要对最坏的情况进行计时,然后再看看。
  • 在我的场景中,这一步很难隔离和计时(需要更多的编码),所以我的想法是在开始工作之前先考虑一下,最终一无所获。感谢大卫的回答...
  • 您可以将tsharks 的输出通过管道传送到一个程序中,该程序不仅如您所描述的那样验证它,然后关闭它的标准输入。然后作者 (tsharks) 应该用 broken pipe 中止。
  • @user1934428 head 已经关闭了标准输入。

标签: bash wc


【解决方案1】:

基准测试

只有一种方法可以找出答案:自己进行基准测试。 以下是一些想到的实现。

gen() { seq "$max"; }
# functions returning 0 (success) iff `gen` prints less than `$thold` lines
a() { [ "$(gen | head -n"$thold" | wc -l)" != "$thold" ]; }
b() { [ -z "$(gen | tail -n+"$thold" | head -c1)" ]; }
c() { [ "$(gen | grep -cm"$thold" ^)" != "$thold" ]; }
d() { [ "$(gen | grep -Fcm"$thold" '')" != "$thold" ]; }
e() { gen | awk "NR >= $thold{exit 1}"; }
f() { gen | awk -F^ "NR >= $thold{exit 1}"; }
g() { gen | sed -n "$thold"q1; }
h() { mapfile -n1 -s"$thold" < <(gen); [ -z "$MAPFILE" ]; }

max=1''000''000''000
for fn in {a..h}; do
  printf '%s: ' "$fn"
  for ((thold=1''000''000; thold<=max; thold*=10)); do
    printf '%.0e=%2.1fs, ' "$thold" "$({ time -p "$fn"; } 2>&1 | grep -Eom1 '[0-9.]+')"
  done
  echo
done

在上面的脚本中gen 是您的实际命令tsharks output lines 的占位符。函数ag 测试tsharks' 输出是否至少有$thold 行。你可以像这样使用它们

a && echo "tsharks printed less than $thold lines"

结果

这些是我系统上的结果:

a: 1e+06=0.0s, 1e+07=0.1s, 1e+08=0.8s, 1e+09=8.9s,
b: 1e+06=0.0s, 1e+07=0.1s, 1e+08=0.9s, 1e+09=8.4s,
c: 1e+06=0.0s, 1e+07=0.2s, 1e+08=1.6s, 1e+09=16.1s,
d: 1e+06=0.0s, 1e+07=0.2s, 1e+08=1.6s, 1e+09=15.7s,
e: 1e+06=0.1s, 1e+07=0.8s, 1e+08=8.2s, 1e+09=83.2s,
f: 1e+06=0.1s, 1e+07=0.8s, 1e+08=8.2s, 1e+09=84.6s,
g: 1e+06=0.0s, 1e+07=0.3s, 1e+08=3.0s, 1e+09=31.6s,
h: 1e+06=7.7s, 1e+07=90.0s, ... (manually aborted)

b: ... 1e+08=0.9s ... 意味着接近 b 需要 0.9 秒才能发现 seq 1000000000 的输出至少有 1e+08 (= 100'000'000) 行。

结论

从这个答案中提出的方法b 显然是最快的。但是,实际结果可能因系统而异(headgrep、... 有不同的实现和版本)以及您的实际用例。我建议使用您的实际数据进行基准测试(即将gen() 中的seq 替换为您的tsharks output lines 并将thold 设置为任何实际使用的值)。

如果您需要更快的方法,可以使用stdbufLC_ALL=C 进行更多试验。

【讨论】:

  • 令人惊讶的是b 对我来说也是最快的。
  • @KamilCuk 感谢您分享您的结果。你为什么感到惊讶?
  • 我想我希望像 awk 这样的单个进程获胜,而不是两个进程 tail | head
  • 在我的系统上也是方法 b)。感谢这个很好的基准示例!
  • 我本以为 sed 会在这里获胜。 headtail 确实很快……但没那么快。我相信样本数据也是一个玩家。如果将gen 替换为printf -- "x%.0s" $(seq $((max*70))) | fold -w70printf -- "x%.0s" $(seq $((max*6000))) | fold -w6000 会怎样(后者选择大于PIPE_BUF
【解决方案2】:

使用包装器启动tshark(或tail -f -n +1 file),该包装器检查输出行数并在阈值后退出。这是 awk 中使用 seq 模仿 tshark 的示例:

$ awk '
BEGIN {
    cmd="seq 1 100"                        # command to execute, outputs 100 lines
    while((cmd|getline res)>0 && ++c<50);  # count to 50 lines and exit
    print res                              # test to show last line of input
    exit
}'

输出:

50

seq 在 50 之后继续运行一段时间,但最终退出。更改cmd="seq 1 10000000 | tee foo"tail foo 我得到了:

...
11407
11408
11

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-02-10
    • 2014-06-12
    • 2011-02-09
    • 1970-01-01
    • 2019-11-04
    • 2013-12-31
    • 1970-01-01
    相关资源
    最近更新 更多