最近,一位采访者向我询问了缓冲的类型。有哪些类型的缓冲区?实际上,当我说我将所有系统调用写入日志文件以监视系统时,出现了这个问题。他说,每次调用文件都会很慢。如何防止它。我说我会用缓冲区,他问我什么类型的缓冲区?有人可以解释我的缓冲类型。
答案 0 :(得分:6)
在UNIX下的C语言(也可能是其他操作系统)中,通常有两个缓冲区,至少在您的给定方案中。
第一个存在于C运行时库中,其中要写入的信息在传送到操作系统之前被缓冲。
第二个是操作系统本身,信息被缓冲,直到它可以物理地写入底层媒体。
作为一个例子,我们在许多月前编写了一个日志库,强制将信息写入磁盘,以便在程序崩溃或操作系统崩溃时它会存在。
这是通过以下序列实现的:
fflush (fh); fsync (fileno (fh));
其中第一个实际确保信息从C运行时缓冲区传递到操作系统,第二个是写入磁盘。请记住,这是一项昂贵的操作,只有在您完全需要立即写入的信息时才应该这样做(我们只在SUPER_ENORMOUS_IMPORTANT日志级别执行此操作)。
说实话,我不完全确定为什么你的面试官认为除非你正在撰写很多的信息,否则它会很慢。已经存在的两个级别的缓冲应该非常充分。如果 是一个问题,那么你可以自己引入另一个层,它将消息写入内存缓冲区,然后将其传递给单个fprint
类型的调用。溢出。
但是,除非你在没有任何函数调用的情况下执行此操作,否则我看不到它比fprint
类缓冲已经给你的速度快得多。
在评论中澄清这个问题实际上是关于内核中的缓冲:
基本上,您希望尽可能快速,高效且可行(不容易出现故障或资源短缺)。
可能最好的选择是缓冲区,无论是静态分配还是在引导时动态分配一次(您希望避免动态重新分配失败的可能性)。
其他人提出了一个环形(或循环)缓冲区,但由于以下原因,我不会那样(技术上):使用经典的循环缓冲区意味着在数据包裹时写出数据将需要两个独立的写作。例如,如果您的缓冲区具有:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|s|t|r|i|n|g| |t|o| |w|r|i|t|e|.| | | | | | |T|h|i|s| |i|s| |t|h|e| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
^ ^
| |
Buffer next --+ +-- Buffer start
然后你必须写"This is the "
后跟"string to write."
。
相反,维护next
指针,如果缓冲区中的字节加上要添加的字节小于缓冲区大小,只需将它们添加到缓冲区中,不要对底层媒体进行物理写入。 / p>
只有当你要溢出缓冲区时才开始做一些棘手的事情。
您可以选择以下两种方法之一:
next
指针设置回开始处理新消息;或next
指针设置回开头以处理剩余的消息。我可能会选择第二个,因为你将不得不考虑对于缓冲区来说太大的消息。
我所说的是这样的:
initBuffer:
create buffer of size 10240 bytes.
set bufferEnd to end of buffer + 1
set bufferPointer to start of buffer
return
addToBuffer (size, message):
while size != 0:
xfersz = minimum (size, bufferEnd - bufferPointer)
copy xfersz bytes from message to bufferPointer
message = message + xfersz
bufferPointer = bufferPointer + xfersz
size = size - xfersz
if bufferPointer == bufferEnd:
write buffer to underlying media
set bufferPointer to start of buffer
endif
endwhile
这基本上通过减少物理写入的数量来有效地处理任何大小的消息。当然会有优化 - 消息可能已被复制到内核空间中,因此如果你要编写它,将它复制到缓冲区是没有意义的。您也可以将内核副本中的信息直接写入底层媒体,并将最后一位传输到缓冲区(因为您必须保存它)。
此外,如果一段时间没有写入任何内容,您可能希望将不完整的缓冲区刷新到底层媒体。这样可以减少丢失有关内核本身崩溃的可能性的信息的可能性。
除此之外:从技术上讲,我猜这个是类型的循环缓冲区,但它具有特殊的案例处理功能,可以最大限度地减少写入次数,并且不需要尾部指针,因为这种优化。
答案 1 :(得分:2)
还有ring buffers具有有限的空间要求,并且可能在Unix dmesg
工具中最为人所知。
答案 2 :(得分:0)
我想到的是基于时间的缓冲区和基于大小的缓冲区。因此,您可以每隔x秒/分钟/小时或者其他任何内容写入缓冲区中的任何内容。或者,您可以等到有x个日志条目或x个字节的日志数据并立即写入它们。这是log4net和log4J执行此操作的方法之一。
答案 3 :(得分:0)
总的来说,有“先进先出”(FIFO)缓冲区,也称为队列;并且有“最新* -In-First-Out”(LIFO)缓冲区,也称为堆栈。
为了实现FIFO,有circular buffers,它们通常用于已分配固定大小字节数组的地方。例如,键盘或串行I / O设备驱动程序可能使用此方法。这是在无法动态分配内存时使用的常用缓冲区类型(例如,在操作系统的虚拟内存(VM)子系统操作所需的驱动程序中)。
在动态内存可用的情况下,FIFO可以通过多种方式实现,特别是对于链表导出的数据结构。
此外,实现priority queues的二项式堆可用于FIFO缓冲区实现。
FIFO和LIFO缓冲区的特定情况是TCP段重组缓冲区。这些可能会在收到尚未到达的中间段之前持有无序(“从未来”)收到的段。
*我的首字母缩略词更好,但大多数人会将LIFO称为“ Last In,First Out”,而不是最新。
答案 4 :(得分:0)
如果我错了,请纠正我,但是不会使用mmap
'd文件来避免小write
系统调用的开销以及应用程序时数据丢失的可能性(但不是OS)崩溃?对我来说,这似乎是性能和可靠性之间的理想平衡。