fseek()在文件系统中是如何实现的?

25

这不是纯粹的编程问题,但它会影响使用fseek()函数的程序性能,因此了解其工作原理非常重要。为避免被关闭,先声明一下。

我想知道在文件中间插入数据的效率如何。假设我有一个1MB大小的文件,然后我在512KB的偏移处插入一些内容,与将我的数据附加到文件末尾相比,这样做的效率如何?为了使示例完整,假设我想插入16KB的数据。

我知道答案会因文件系统而异,但我认为常见文件系统使用的技术相当相似,我只是想获得正确的概念。


4
使用fseek()等函数,您无法在文件中间插入数据,因此您的问题是无意义的。 - anon
2
你不能简单地在文件中间插入数据(就像你不能从文件的开头或中间删除任何东西一样)。你能做的最好的事情是覆盖文件中间的数据。 - dmeister
6个回答

6
(免责声明:我只想在这个有趣的讨论中添加一些提示) 我认为有几点需要考虑:
1)fseek不是主要的系统服务,而是一个库函数。为了评估其性能,我们必须考虑文件流库的实现方式。通常,文件I/O库会在用户空间添加一个缓冲层,因此如果目标位置在当前缓冲区内或外,fseek的性能可能会有很大差异。此外,I/O库使用的系统服务可能差异很大。例如,在某些系统上,如果可能的话,该库会广泛使用文件内存映射。
2)正如您所说,不同的文件系统可能表现出非常不同的行为。特别是,我希望事务性文件系统必须采取一些非常聪明且可能昂贵的操作,以准备好在文件中间的已中止写入操作的可能回滚。
3)现代操作系统具有非常积极的缓存算法。一个“fseeked”文件很可能已经存在于缓存中,因此操作变得更快。但是,如果其他进程产生的整体文件系统活动变得重要,它们可能会大幅度降低。
有任何评论?

当然,一般来说插入比追加更昂贵,因为要插入至少需要移动先前的内容,即追加到文件末尾!但是Pajton问题中有趣的部分是关于fseek操作性能的。有任何评论吗? - Giuseppe Guerrini

4
让我们以ext2文件系统和Linux操作系统为例。我认为插入和追加之间不会有显著的性能差异。在这两种情况下,都必须读取文件节点和偏移表,将相关磁盘扇区映射到内存中,更新数据,并在稍后的某个时间点将数据写回磁盘。在这个例子中,会对性能产生重大影响的是访问文件部分时良好的时间和空间局部性,因为这将减少加载/存储组合的数量。
正如先前的答案所说,如果你处理的数据写入恰好是FS块大小的倍数,那么你可能能够加快这两个操作的速度,在这种情况下,你可以跳过加载阶段,直接将新块插入到文件的inode数据结构中。但这并不实用,因为你需要低级别地访问FS驱动程序,并且使用它将非常受限制且不可移植。

4

fseek(...)是一个库调用,而不是操作系统系统调用。从技术上讲,运行时库负责处理调用操作系统的实际开销,间接地说,fseek(...)间接地调用了系统,但实际上并没有(这引出了库调用和系统调用之间的明显区别)。 无论底层系统如何,fseek(...)都是标准的输入输出函数...然而...这是非常大的“however”...

操作系统很可能已经将文件缓存在其内核内存中,也就是在磁盘上存储1和0的位置的直接偏移量,很可能是在内核中的最高层中拥有关于文件组成的快照,即数据,而不管它包含什么(只要该偏移量指向磁盘上的有效结构!)...

当发生fseek(..)时,会有很多开销,间接地,内核委托了从磁盘读取的任务,具体取决于文件的碎片情况,从用户空间的角度来看,也就是C代码执行fseek(...),它可能会分散在各处,以收集数据并插入到文件中的"连续数据"中。因此,在文件中插入到中间位置(记住在此阶段,内核必须调整到实际磁盘盘片上的数据偏移量)将被认为比追加到文件末尾慢。

原因很简单,内核“知道”最后一个偏移量是什么,并简单地抹去EOF标记并插入更多数据。在幕后,内核必须为具有调整偏移量的磁盘缓冲区分配另一个内存块,以跟随EOF标记的位置。完成数据的追加后。


好的提示,数据分散可能是性能不佳的重要原因。谢谢! - Giuseppe Guerrini

2
我对 Solaris 上的 fseek 做出了一个观察,每次调用它都会重置 FILE 的读取缓冲区。下一次读取将始终读取完整块(默认为 8K)。因此,如果您需要大量的随机访问小读取,请单独处理它(使用带有 NULL 缓冲区的 setvbuf),甚至使用直接系统调用(lseek+read 或者更好的 pread,只需 1 个系统调用而不是 2 个)。我认为这种行为在其他操作系统上也是类似的。

1

如果数据大小是FS扇区的倍数,那么您只能有效地将数据插入到文件中间,但操作系统不提供这样的功能,因此您必须使用低级接口来访问FS驱动程序。


1
在文件中间插入数据比追加到末尾效率低,因为在插入时,您必须移动插入点后的数据以腾出空间来插入数据。移动这些数据需要从磁盘读取它们,写入要插入的数据,然后写入插入数据后的旧数据。因此,在插入时至少需要进行一次额外的读取和写入操作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接