读取图像序列的最快方法是什么?

5
我有一个速度关键的程序,需要从磁盘中重复读取图像并计算出值。这些图像太多了,无法存储在内存中。
同一组图像将被读取,我们不会更改/编辑它们,并且它们的顺序是固定的。
并非所有图像都具有相同的大小,但当作为PNG编码时,它们大约为1 Mb。其中有数万个图像,大部分RAM已用于存储计算出的值。
除了购买更快的磁盘或使用RAID外,有什么最快的方法可以读取一系列图像?
将它们全部放在一个大的tar文件中(并使用自定义untaring代码读取它们),而不是作为单独的文件放在文件夹中,是否会更快?
我找不到PNG解码的多线程实现,因此这个阶段也可能成为瓶颈。使用WebP代替PNG是否能提供额外的速度优势?
还应考虑/评估哪些其他想法?

1
如果我要猜的话,没有比一个一个读取更快的方法了。想一想,瓶颈应该是磁盘访问,而且没有绕过它的办法...唯一能想到的就是选择一种图像格式,使得磁盘->CPU传输的开销更小。解码图像可能比从磁盘读取文件要快得多。 - Ian Medeiros
你尝试过其他替代方案并获得了一些分析结果吗?使用SSD硬盘是一个选择吗? - Roger Rowland
也许你应该先读取它们一次,计算出你需要的任何值/信息,并存储这些信息,这样你就不必再次读取所有图像(至少在图像或你需要的计算信息发生变化之前不用)。 - twalberg
@SigTerm 不用担心,我的工作站上没有运行任何杀毒软件;p。对于这个问题,机器上只有两个应用程序在运行,我的速度敏感程序和操作系统;没有其他东西。 - rodrigob
@RogerRowland 我确实可以访问SSD,但是集群中并非所有机器都有SSD。我提出这个问题是为了了解评估的想法。完成后,我会报告我的结果。 - rodrigob
显示剩余7条评论
5个回答

6
亲爱的stack overflow社区,
如承诺所述,这里是基于您们众多建议所做实验的结果。特别感谢@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倍至10倍的速度提升。


根据 https://groups.google.com/a/webmproject.org/forum/?fromgroups=#!topic/webp-discuss/FPOfZs2cCS4 尝试使用 "cwebp -preset photo -q 100" 而不是 "cwebp -preset photo -lossless"。新的tar文件现在约为100 MB,读取WebP文件的刷新率为90 Hz,缓存为105 Hz。从tar文件中读取时,它是115 Hz(无论是刷新还是缓存)。比起pnm + tar更好了,但仍然不如pnm + tar(可能已经引入了轻微的压缩伪影)。 - rodrigob

3

PNG并非速度优先设计。它比JPEG更慢,而比TIFF文件大小还要大。如果您不得不使用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进行基准测试,它应该既能更快地解压缩,又更紧凑。 - rodrigob
WebP的初始结果并不令人鼓舞,让我们看看它的发展如何。https://groups.google.com/a/webmproject.org/forum/?fromgroups=#!topic/webp-discuss/FPOfZs2cCS4 - rodrigob

1
你应该反转阅读顺序。也就是说,在第一遍阅读中,从图像1到图像N进行阅读,然后在第二遍阅读中,从图像N到图像1进行阅读,然后在第三遍阅读中,从图像1到图像N进行阅读,以此类推。这样,你会更频繁地使用磁盘缓存。
同时,使用不同的线程同时处理(或至少加载)多个图像,可能也有助于提高整体吞吐量,因为操作系统将能够优化磁盘查找。
如果操作系统对AIO有很好的支持,那么也可能会有益处。
将图像放入单个文件中确实有助于最小化查找(取决于文件系统的碎片整理策略)。在这种情况下,你应该使用具有快速访问单个文件的存档,以便能够按相反的顺序读取文件,例如“zip”而无需压缩。
使用内存映射时,应该有一个选项来要求操作系统预取内存映射文件的一部分(例如MAP_POPULATE)。以这种方式读取存档的大部分内容可能比逐块读取更快。

倒序听起来像是一个有趣的想法。然而在我的情况下,在两次操作之间,我将会把一些结果存储到磁盘上。这可能会抵消建议技巧所带来的好处(除非存储的结果比磁盘缓存小得多,我猜)。 - rodrigob

0

内存映射,特别是当您计划多次重新读取图像时,将是最快的方法,可以尽可能少地复制数据到RAM中。
使用“聪明的技巧”(例如未缓冲读取)来利用DMA并不可取,因为这不会使用缓冲区,而缓冲区比磁盘快几个数量级。这在您只需一次接触数据的情况下可能是一个优势,但在您需要多次读取某个片段的情况下则永远不是。通常,正常的缓冲读取速度通常比内存映射慢,因为它们需要进行内存复制。

在典型的硬盘上,您可以期望第一次运行的性能约为100 MB/s,并且从缓冲区中可以获得3-4 GB/s的速度,甚至在快速机器上可能更快。

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

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

即使如此,内存映射仍然是一种好处,尽管它稍微少了一些(缓冲区显然对于比物理RAM集合大得多的情况帮助不太大)。有了映射,您只需要在物理RAM中进行一次副本(操作系统拥有的那个会为您的应用程序获取一个页表项),因此您实际上消耗的物理内存减半。此外,您还可以节省实际复制的时间,并且预取工作更加“自然”和有效。 - Damon
我可以看到一些RAM被节省了,但是关于预取,当进行大量顺序读取时,这似乎并不是http://lemire.me/blog/archives/2012/06/26/which-is-fastest-read-fread-ifstream-or-mmap/的情况。 - rodrigob
我以前的一个问题(https://dev59.com/7uo6XIcBkEYKwwoYTSou)是关于另一个问题,但包括内存映射与顺序读取的基准测试。内存映射比任何其他方法都要快。您链接到的网站上的计时肯定不正确,甚至不可信(流比原始libc读取更快,比内存映射更快)。如果您仔细想想,这在大多数操作系统上甚至在理论上都是不可能的,因为它们通过文件映射内部实现“读取”。libc在其上进行分层(带有额外的副本),而流则在其上进行分层。 - Damon
很有可能在读取微小的数据量(例如读取1-2个字节数百万次)时,freadread(或ReadFile)更快,因为缓冲可以减少系统调用开销,但是对于文件映射来说并非如此。通过先将数据复制到库缓冲区,然后再复制到应用程序中的另一个位置,无法更快地访问已经存在的数据。 - Damon
@user1764961:不对。如果可用,页面故障自然使用DMA(在不是15年前的硬件和不是15年前的操作系统上是默认情况),但对于标准I/O,您确实可以控制是否通过DMA对您的地址空间进行I/O。根据您使用的API和模式,您会获得或不会获得DMA。在Windows下,这将是重叠I/O,在Linux下则使用kaio,在两种情况下都禁用缓冲区(正如我所指出的,这通常是反优化)。 - Damon
显示剩余2条评论

0

你应该问问自己:

  • 计算任何单元(完整图像或其片段)需要多长时间。
  • 在此期间,您可以读取多少个图像单元(假设为N)。

我不知道如何使单个图像单元的读取更快,但还有其他方法可供尝试。

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

使用线程构建生产者-消费者模型在理论上很简单。但实现通常很棘手。

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


瓶颈不在于 CPU 处理速度,而是磁盘->CPU 传输。即使您实现多线程读取器,并使用 100% 的 CPU 时间来执行该操作,所有线程仍需要在访问磁盘内存之前访问北桥总线。在您所描述的情况下,生产者将会慢得多,这将使系统整体变慢。 - Ian Medeiros

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接