我假设您正在使用连接到标准计算机中内置磁盘控制器的 ATA 或 SATA 驱动器。这是一个有效的假设,还是您使用了任何不寻常的东西(硬件 RAID 控制器、SCSI 驱动器、外部驱动器等)?
作为一名在工作中进行大量磁盘 I/O 性能测试的工程师,我会说这听起来很像您的写入被缓存在某个地方。您的“高延迟”I/O 是缓存最终被刷新的结果。即使没有文件系统,I/O 操作也可以缓存在 I/O 控制器或磁盘本身中。
为了更好地了解正在发生的事情,不仅要记录最大延迟时间,还要记录平均延迟时间。考虑记录最多 10-15 个延迟样本,以便更好地了解这些高延迟样本的频率。此外,丢弃在测试前两三秒记录的数据,然后开始数据记录。在磁盘测试开始时可能会看到高延迟的 I/O 操作,这些操作并不表示磁盘的真实性能(可能是由于磁盘必须全速运转,磁头必须做大型初始寻道、磁盘写入缓存被刷新等)。
如果您想对磁盘 I/O 性能进行基准测试,我建议您使用 IOMeter 之类的工具,而不是使用 dd 或自行开发。 IOMeter 可以轻松查看更改 I/O 大小、对齐方式等方面的差异,此外它还跟踪许多有用的统计信息。
要求 I/O 操作在一定时间内发生是一件冒险的事情。一方面,系统上的其他应用程序可以与您竞争磁盘访问或 CPU 时间,几乎不可能预测它们对您的 I/O 速度的确切影响。您的磁盘可能会遇到坏块,在这种情况下,它必须做一些额外的工作来重新映射受影响的扇区,然后再处理您的 I/O。这引入了不可预测的延迟。您也无法控制操作系统、驱动程序和磁盘控制器正在做什么。您的 I/O 请求可能会因为许多不可预见的原因而被备份到其中一个层中。
如果您对 I/O 时间有硬性限制的唯一原因是因为您的缓冲区正在被重复使用,请考虑改为更改您的算法。尝试使用循环缓冲区,以便在写入数据时可以将数据从其中刷新。如果您发现填充它的速度比刷新它的速度快,则可以限制缓冲区的使用。或者,您也可以创建多个缓冲区并循环访问它们。当一个缓冲区填满时,将该缓冲区写入磁盘并切换到下一个缓冲区。即使第一个缓冲区仍在写入,您也可以写入新缓冲区。
对评论的回应:
你不能真正“让内核不碍事”,它是系统中的最低级别,你必须在某种程度上经历它。您可能能够为您的磁盘控制器构建一个自定义版本的驱动程序(前提是它是开源的),并构建一个“高优先级”I/O 路径供您的应用程序使用。您仍然受制于磁盘控制器的固件和驱动器本身的固件/硬件,您不一定能预测或做任何事情。
传统上,硬盘驱动器在执行大型顺序 I/O 操作时性能最佳。驱动程序、设备固件和操作系统 I/O 子系统会考虑到这一点,并尝试将较小的 I/O 请求组合在一起,以便它们只需要向驱动器生成单个大型 I/O 请求。如果您一次只刷新 32K,那么您的写入可能会在某个级别被缓存、合并并一次全部发送到驱动器。通过击败这种合并,您应该减少 I/O 延迟“尖峰”的数量并看到更统一的磁盘访问时间。但是,这些访问时间将比您通常看到的中等时间更接近“尖峰”中看到的大时间。延迟峰值对应于未与任何其他请求合并的 I/O 请求,因此必须吸收磁盘寻道的全部开销。请求合并是有原因的;通过捆绑请求,您可以将驱动器寻道操作的开销分摊到多个命令上。击败合并会导致执行比通常更多的查找操作,从而使您的 I/O 速度整体变慢。这是一种权衡:您以偶尔发生异常的高延迟操作为代价来减少平均 I/O 延迟。然而,这是一个有益的权衡,因为与禁用合并相关的平均延迟增加几乎总是比拥有更一致的访问时间是优势更多。
我还假设您已经尝试调整线程优先级,并且这不是您的高带宽生产者线程因 CPU 时间而耗尽缓冲区刷新线程的情况。你确认了吗?
您说您不想干扰也在系统上运行的高带宽线程。您是否实际测试过各种输出缓冲区大小/数量并测量它们对另一个线程的影响?如果是这样,请分享您测量的一些结果,以便我们在头脑风暴时使用更多信息。
鉴于大多数机器拥有的内存量,从 32K 缓冲区移动到循环通过 4 个 32K 缓冲区的系统是内存使用量的一个相当无关紧要的跳跃。在具有 1GB 内存的系统上,缓冲区大小的增加仅代表系统内存的 0.0092%。尝试使用交替/旋转缓冲区的系统(为简单起见,从 2 开始)并测量对高带宽线程的影响。我敢打赌,额外的 32K 内存不会对另一个线程产生任何明显的影响。这不应该是“污染生产者线程的缓存”。如果您经常使用这些内存区域,则应始终将它们标记为“正在使用”,并且永远不应从物理内存中换出。被刷新的缓冲区必须保留在物理内存中以使 DMA 工作,第二个缓冲区将在内存中,因为您的生产者线程当前正在写入它。 确实,使用额外的缓冲区会减少生产者线程可用的物理内存总量(尽管只是非常轻微),但是如果您正在运行的应用程序需要高带宽和低延迟,那么您将设计您的系统,使其拥有超过 32K 的可用内存。
与其通过尝试强制硬件和低级软件执行特定的性能测量来解决问题,更简单的解决方案是调整您的软件以适应硬件。如果您测量您的最大写入延迟为 1 秒(为了更好的整数),请编写您的程序,以便刷新到磁盘的缓冲区至少在 2.5-3 秒内不需要重新使用。这样您就可以覆盖最坏的情况,并提供安全余量以防万一发生真正意外的情况。如果您使用的系统在 3-4 个输出缓冲区中循环,则不必担心在缓冲区被刷新之前重新使用它。您将无法过于密切地控制硬件,并且如果您已经在写入原始卷(无文件系统),那么您与可以操作或消除的硬件之间就没有太多东西了。如果您的程序设计不灵活并且您看到不可接受的延迟峰值,您总是可以尝试更快的驱动器。固态硬盘不必“寻找”来执行 I/O 操作,因此您应该会看到相当均匀的硬件 I/O 延迟。