2

我有一个对延迟非常敏感的例程,它顺序生成整数,但需要将最后生成的整数存储到磁盘以防崩溃或重新启动。

目前我正在寻找文件的开头,然后写出整数,然后在每次生成新的 int 时刷新。刷新是必需的,因此写入至少会命中电池支持的控制器缓存。

寻道的成本很高,所以我想只附加 4 个字节,如果需要恢复,则寻道到最后并读取最后 4 个字节。前面的语句显然假设没有太多其他磁盘活动发生,因此理想情况下,写头应该留在文件末尾。

这个数字通常不会超过 10,000,000,所以 40MB 还不错。

关于如何在不牺牲完整性的情况下实现最小延迟的任何建议?

Linux 2.6+ 上的 C 或 C++

4

8 回答 8

8

我认为最快/最简单的方法是使用 mmap/msync -- 将文件的 1 页 mmap 到内存中并将值存储在该页面上。每当值更改时,调用 msync(2) 将页面强制返回到磁盘。这样每个商店只需要一个系统调用

于 2011-01-26T23:43:55.137 回答
2

如果我没看错,使用内存映射文件怎么样?只需将您的号码写入分配的地址,它就会出现在文件中。这假设操作系统在需要时将缓存可靠地写入磁盘,但您可能会发现它值得一试。

int len = sizeof(unsigned);
int fildes = open(...)
void* address = mmap(0, len, PROT_READ, MAP_PRIVATE, fildes, 0)
unsigned* mappedNumber = (unsigned*)(address);

*mappedNumber 现在可以包含您的整数。

于 2011-01-26T23:43:05.457 回答
2

措施。

你对硬件有多少控制权?如果任何内容少于full,您将无法得到任何保证。

在 Linux 上,我可能会尝试制作一个内核驱动程序,它会以最高优先级进行写入,甚至可能不使用文件系统。

但是,理论上...如果您访问控制器缓存就足够了,那么每次您将任何内容刷新到磁盘时,数据都会命中它。这意味着无论驱动器内部是否存在物理寻道,数据都已经存在。而且因为您永远不会知道其他应用程序会做什么,或者磁盘旋转的速度有多快,所以即使您将逻辑文件句柄保留在文件的开头或结尾,您的查找也将是随机的。

您可以随时要求您的用户使用闪存驱动器。

于 2011-01-26T23:48:15.797 回答
1

写入文件的最快方法是将该文件映射到内存中并将其视为 char 数组。

如果您不关心操作系统崩溃,则不需要同步文件(Linux 在生产中从未在我身上崩溃过)。您的所有写入都绕过内核转到该文件映射,换句话说,真正的零拷贝(您还不能使用标准硬件上的套接字来做到这一点)。您可能需要在该文件中保留一个包含许多记录的标题,以防应用程序在将记录写入内存时崩溃。即写一条记录,然后才增加记录计数器。

调整此文件的大小需要ftruncate()/remap()序列,这可能会花费一些时间,因此您可能希望通过将文件增长一个因子来最小化调整大小,例如在溢出时将std::vector<>其大小增长 1.5 。push_back()根据您的吞吐量和延迟要求,可以应用某些优化。

内核将异步将文件映射写入磁盘(就好像您的应用程序中有另一个线程专门用于写入磁盘)。如有必要,有一种方法可以强制写入磁盘,方法是使用msync(). 但是,如果您想在操作系统崩溃中幸存下来,这只是必要的。但无论如何,从操作系统崩溃中幸存下来需要复杂的应用程序设计,因此在实践中,从应用程序崩溃中幸存下来就足够了。

于 2011-01-26T23:53:21.650 回答
1

为什么您的应用程序必须等待写入完成?

异步写入数据,或者可能从另一个线程写入数据。

您对硬盘驱动器实际上并没有太多的低级控制。只要你一次只写这么少的数据,你就会招致很多昂贵的搜索。但是由于您仅将其用作“检查点”以在发生崩溃时从中恢复,因此似乎没有理由不能异步进行写入。

于 2011-01-27T00:18:29.870 回答
0

无论块大小如何,存储一个 int 只需要一个块在磁盘上。因此,您必须将一个块同步到光盘,并且需要尽可能长的时间,并且您无法做任何事情来使其更快。

无论您做什么,fdatasync() 都将是时间上的杀手。它会将一个块同步到您的(电池支持的 RAID)控制器中。

除非您有某种非易失性 ram,否则所有(明智的)方法都将完全等效,因为它们都需要同步一个块。

执行 seek 系统调用不会产生任何影响,因为这对硬件没有影响。在任何情况下,您都可以使用 pwrite() 来避免它。

于 2011-01-27T07:41:37.220 回答
0

考虑一下“附加 4 个字节”的含义。磁盘不存储文件,甚至不存储字节。它们存储集群和固定数量的集群。文件的概念是由操作系统创建的。它将一些集群分配给文件系统表,以跟踪文件的精确位置。现在,附加 4 个字节意味着至少将 4 个字节写入一个簇。但这也意味着确定哪个集群。现有文件大小是多少?我们需要一个新的集群吗?如果没有,我们需要读取最后一个簇,将 4 个字节修补到正确的位置,然后写回簇,然后更新文件系统中的文件大小。如果我们确实追加了一个新集群,我们可以写入 4 个字节后跟零(不需要旧值),但我们需要做大量的簿记才能将集群添加到文件中。

因此,绝对最快的方法永远不可能是附加 4 个字节。您必须覆盖 4 个现有字节。最好在内存中已有的扇区中。其他人已经指出,您可以使用mmap/msync.

显然,考虑到当前 SSD 和开发人员的价格,以及 40 MB 的限制,您将使用 SSD。如果您节省一个小时,它就会物有所值。因此,寻道时间无关紧要;SSD 没有物理磁头。

于 2011-01-27T11:18:32.807 回答
0

这里有很多人在谈论 mmap() 好像它可以解决某些问题,但是与磁盘写入开销相比,您的系统调用开销基本上为零。 请记住,追加或写入文件需要您更新 inode(mtime,filesize),因此这意味着磁盘查找。

我建议您考虑将整数存储在磁盘以外的其他地方。 例如:

  • 将其写入您控制的某个 nvram(例如,在嵌入式系统上)。(如果您的 RAID 控制器具有用于写入的 nvram,它可能会为您执行此操作。但如果您要问这个问题,它可能不会。)

  • 将其写入系统 CMOS 内存中的空闲字节(例如在 PC 硬件上)。

  • 将其写入网络上的另一台机器(如果它是一个快速网络)并让他们确认。

  • 重新设计您的应用程序,这样您就可以在每 n 次事务之后进行同步,而不是在每次事务之后进行同步。这将比每次都快 n 倍。

  • 重新设计您的应用程序,以便如果整数丢失,您最近交易的更改也会丢失。那么从技术上讲,您丢失了整数更新这一事实并不重要。当您重新启动时,就好像您从未增加它一样,因此您可以从那里恢复。

您没有解释为什么需要这种行为;老实说,如果你的应用需要这个,听起来你的应用可能设计得不太好。例如,有些人建议使用数据库,因为他们一直在做这种事情;是的,但数据库通过缓慢(即每次同步磁盘)来做到这一点,除非您首先创建事务,在这种情况下,磁盘只需要在您执行“提交事务”时同步。但是,如果您绝对必须在每个整数之后进行同步,那么您将不断提交事务,而数据库无法将您从中拯救出来;除非它至少执行 fdatasync(),否则数据库无法保证不会丢失数据。

于 2011-02-08T03:47:36.607 回答