【问题标题】:What is the fastest way to read a sequence of images?读取图像序列的最快方法是什么?
【发布时间】:2013-05-12 16:43:50
【问题描述】:

我有一个速度关键的程序,它将反复读取磁盘中的图像并从中计算值。图像太多,无法存储在内存中。

将读取同一组图像,我们不会更改/编辑它们,它们的顺序是固定的。

并非所有图像都具有相同的大小,但它们在编码为 PNG 时都有大约 1 Mb。它们有数万个,并且大部分 RAM 已用于存储计算值。

除了购买更快的磁盘或使用 RAID,读取图像序列的最快方法是什么?

将它们全部放在一个大的 tar 文件中(并使用自定义解压缩代码读取它们)而不是作为文件夹中的单个文件会更快吗?

我找不到PNG解码的多线程实现,所以这个阶段也可能成为瓶颈。使用 WebP 代替 PNG 会提供额外的速度优势吗?

我应该考虑/评估哪些其他想法?

【问题讨论】:

  • 如果我猜的话,没有比一本一本阅读更快的方法了。考虑一下,瓶颈将是磁盘访问,并且没有办法解决它......我唯一想到的就是选择一种图像格式,它可以减少磁盘-> cpu 传输的开销。解码图像可能比从磁盘读取文件快很多。
  • 您是否尝试过任何替代方法并获得了一些分析结果?使用 SSD 磁盘是一种选择吗?
  • 也许您应该阅读它们一次,计算您需要的任何值/信息,然后存储该信息,这样您就不必再次阅读所有图像(至少直到图像或您需要从中计算出的信息会发生变化)。
  • @twalberg 这当然是一个选项,但我计算的确切数据会定期更改,结果会比原始图像更大。与在飞行中重新计算数据相比,写入和读取所有数据会更慢。
  • @IanMedeiros:将图像打包到单个文件中可能会更快,因为防病毒软件(如果存在)可能会扫描您打开的每个文件。如果您打开文件一次,就会有一次检查。如果你打开很多文件,就会有多个检查。

标签: c++ image performance io


【解决方案1】:

内存映射,特别是因为您计划多次重新读取图像,这将是使用尽可能少的副本将数据放入 RAM 的最快方法。
不建议使用像无缓冲读取这样的“聪明技巧”来利用 DMA,因为这不会使用比磁盘快几个数量级的缓冲区。这可能在触摸数据一次且仅一次时是一个优势 - 但如果您想多次阅读一篇文章,就像您的情况一样。正常的缓冲读取通常也比内存映射慢很多,因为它们需要进行内存复制。

在典型的硬盘上,第一次运行时您可以预期大约 100 MB/s 的性能,第二次和进一步运行时缓冲区的性能为 3-4 GB/s(在快速机器上可能更高)。

解码 PNG 涉及对 LZ77 流进行解压缩,因此这也可能成为限制因素。为了解决这个问题,您可以使用多线程。多线程解码单个流并非完全简单,但没有什么能阻止您同时解码多个图像(这非常简单)。

将图像连接成一个大文件可能会带来好处,因为它可以减少搜索次数,但这通常只有在您必须读取数百或数千个文件时才开始真正重要。在这种情况下,您最好也按照读取它们的顺序存储它们(希望这会导致磁盘上的布局连续,但不能保证)。

【讨论】:

  • 内存映射如何帮助提高速度?要读取的数据量比可用 RAM 大得多,并且数据是全通读取的。当我到达最后一张图像时,内存缓冲区将“丢失”第一张图像中的数据,所以下次我开始读取图像时,操作系统将不得不再次开始从磁盘读取。或者我错过了什么?
  • 即便如此,内存映射也是一个好处,尽管稍微少一些(缓冲区显然不会对比物理内存大得多的 RAM 集有太大帮助)。使用映射,您只需要物理 RAM 中的一个副本(操作系统拥有的副本为您的应用程序获取页表条目),因此您有效地消耗了一半的物理内存。此外,您无需进行实际复制,而且预取工作更加“自然”且更有效。
  • 我可以看到一些 RAM 是如何保存的,但是关于预取,这个 lemire.me/blog/archives/2012/06/26/… 在进行大量顺序读取时似乎不是这种情况。
  • This old question of mine 是关于一个不同的问题,但包括内存映射与顺序读取的基准。内存映射比任何其他方法都快。您链接到的站点上的时间肯定不正确,它们甚至不合理(流比原始 libc 读取快,比内存映射快)。如果您考虑一下,这在大多数操作系统上甚至在理论上都是不可能的,因为它们通过内部文件映射实现read。 libc 在其上分层(带有额外的副本),流在其上分层。
  • 很可能freadread(或ReadFile)在读取少量(读取几百万次1-2 字节)时更快,因为缓冲减少了系统调用开销,但是对于文件映射来说,没有这样的事情。通过先将数据复制到库缓冲区,然后再复制到应用程序中的另一个位置,无法更快地访问现成的数据。
