简短的回答是不,没有便携的方法可以做到这一点。
该sendfile()
方法是特定于 Linux 的,因为在大多数其他实现它的操作系统上,源必须是文件或共享内存对象。(我什至没有检查是否/在哪个 Linux 内核版本中,sendfile()
从套接字描述符到/dev/null
受支持。老实说,我会非常怀疑这样做的代码。)
查看例如 Linux 内核源代码,并考虑到 assize_t discard(fd, len)
与标准的差异很小ssize_t read(fd, buf, len)
,显然可以添加这样的支持。甚至可以通过 ioctl(例如SIOCISKIP
)添加它,以便于检测支持。
但是,问题在于您设计了一种低效的方法,而不是在算法级别修复该方法,您正在寻找可以使您的方法表现更好的拐杖。
你看,很难证明“额外副本”(从内核缓冲区到用户空间缓冲区)是一个实际的性能瓶颈。系统调用(用户空间和内核空间之间的上下文切换)的数量有时是。如果您向上游发送了一个补丁,例如ioctl(socketfd, SIOCISKIP, bytes)
为 TCP 和/或 Unix 域流套接字实现,他们会指出,希望实现的性能提升最好通过不尝试获取您不需要的数据来获得。(换句话说,您尝试做事的方式本质上是低效的,与其创建拐杖来使该方法更好地工作,您应该选择一种性能更好的方法。)
在您的第一种情况下,接收由类型和长度标识符构成的结构化数据的进程希望跳过不需要的帧,最好通过修复传输协议来修复。例如,接收方可以通知发送方它对哪些帧感兴趣(即基本过滤方法)。如果你被一个由于外部原因而无法替换的愚蠢协议所困,那么你只能靠自己了。(FLOSS 开发者社区不是,也不应该因为有人为此而哀叹而维护愚蠢的决定。任何人都可以自由地这样做,但他们需要以一种不需要其他人额外工作的方式来做也。)
在第二种情况下,您已经读取了数据。不要那样做。相反,使用足够大的用户空间缓冲区来容纳两个完整大小的帧。每当您需要更多数据,但帧的开头已经超过缓冲区的中间时,memmove()
帧首先从缓冲区的开头开始。
当您有一个部分读取的帧,并且您N
从左侧有您不感兴趣的未读字节时,请将它们读入缓冲区的未使用部分。总是有足够的空间,因为你可以覆盖当前帧已经使用的部分,并且它的开始总是在缓冲区的前半部分。
如果帧很小,例如最大 65536 字节,则应使用可调整的最大缓冲区大小。在大多数具有高带宽流套接字的台式机和服务器机器上,2 MiB(2097152 字节或更多)更合理。这并没有浪费太多的内存,但你很少做任何内存副本(当你这样做时,它们往往很短)。(您甚至可以优化内存移动,以便仅复制、对齐完整的缓存线,因为在缓冲区的开头留下几乎一个垃圾缓存线是微不足道的。)
我使用大型数据集(包括文本形式的分子数据,其中记录由换行符分隔,并且使用用于转换十进制整数或浮点值的自定义解析器以获得更好的性能)进行 HPC,并且这种方法在实践中效果很好。简单地说,跳过缓冲区中已经存在的数据不是您需要优化的;与简单地避免做你不需要的事情相比,这是微不足道的开销。
还有一个问题是您希望通过这样做来优化什么:使用的 CPU 时间/资源,或整个任务中使用的挂钟。它们是完全不同的东西。
例如,如果您需要对某个文件中的大量文本行进行排序,那么您只需将整个数据集读取到内存中,构造一个指向每行的指针数组,对指针进行排序,最后写入,那么您使用的 CPU 时间最少每行(使用内部缓冲和/或 POSIX writev()
,这样您就不需要write()
为每个单独的行执行系统调用)。
但是,如果您希望最小化所使用的挂钟时间,您可以使用二叉堆或平衡二叉树来代替指针数组,并堆化或按顺序插入每行完全读取,这样当最后一行终于读完了,你已经按照正确的顺序排列了这些行。这是因为存储 I/O(对于除了病态输入情况之外的所有情况,例如单字符行)比使用任何强大的排序算法对它们进行排序需要更长的时间!内联工作(当数据进入时)的排序算法的 CPU 效率通常不如离线工作(在完整数据集上)的排序算法,因此最终会使用更多的 CPU 时间;但是因为 CPU 工作是在等待整个数据集加载到内存中浪费的时间完成的,所以它在更短的挂钟时间内完成!
如果有需要和兴趣,我可以提供一个实际的例子来说明这些技术。但是,绝对没有魔法,任何 C 程序员都应该能够自己实现这些(缓冲方案和排序方案)。(我确实考虑使用在线 Linux 手册页和 Wikipedia 文章和伪代码等资源,例如二进制堆“自行”完成。只要您不只是复制粘贴现有代码,我认为它是“在您的拥有”,即使有人或某些资源可以帮助您找到好的、强大的方法来做到这一点。)