【问题标题】:Plotting thousands of files with python用python绘制数千个文件
【发布时间】:2014-10-15 10:27:58
【问题描述】:

我有顺序或 10^5 个二进制文件,我在 for 循环中使用 numpy 的 fromfile 并使用 pyplot 的 imshow 进行绘图。每个文件大约需要一分钟来读取和绘制。

有没有办法加快速度?

这里有一些伪代码来解释我的情况:

#!/usr/bin/env python

import numpy as np
import matplotlib as mpl
mpl.use('Agg')

import matplotlib.pyplot as plt

nx = 1200 ; ny = 1200

fig, ax = plt.subplots()
ax.set_xlabel('x')
ax.set_ylabel('y')

for f in files:
  data = np.fromfile(open(f,'rb'), dtype=float32, count=nx*ny)
  data.resize(nx,ny)
  im = ax.imshow(data)
  fig.savefig(f+'.png', dpi=300, bbox_inches='tight')
  im.remove()

我发现最后一步很关键,以免内存爆炸

【问题讨论】:

  • 我猜问题是有非常大的文件?在这种情况下,我认为真的没有。您也许可以使用multiprocessing 进行并行化。
  • @Al.Sal - np.fromfile 实际上在速度上与在 C 中做同样的事情相同。这里的瓶颈是使用 matplotlib 渲染图像,而不是读取数据。
  • @Shahar - 我非常惊讶于读取 5.7 MB 文件需要 12 秒。 (相比之下,在我的系统上使用相同方法读取 9GB 文件大约需要 24 秒。)这是通过网络驱动器吗?无论如何,这似乎很不寻常......尽管如此,即使存在 IO 瓶颈,multiprocessing 也应该在这里有所帮助。 Matplotlib 渲染图像需要相当长的时间,没有理由不能在多核上独立完成。
  • @JoeKington,文件是本地的,在 SSD 上!我同意 12 秒很长,但我不知道该怎么做。就multiprocessing 而言,我会试一试,但我之前没有在 python 中做过这样的事情,所以指针将不胜感激。
  • @Ben,我是。我的代码是从终端调用的.py 可执行文件。我不会梦想从 Canopy 内部运行它。我注意到使用 Canopy 的 python 解释器作为额外信息可能与我遇到的缓慢有关。

标签: python numpy imshow fromfile


【解决方案1】:

由于图像数量非常多,并且您使用的是imshow,我建议采用不同的方法。

  1. 创建具有所需尺寸和空白图像的输出文件(任何颜色都可以,只要它与书脊颜色不同)
  2. 存图到template.png
  3. 使用scipy.ndimage.imread 加载template.png
  4. 将图像数据加载到数组中
  5. 使用颜色图将数据转换为颜色
  6. 缩放图像以适合模板的像素尺寸 (scipy.ndimage.zoom)
  7. 将像素数据复制到模板中
  8. 将生成的图像保存为scipy.ndimage.save
  9. 根据需要多次重复步骤 4 - 8

这将绕过很多渲染内容。一些cmets:

  • 第 1 步可能需要花很多时间(尤其是抗锯齿可能需要注意,在刺的边缘有一个清晰的黑/白边框是有益的)
  • 如果第4步很慢(我不明白为什么),试试numpy.memmap
  • 如果可以的话,尝试使用可以通过简单算术运算对数据产生的颜色图(例如,灰度、带有伽马的灰度等),那么可以加快第 5 步
  • 如果您可以处理数据未缩放的图像(即原始imshow 使用的区域为1200x1200),则可以摆脱缓慢的缩放操作(步骤6);如果您可以按整数下采样,它也会有所帮助
  • 如果您需要在步骤 6 中重新采样图像,您还可以查看 cv2 (OpenCV) 模块中的函数,这可能比 scipy.ndimage 中更通用的函数更快

就性能而言,最慢的操作是 5、6 和 9。我希望该函数每秒能够处理十个数组。在此之上,磁盘 I/O 将开始成为一个限制因素。如果处理步骤是限制因素,我将启动脚本的四个(假设有四个核心)副本,每个副本都可以访问不同的 2.5 x 10^4 图像集。对于 SSD 磁盘,这不会导致 I/O 寻道灾难。

不过,只有分析才能说明问题。

