【问题标题】:real-time writes to disk实时写入磁盘
【发布时间】:2010-08-19 19:10:16
【问题描述】:

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

我已经用 dd 测试过磁盘。我没有在上面使用任何文件系统并直接写入磁盘(使用直接标志打开它)。我可以在 32K 块大小的情况下获得大约 100 MB/s。

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

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

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

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

【问题讨论】:

  • 你为什么不试试缓冲的东西。在处理性能问题时,不先测试就放弃它不是一个好习惯。
  • 请描述最大允许延迟。
  • 正如我所描述的,我目前正在缓冲,但我想尽量减少用于缓冲的内存量。因此,我可以获得的写入延迟越小越好,因为我可以使用更小或更少的缓冲区。
  • 您使用的是什么型号的驱动器?了解驱动器的规格将有助于确定硬件或软件是否可能导致此处出现问题。
  • 我使用的磁盘是seagate barracuda 7200.12 1TB

标签: c linux real-time


【解决方案1】:

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

【讨论】:

  • 感谢您的回复,我做了进一步的测试,发现通常只有少数几个较大的写入延迟会影响我的机器。我从机器上的一个线程获得了非常高的带宽输出。我想尽可能少地干扰该线程的缓存,所以我不想使用大量内存来缓冲数据,而是只缓冲到一个小区域,然后在需要缓冲之前将其写入磁盘再次写入(同时,数据生产者将使用另一个缓冲区)。有没有办法让内核脱离我的方式来防止高延迟
  • 感谢您对我之前评论的回复。不幸的是,由于应用程序的性质,我无法衡量增加缓冲区对生产者线程的影响。我发现我需要大约 100 个 32K 缓冲区来分摊长写入的成本。此内存量是 L1 缓存的重要组成部分,应该会影响应用程序在生产 CPU 上的操作。我将生产者和编写者固定在不同的 cpu 上,这样我就不用担心 cpu 资源了。
【解决方案2】:

只要你使用O_DIRECT | O_SYNC,你就可以使用ioprio_set()来设置你的进程/线程的IO调度优先级(虽然手册页说“进程”,我相信你可以传递一个TID,如gettid())。

如果您设置了实时 IO 类,那么您的 IO 将始终被授予对磁盘的第一个访问权限 - 听起来这就是您想要的。

【讨论】:

    【解决方案3】:

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

    我已经用 dd 测试过磁盘。我没有在上面使用任何文件系统并直接写入磁盘(使用直接标志打开它)。我可以在 32K 块大小的情况下获得大约 100 MB/s。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    【讨论】:

    • 正如我在问题中所说,我没有使用文件系统。我也在使用 O_DIRECT 标志。这是一个实时约束,如果在我需要再次写入缓冲区的时间内没有完成写入,那么数据就是垃圾。实时的定义不是需要
    • @dschatz:O_DIRECT + 无文件系统:您的硬件有问题。例如。所有现代硬盘也都有写缓存。几乎所有台式机驱动器都忽略了同步命令(正如他们所说的“提高性能”),只有所谓的“RAID”或“服务器就绪”品种真正支持同步。实时:您没有提及您实际需要的写入延迟。
    • @dschatz:顺便说一句,不要忘记您所做的 IO 数量的限制。缓存机制的存在正是出于以下原因:突破 IO 限制。大多数驱动器每秒不能超过 200 次 IO。 IOW,如果你在 32K 块中写入无缓冲,200*32K = 6.4M/s 是你的写入速度的极限。
    • dd 能够以 32K 的写入块显着高于 6.4MB/s。这是直接标志打开,没有文件系统。
    • @dschats:这意味着您使用的桌面 HDD 并不真正支持任何类型的同步,对它们的 O_DIRECT 是无操作的,它们会自行缓冲。我敢打赌dd 会有同样的延迟问题。尝试为服务器/RAID 级硬盘更换硬盘。 (这几乎只是它们之间的区别:以前的缓冲区数据随心所欲,后来遵守主机命令将数据刷新到盘片。)
    【解决方案4】:

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

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

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

    【讨论】:

    • 我正在直接写入磁盘。上面没有文件系统。我正在以 32K 块(不是文件)写入数据。我知道,如果我不经常将数据流式传输到磁盘,则会涉及一些旋转延迟,但我无法想象我会因此而得到一些慢 100 倍的写入。如果我错了,请纠正我。
    • 即使使用您的解决方案,我的写作仍然需要时间。我总是可以条带化到更多磁盘,但我需要知道为什么单个写入花费的时间比我预期的要长
    • 没有文件系统使它有点复杂,一种可能是直接模式下的设备驱动程序不会返回,直到所有数据物理上都在驱动器上,单个巨大的 dd 没有效果,但它可能为每个单独的写入添加很多内容。
    • dd 也需要块大小。我将其设置为 32K,这与我正在做的事情相同。这转化为完全相同的 write() 系统调用,因此没有“单个巨大的 dd”。此外,我怀疑任何设备驱动程序都声称只有在数据在磁盘上而不是在其 SRAM 中时才完成写入。此外,没有文件系统会显着简化,而不是复杂化。
    • 如果您执行了 'dd' of say count=1000 bs=32K 块,那么只有一次写入磁盘并且 dd 将占用 bytes*rate + 一次等待缓冲区刷新开销。如果您写入 1000 个单独的 32k 块,则每个 write() 都会产生提交开销。不同的操作系统确实会返回是否完全提交的数据 - 人们发现 Linux/Windows 的速度要慢得多。
    【解决方案5】:

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

    【讨论】:

      猜你喜欢
      • 2018-11-07
      • 2013-03-25
      • 1970-01-01
      • 2013-01-02
      • 2011-10-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多