【解决方案2】:

你应该问问自己,

  • 在一个单元(完整图像或其中的一部分)上计算您正在计算的任何内容需要多少时间。
  • 在此期间,您可以读取多少个图像单位(假设为 N)。

我不知道如何更快地读取单个图像单元,但您可以尝试其他方法。

创建一个共享/全局变量来保存图像的单位。使用线程在其中存储一个图像单元。如果 N 小于 1,则意味着您的阅读速度将比您消耗图像的速度快,因此再加快阅读速度也无济于事。但是,如果您使用图像的速度更快(例如 N 个线程一起工作以使用图像),那么您需要更多的线程来在内存中存储足够多的图像单元。

使用线程构建消费者-生产者模型在理论上是直截了当的。但实施通常很棘手。

PS:在单个处理器上运行超过 1 个线程通常比正常的无线程程序效率低。除非您拥有多核机器,否则我看不到改进的方法。

【讨论】:

  • 瓶颈不是CPU处理速度,而是磁盘->cpu传输。即使您实现了一个多线程读取器,使用 100% 的 CPU 时间来执行此操作,所有线程都需要在访问磁盘内存之前访问北桥总线。在您描述的情况下,生产者会慢得多,这会降低整个系统的速度。
【解决方案3】:

你应该颠倒阅读顺序。也就是说,在第一遍中从图像 1 读取到图像 N,然后在第二遍中从图像 N 读取到图像 1,然后在第三遍中从图像 1 读取到图像 N,依此类推。这样你会更多地访问磁盘缓存。

在不同的线程中一次处理(或至少加载)多个图像也可能有利于整体吞吐量,因为操作系统将能够优化磁盘寻道。

如果操作系统对 AIO 有很好的支持,那么它也可能是有益的。

将图像放入单个文件可能确实有助于最大程度地减少搜索(不过取决于文件系统碎片整理策略)。在这种情况下,您应该使用可以快速访问单个文件的存档,以便能够以相反的顺序读取文件,例如没有压缩的“zip”。

使用内存映射应该有一个选项来要求操作系统预取内存映射文件的一部分(例如 MAP_POPULATE)。以这种方式读取大部分存档可能会比逐块读取更快。

【讨论】:

  • 颠倒顺序听起来很有趣。但是在我的情况下,在两次传递之间,我会将一些结果存储到磁盘中。这可能会扼杀建议的技巧的好处(除非存储的结果比磁盘缓存小得多,我猜)。
【解决方案4】:

PNG 不是为速度而构建的。它比 jpeg 慢,并且不小于 tif。如果您被 PNG 卡住了,那么其他优化都不会产生任何影响。

例如:

$ time vips avg wtc.tif
117.853995
real    0m0.525s
user    0m0.756s
sys 0m0.580s
$ time vips avg wtc.png
117.853995
real    0m3.622s
user    0m3.984s
sys 0m0.584s

其中“wtc”是一张 10,000 x 10,000 像素的 RGB 照片,tif 是未压缩的条形格式,而 png 也是未压缩的,两个图像都在磁盘缓存中,“avg”查找并打印平均像素值。

vips 有自己的“.v”格式,它只是一个巨大的像素缓冲区。这种格式可以和 mmap() 并行读取,又快了一点:

$ time vips avg wtc.v
117.853995
real    0m0.162s
user    0m0.460s
sys 0m0.092s

如果您的图像可以被压缩,则权衡会有所改变。例如,jpeg 通常会压缩 10 倍,因此解码速度变得比光盘速度更重要。您可能希望使用优化的解码库(如 libturbojpeg)并一次处理多个文件。

$ time vips avg wtc.jpg
117.853995 
real    0m1.413s
user    0m1.696s
sys 0m0.564s

PNG 使用 libz,对于摄影图像,压缩率不会超过 2 倍。即使在相同的压缩级别下,它也比带有 deflate 的 tif 慢很多:

$ time vips avg wtc.tif
117.853995
real    0m3.154s
user    0m3.496s
sys 0m0.540s
$ time vips avg wtc.png
117.853995
real    0m4.888s
user    0m5.196s
sys 0m0.556s
$ ls -l wtc.*
-rw-r--r-- 1 john john  15150881 Feb 20  2012 wtc.jpg
-rw-rw-r-- 1 john john 135803013 May 18 12:47 wtc.png
-rw-rw-r-- 1 john john 143807446 May 18 12:53 wtc.tif
-rw-rw-r-- 1 john john 263509369 May 18 12:37 wtc.v