【讨论】:

    【解决方案2】:

    奇怪的是,重新启动后,我通常不采用的解决方案是,每个文件的读取时间减少到 ~0.002 秒(平均),渲染时间是 ~0.02 秒。保存.png 文件大约需要 2.6 秒,所以总而言之,每帧大约需要 2.7 秒。

    我接受了@DrV 的建议,

    ...我将启动脚本的四个(假设有四个核心)副本,每个副本都可以访问不同的 2.5 x 10^4 图像集。对于 SSD 磁盘,这不会导致 I/O 寻道灾难。

    将文件列表划分为 8 个子列表并运行我的脚本的 8 个实例。

    @DrV 的评论

    此外,如果文件不在 RAM 缓存中,则读取 5.7 MB 文件的 0.002 秒读取时间听起来并不现实,因为它表明磁盘读取速度为 2.8 GB/秒。 (快速 SSD 可能仅达到 500 MB/s。)

    让我对笔记本电脑 (MacBookPro10,1) 的读/写速度进行了基准测试。我使用以下代码生成了 1000 个具有 1200*1200 随机浮点数(4 字节)的文件,使得每个文件为 5.8 MB(1200*1200*4 = 5,760,000 字节),然后一个一个地读取它们,对过程进行计时。代码从终端运行,永远不会占用超过 50 MB 或内存(对于仅在内存中保存一个 5.8 MB 的数据数组来说已经很多了,不是吗?)。

    代码:

    #!/usr/bin/env ipython
    
    import os
    from time import time
    import numpy as np
    
    temp = 'temp'
    if not os.path.exists(temp):
        os.makedirs(temp)
        print 'temp dir created'
    os.chdir(temp)
    
    nx = ny = 1200
    nof = 1000
    print '\n*** Writing random data to files ***\n'
    t1 = time(); t2 = 0; t3 = 0
    for i in range(nof):
        if not i%10:
            print str(i),
        tt = time()
        data = np.array(np.random.rand(nx*ny), dtype=np.float32)
        t2 += time()-tt
        fn = '%d.bin' %i
        tt = time()
        f = open(fn, 'wb')
        f.write(data)
        f.close
        t3 += time()-tt
    print '\n*****************************'
    print 'Total time: %f seconds' %(time()-t1)
    print '%f seconds (on average) per random data production' %(t2/nof)
    print '%f seconds (on average) per file write' %(t3/nof)
    
    print '\n*** Reading random data from files ***\n'
    t1 = time(); t3 = 0
    for i,fn in enumerate(os.listdir('./')):
        if not i%10:
            print str(i),
        tt = time()
        f = open(fn, 'rb')
        data = np.fromfile(f)
        f.close
        t3 += time()-tt
    print '\n*****************************'
    print 'Total time: %f seconds' %(time()-t1)
    print '%f seconds (on average) per file read' %(t3/(i+1))
    
    # cleen up:
    for f in os.listdir('./'):
        os.remove(f)
    os.chdir('../')
    os.rmdir(temp)
    

    结果:

    temp dir created
    
    *** Writing random data to files ***
    
    0 10 20 30 40 50 60 70 80 90 100 110 120 130 140 150 160 170 180 190 200 210 220 230 240 250 260 270 280 290 300 310 320 330 340 350 360 370 380 390 400 410 420 430 440 450 460 470 480 490 500 510 520 530 540 550 560 570 580 590 600 610 620 630 640 650 660 670 680 690 700 710 720 730 740 750 760 770 780 790 800 810 820 830 840 850 860 870 880 890 900 910 920 930 940 950 960 970 980 990 
    *****************************
    Total time: 25.569716 seconds
    0.017786 seconds (on average) per random data production
    0.007727 seconds (on average) per file write
    
    *** Reading random data from files ***
    
    0 10 20 30 40 50 60 70 80 90 100 110 120 130 140 150 160 170 180 190 200 210 220 230 240 250 260 270 280 290 300 310 320 330 340 350 360 370 380 390 400 410 420 430 440 450 460 470 480 490 500 510 520 530 540 550 560 570 580 590 600 610 620 630 640 650 660 670 680 690 700 710 720 730 740 750 760 770 780 790 800 810 820 830 840 850 860 870 880 890 900 910 920 930 940 950 960 970 980 990 
    *****************************
    Total time: 2.596179 seconds
    0.002568 seconds (on average) per file read
    

    【讨论】:

    • 只有一条评论:savefig 花费的时间实际上是渲染时间加上 png 编码加上保存。快速渲染时间实际上并不涉及任何像素操作,它只是构建对象结构。此外,如果文件不在 RAM 缓存中,则读取 5.7 MB 文件的 0.002 秒读取时间听起来并不现实,因为它表明磁盘读取速度为 2.8 GB/秒。 (快速 SSD 可能仅达到 500 MB/s。)
    • @DrV - 我编辑了我的答案并添加了一个小基准来测试读/写速度。
    • 把你的1000改成10000,然后你就会看到了。如果您写入 1000 个文件,每个文件 5.76 MB,则写入磁盘的所有块都将在操作系统页面缓存中,并且读取速度会很快。此内存消耗不作为应用程序内存消耗可见。 (解决此问题的另一种方法是打开 OS X 活动监视器,选择磁盘选项卡,然后检查“写入的数据”和“读取的数据”数字。)使用类似的机器(16 GiB RAM)和 10000 个文件,我得到每次写入 0.0133 秒,每次读取 0.0141 秒。自己试试吧!