前几天,我在午餐时读了一些书呆子的书,遇到了一个在不应该使用 TCP_NODELAY 的程序中遇到问题的人。 TCP_NODELAY 是关闭 Nagle 算法的因素之一,该算法通常用于批量处理一堆小写入,这样您就不会用大量的小数据包向网络发送垃圾邮件。 (如果这对长期读者来说听起来很熟悉,那是因为它出现在 2020 年秋季流传的一篇帖子中。)
所有这些数据包都有开销。这与我们拥有 10 兆位共享媒体网络时发生冲突的问题不太一样,但在对延迟不敏感的事情上浪费带宽和 CPU 时间仍然不太好。
当你的程序有很多东西需要连接时,问题就出现了,但它是通过单独调用 write() 来完成的。它不是通过一次调用向网络推送(例如)约 2 KB,而是在缓冲区中旋转,单独写入每个缓冲区。现在,您有 2000 个数据包在四处传播,所有数据包都带有标头,而其他所有内容都作为开销。让内核将其批处理基本上可以使世界免受损坏的代码的侵害。
我看到这个,它让我想起了我自己生活中类似的伤害。我有一些项目,我被迫包装另一个程序并监听它的标准输出。它没有库形式,因此使用它的唯一方法就是完成整个繁琐的工作。我创建一个管道,然后分叉并让子进程将标准输出连接到该管道并执行有问题的程序。然后父进程坐在那里监听管道的更新。
我意识到我的程序(阅读器)醒来的次数太频繁了。我应该每 30-45 秒获取一次更新,但在这段时间内它会唤醒几千次。搞什么?好吧,事实证明,无论出于何种原因,它一次向标准输出(或多或少)写入一个字节。
严重地。我必须亲自看看这个,并用 strace 附加到它上。它几乎看起来像这样:
708589 22:46:24.174856 写(1, "\"", 1) = 1 <0.000039> 708589 22:46:24.175018 写(1, "i", 1) = 1 <0.000041> 708589 22:46:24.175187 写(1, "d", 1) = 1 <0.000040> 708589 22:46:24.175339 写(1, "\"", 1) = 1 <0.000041> 708589 22:46:24.175506 写(1, ":", 3) = 3 <0.000048> 708589 22:46:24.175666 写(1, "12345", 5) = 5 <0.000041> 708589 22:46:24.175814 写(1, ", ", 2) = 2 <0.000041> 708589 22:46:24.175981 写(1, "\"", 1) = 1 <0.000041> 708589 22:46:24.176138 写(1, "c", 1) = 1 <0.000039> 708589 22:46:24.176279 写(1, "h", 1) = 1 <0.000040> 708589 22:46:24.176443 写(1, "a", 1) = 1 <0.000041> 708589 22:46:24.176596 写(1, "n", 1) = 1 <0.000040> 708589 22:46:24.176732 写(1, "n", 1) = 1 <0.000040> 708589 22:46:24.176875 写(1, "e", 1) = 1 <0.000043> 708589 22:46:24.177045 写(1, "l", 1) = 1 <0.000070> 708589 22:46:24.177331 写(1, "\"", 1) = 1 <0.000030> 708589 22:46:24.177454 写(1, ":", 3) = 3 <0.000029>
这是一份更长的日志中的 17 行。仅这 17 行就在大约三毫秒内显示出来,并且上面和下面还有更多很多。我在这里,试图查看它发送给我的数据类型,但它正在向我发送系统调用垃圾邮件。
如果仔细观察,您会发现每次 write() 并不完全是一个字节,但已经非常接近了。无论出于何种原因,数字都会同时消失,而那些“:”字符串是另一个好奇心。
我几乎忘记了这一点,直到 TCP_NODELAY 帖子穿过我的路径,然后我才想起这一点。显然,短写很常见。
但我想知道,人们不再跟踪程序了吗?如果这是我的项目并且我想弄清楚一些事情,那么所有的垂直滚动都会让我发疯。当它喷出的内容超出了我的回滚缓冲区允许我访问的范围时,就说明出了问题!我会竭尽全力尝试将其批量化一点。
我控制的这个过程的一部分是阅读器,因此这一方添加了一些使理智得以实现的黑客攻击。我在两次检查之间等待最多一秒钟,即使这样,我也会调用 select() 并设置 250 毫秒的超时。这使得系统调用垃圾邮件编写器程序有机会在我去读取它之前完成对管道的写入。这增加了我从一次 read() 调用中获取全部内容的机会。他们的程序可以在每个事件中进行数千个系统调用。我的大约有四个:futex、futex、select、read。
…
全面披露:我考虑编写一个程序,将这篇文章的正文作为一系列单独的字节传递给 write() ,然后该文章本身将只是在 strace 中运行它的输出。每个人都必须垂直阅读它,我敢打赌这对基本上任何人来说都会非常烦人,除了极少数经历过它并会发现它奇怪搞笑的人。
但是,我想在这里表达一个观点,所以我决定让那些倾向于用单词和句子来阅读内容的人可以使用它。我把“crazy up”留在了标题的前两个词中。