5

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

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

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

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

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

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

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

4

5 回答 5

5

亲爱的堆栈溢出社区,

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

tl;博士在未压缩的 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 倍的加速。

于 2013-06-17T16:45:39.507 回答
3

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

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

于 2013-05-18T12:13:25.947 回答
1

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

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

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

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

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

于 2013-05-17T14:03:20.307 回答
0

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

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

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

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

于 2013-05-17T13:39:51.587 回答
0

你应该问问自己,

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

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

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

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

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

于 2013-05-17T13:55:16.280 回答