前几天午餐时我正在读一些书呆子般的书,遇到了一个在使用 TCP_NODELAY 的程序中遇到问题的人,而这个程序也许不应该使用。 TCP_NODELAY 是关闭 Nagle 算法的其中一个东西,它通常用于批量写入一堆小的写入,这样你就不会用大量的小数据包向网络发送垃圾邮件。 (如果这对长期读者来说听起来很熟悉,那是因为它主演了一篇在 2020 年秋季流传的帖子。)
所有这些数据包都有开销。这与我们拥有 10 兆位共享媒体网络时的问题并不完全相同,但将带宽和 CPU 时间浪费在对延迟不敏感的事情上仍然不是很好。
当你有一个程序有一堆东西要放在线上时,问题就来了,但它是通过单独调用 write() 来完成的。它不是通过一次调用在网络上推送(比如说)~2 KB,而是在缓冲区中旋转,单独写入每个缓冲区。现在你有 2000 个数据包在四处飞来飞去,所有数据包都有它们的标头,其他一切都在开销中。让内核对其进行批处理基本上是在将世界从损坏的代码中拯救出来。
我看到了这个,它让我想起了我自己生活中类似的一点伤害。我有一些项目,我被迫包装另一个程序并听取它的标准输出。它没有图书馆形式,所以使用它的唯一方法是通过整个 rigamarole。我开始创建一个管道,然后 fork 并让孩子将 stdout 连接到该管道并执行相关程序。父进程然后坐在那里听管道更新。
我意识到我的程序(读者)唤醒 FAR 的频率太高了。我应该每 30-45 秒更新一次,但它会在那个时间间隔内唤醒几千次。什么鬼?好吧,事实证明,无论出于何种原因,它一次(或多或少)写入一个字节到标准输出。
严重地。我必须亲自看到这个,并用 strace 附加到它。它看起来像这样:
708589 22:46:24.174856 写(1,“\”,1)= 1 >0.000039< 708589 22:46:24.175018 写(1,“我”,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 行就显示了大约 3 毫秒,而且上下还有更多行。我在这里,试图查看它向我发送了什么样的数据,它正在用系统调用向我发送垃圾邮件。
如果你仔细观察,你会发现它不是每个 write() 一个字节,但它非常接近。无论出于何种原因,数字都会同时出现,而那些“:”字符串是另一种好奇心。
我几乎忘记了这一点,直到 TCP_NODELAY 帖子出现在我面前,然后我才想起这一点。显然,短写很常见。
不过我想知道,人们不再使用 strace 程序了吗?如果这是我的项目并且我正在尝试弄清楚一些东西,那么所有的垂直滚动都会让我发疯。当它喷出的内容超过我的回滚缓冲区将允许我访问时,出现了问题!我会逐步尝试将其分批处理。
我控制的这个过程的一部分是阅读器,因此在这一方面添加了一些支持理智的技巧。我在两次检查之间最多等待一秒钟,即便如此,我还是以 250 毫秒的超时时间调用 select()。这让 syscall-spaming writer 程序有机会在我去阅读它之前完成对管道的写入。这增加了我从单个 read() 调用中获得全部内容的机会。他们的程序可以在每个事件中进行数千次系统调用。我的大约有四个:futex、futex、select、read。
…
全面披露:我考虑编写一个程序,将这篇文章的主体作为一系列单独的字节传递给 write(),然后文章本身就是在 strace 中运行它的输出。每个人都必须垂直阅读它,我敢打赌,基本上任何人都会非常讨厌它,除了少数经历过它并且会觉得它异常有趣的人。
但是,我想在这里说明一点,所以我决定让那些倾向于用单词和句子来阅读东西的人也能理解它。我在标题的前两个词中留下了疯狂。