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



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

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

不是所有的图像都有相同的大小,但它们都有大约1mb的编码为PNG。它们有成千上万个,而且大部分RAM已经用于存储计算值。

除了购买更快的磁盘,或者使用RAID,还有什么最快的方法来读取一系列映像?

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

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

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

亲爱的堆栈溢出社区,

如前所述,这是根据您的许多建议所做的实验结果。特别感谢@user894763让我走上了"正确的道路"。

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

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

从这些实验中我学到了两件重要的事情:

  • 当涉及到磁盘中的文件时,操作系统磁盘缓存是王道(即操作系统尽可能地将文件操作保存在RAM中而不是物理设备中,并且它在这方面做得非常好)。
  • 与我最初的猜测相反,从磁盘读取映像是一个有CPU限制的操作,而不是I/O限制。

实验协议

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

"Fresh read"是指机器上的第一次读取。
"Cached read"是指在同一台机器上进行的第二次读取(以及随后的任何一次)。

所有数字大致为+- 10hz。

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倍的加速。

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"查找并打印平均像素值。

vip有自己的"。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

我想另一个因素是你们的处理时间太长了。如果你正在做一些密集的事情,读取速度和解码速度就不重要了。

你应该把阅读的顺序倒过来。也就是说,第一次从图像1读取到图像N,然后在第二次从图像N读取到图像1,然后在第三次从图像1读取到图像N,以此类推。这样你就会更多地使用磁盘缓存。

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

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

将映像放到单个文件中可能确实有助于减少查找(不过,这取决于文件系统碎片整理策略)。在这种情况下,您应该使用能够快速访问单个文件的归档,以便能够以反向顺序读取文件,例如:"zip"没有压缩

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

内存映射,特别是当您计划多次重新读取映像时,将是使用尽可能少的副本将数据放入RAM的最快方法。
使用"聪明的技巧"(如无缓冲读取)来利用DMA是不可取的,因为这不会使用缓冲区,这比磁盘快几个数量级。当触摸数据一次且仅一次时,这个可能是一个优势——但如果您想要多次读取一段数据(就像您的情况一样),则永远不会。普通的缓冲读取通常也比内存映射慢得多,因为它们需要进行内存复制。

在一个典型的硬盘上,您可以期望在第一次运行时获得大约100 MB/s的性能,在第二次和进一步运行时(在快速机器上可能更多)可以获得3-4 GB/s的性能。

解码PNG涉及到LZ77流的解压缩,所以这也可能成为一个限制因素。为了解决这个问题,可以使用多线程。多线程解码单个流并不是完全微不足道的,但是没有什么可以阻止您同时解码多个图像(非常微不足道)。

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

你应该问问自己,

  • 计算你在一个单元上计算的任何东西所需的时间(无论是完整的图像还是它的一部分)。
  • 在这段时间内,你可以读取多少单位的图像(假设是N)。

我不知道如何使单个图像单元的读取更快,但有别的东西你可以尝试。

创建一个共享/全局变量来保存image的单位。使用线程在其中存储一个图像单元。如果N小于1,这意味着你的读取速度比你消耗图像的速度要快,因此它对更快的读取没有多大帮助。然而,如果你的图像消耗更快(例如N个线程一起工作来消耗图像),那么你需要更多的线程来在内存中存储足够的图像单元。

从理论上讲,使用线程构建消费者-生产者模型是很简单的。但实现起来往往很棘手。

PS:在单个处理器上运行多个线程通常比普通的无线程程序效率低。除非你有多核机器,否则我看不出有什么改进的办法。

相关内容

最新更新