我想另一个因素是您的处理过程有多耗时。如果您正在做一些密集的事情,读取速度和解码速度将不重要。

【讨论】:

  • 我的处理目前是高度优化的(启用 GPU),它是如此之快以至于磁盘 I/O 成为从磁盘执行操作时的瓶颈。事实上,我已经确定 PNG 解压缩是一个问题。我需要使用无损格式,但是可以有多种选择。我很惊讶地看到读取“v”格式比 jpg 更快,这也适用于简单的 pgm 图像吗?我正在考虑对 WebP 进行基准测试,它应该既能更快地解压缩又更紧凑。
  • WebP 的初步结果并不令人鼓舞,让我们看看它是如何演变的。 groups.google.com/a/webmproject.org/forum/?fromgroups=#!topic/…
【解决方案5】:

尊敬的堆栈溢出社区,

这里承诺的是根据您的许多建议进行的实验结果。 特别感谢@user894763 如何让我走上“正确的道路”。

tl;dr 在未压缩的 tar 中使用 pnm 文件(是的,我说的是 pnm!)。

我在两台高端机器上做过实验,一台启用 SSD 磁盘,另一台使用网络文件系统。两者都具有高端 CPU,但在磁盘访问方面显示“频谱的两端”。令人惊讶的是,两台机器的结论是相同的。我只报告一组结果(用于后一种情况)。在两个实验中,文件格式之间的比率几乎相同。

从这些实验中我学到了两个重要的东西:

  • 当处理来自磁盘的文件时,操作系统磁盘缓存为王(即操作系统尽可能将文件操作保存在 RAM 中而不是物理设备中,它在这方面做得非常好)。
  • 与我最初的猜测相反,从磁盘读取图像是受 CPU 限制的操作,而不是受 I/O 限制的操作。

实验协议

我正在以固定序列读取一组约 1200 张图像,没有对图像进行任何计算,我只是测量将像素加载到内存中的时间。 tar 文件的大小约为 pnm 格式 600 MB,png 格式约为 300 MB,webp 格式约为 200 MB。

“新读取”是指在机器上完成的第一次读取。
“缓存读取”是指在同一台机器上完成的第二次读取(以及任何后续读取)。

所有数字大约为 +- 10 Hz。

webp fresh read: 30 Hz
webp cached read: 80 Hz

webp + tar fresh read: 100 Hz
webp + tar cached read: 100 Hz

png fresh read:  50 Hz
png cached read: 165 Hz

png + tar fresh read: 200 Hz
png + tar cached read: 200 Hz

pnm fresh read: 50 Hz
pnm cached read: 600 Hz

pnm + tar fresh read: 200 Hz
pnm + tar cached read: 2300 Hz

注意事项

有人告诉我,也许有办法改变 webp 压缩参数以加快解压速度。我怀疑它仍然无法匹配 pnm 性能。

请注意,我使用自定义代码读取 tar 文件中的图像,该文件是从磁盘“逐个图像”读取的。

我不知道为什么“新鲜”读取 webp 图像比 png 图像慢,我只能推测网络磁盘系统有一些“内部”缓存,这在一定程度上改变了行为。不过这不影响上课。

课程

  1. 如果您将多次读取一个文件(或一组文件),操作系统磁盘缓存将使所有未来的读取基本上“与从 RAM 中读取一样快”。

  2. 即使从磁盘读取,解压缩图像的时间也不容忽视。

  3. 将所有文件放入单个未压缩 (tar) 文件中,可以显着加快速度,因为操作系统会假设将读取整个文件,甚至在我们访问它们之前预加载未来的图像。仅在文件夹中读取时似乎不会发生这种情况。

  4. 如果小心谨慎,从磁盘读取一系列图像(特别是重复读取)时,速度可以提高 4 倍 ~ x10 倍。

【讨论】:

  • groups.google.com/a/webmproject.org/forum/?fromgroups=#!topic/… 之后尝试了“cwebp -preset photo -q 100”而不是“cwebp -preset photo -lossless”。新的 tar 文件现在约为 100 MB,新鲜读取 webp 文件为 90 Hz,缓存为 105 Hz。从 tar 文件来看,它是 115 Hz(新鲜的和缓存的)。更好,但不比 pnm + tar 更好(另外,我们可能引入了较小的压缩伪影)。
猜你喜欢
  • 2019-09-13
  • 2013-06-08
  • 2023-03-25
  • 1970-01-01
  • 1970-01-01
  • 2016-02-14
  • 1970-01-01
  • 2011-12-03
  • 2015-06-15
相关资源
最近更新 更多