繁体   English   中英

实时写入磁盘

[英]real-time writes to disk

我有一个线程需要将数据从内存缓冲区写入磁盘数千次。 我对每次写入需要多长时间有一些要求,因为需要清除缓冲区以供单独的线程再次写入。

我已经用 dd 测试了磁盘。 我没有在其上使用任何文件系统并直接写入磁盘(使用直接标志打开它)。 我能够以 32K 的块大小获得大约 100 MB/s 的速度。

在我的应用程序中,我注意到我无法以接近这个速度将数据写入磁盘。 所以我调查了正在发生的事情,我发现有些写入需要很长时间。 我的代码块看起来像(顺便说一下,这是在 C 中):

last = get_timestamp();
write();
now = get_timestamp();
if (longest_write < now - last)
  longest_write = now - last;

最后我打印出最长的写入。 我发现对于 32K 缓冲区,我看到最长的写入速度约为 47 毫秒。 这太长了,无法满足我的应用程序的要求。 我认为这不能完全归因于磁盘的旋转延迟。 任何想法发生了什么以及我可以做些什么来获得更稳定的写入速度? 谢谢

编辑:我实际上使用了我在上面声明的类型的多个缓冲区并将它们之间的条带化到多个磁盘。 我的问题的一种解决方案是增加缓冲区的数量以分摊长写入的成本。 但是,我希望将用于缓冲的内存量保持在尽可能小,以避免弄脏产生写入缓冲区的数据的线程的缓存。 我的问题应该仅限于处理将小块写入磁盘的延迟差异以及如何减少它。

我假设您使用的是连接到标准计算机中内置磁盘控制器的 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 延迟。

只要你使用O_DIRECT | O_SYNC O_DIRECT | O_SYNC ,您可以使用ioprio_set()来设置进程/线程的 IO 调度优先级(尽管手册页说“进程”,但我相信您可以传递gettid()给出的 TID)。

如果您设置了实时 IO 类,那么您的 IO 将始终首先访问磁盘 - 听起来这就是您想要的。

我有一个线程需要将数据从内存缓冲区写入磁盘数千次。

我已经用 dd 测试了磁盘。 我没有在其上使用任何文件系统并直接写入磁盘(使用直接标志打开它)。 我能够以 32K 的块大小获得大约 100 MB/s 的速度。

dd的块大小与文件系统块大小对齐。 我猜你的日志文件不是。

另外,您的应用程序可能不仅会写入日志文件,还会执行其他一些文件操作。 或者您的应用程序并不孤单使用磁盘。

通常,磁盘 I/O 没有针对延迟进行优化,而是针对吞吐量进行了优化。 高延迟很正常 - 网络文件系统的延迟更高。

在我的应用程序中,我注意到我无法以接近这个速度将数据写入磁盘。 所以我调查了正在发生的事情,我发现有些写入需要很长时间。

一些写入需要更长的时间,因为在某个时间点之后,您将写入队列饱和,而操作系统最终决定将数据实际刷新到磁盘。 默认情况下,I/O 队列配置得非常短:以避免由于崩溃而导致过度缓冲和信息丢失。

注意:如果您想查看实际速度,请尝试在打开文件时设置O_DSYNC标志。

如果您的块确实对齐,您可以尝试使用O_DIRECT标志,因为这将消除 Linux 磁盘缓存级别上的争用(与其他应用程序)。 写入将以磁盘的实际速度工作。

使用dd 100MB/s - 没有任何同步 - 是一个高度综合的基准测试,因为您永远不知道数据已经真正到达磁盘。 尝试将conv=dsync添加到dd的命令行。

还尝试使用更大的块大小。 32K还是小。 几年前,当我测试顺序 I/O 和随机 I/O 时,IIRC 128K 大小是最佳的。

我看到最长的写入速度约为 47 毫秒。

“实时”!=“快速”。 如果我将最大响应时间定义为 50 毫秒,并且您的应用始终在 50 毫秒内响应(47 < 50),那么您的应用将被归类为实时。

我认为这不能完全归因于磁盘的旋转延迟。 任何想法发生了什么以及我可以做些什么来获得更稳定的写入速度?

我认为您无法避免write()延迟。 延迟是磁盘 I/O 的继承属性。 您无法避免它们 - 您必须期待并处理它们。

我只能想到以下选项:使用两个缓冲区。 第一个将由write() ,第二个 - 用于存储来自线程的新传入数据。 write()完成时,切换缓冲区,如果有东西要写,就开始写。 这样,线程总是有一个缓冲区可以将信息放入其中。 如果线程生成信息的速度比 write() 可以写入的速度快,则仍可能发生溢出。 在这种情况下,动态添加更多缓冲区(最多达到某个限制)可能会有所帮助。

否则,只有当您的应用程序是磁盘的唯一用户时,您才能实现(旋转)磁盘 I/O 的某种实时性。 (实时应用程序的旧规则适用:只能有一个。) O_DIRECT以某种方式帮助从等式中消除操作系统本身的影响。 (尽管由于文件扩展名的块分配,您仍然会以偶尔延迟的形式存在文件系统的开销。在 Linux 下,它工作得非常快,但仍然可以通过提前预分配整个文件来避免,例如通过写入零。 ) 如果时间真的很重要,请考虑为这项工作购买专用磁盘。 SSD 具有出色的吞吐量,并且不会受到搜索的影响。

您是写入新文件还是覆盖同一个文件?

与 dd 的最大区别可能是寻道时间,dd 正在流式传输到一个连续的(主要是)块列表,如果您正在编写大量小文件,则头部可能会在整个驱动器上寻找以分配它们。

解决问题的最好方法可能是取消在特定时间写入日志的要求。 您能否使用一组缓冲区,以便在新日志数据到达另一个缓冲区时写入(或至少发送到驱动器的缓冲区)一个缓冲区?

linux 不会直接向磁盘写入任何内容,它将使用虚拟内存,然后内核线程调用 pdflush 会将这些数据写入磁盘,可以通过 sysctl -w "" 控制 pdflush 的行为

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM