尽管有足够的 RAM,但 numpy.append 中的 MemoryError

Posted

技术标签:

【中文标题】尽管有足够的 RAM,但 numpy.append 中的 MemoryError【英文标题】:MemoryError in numpy.append despite enough RAM 【发布时间】:2019-11-01 16:39:59 【问题描述】:

我一直在尝试将用 laspy 读取的点云的点列表附加到另一个点列表中,基本上是合并两个点云。合并多个点云时,我一直将所有点附加到同一个 np.ndarray 以便将其保存回一个 laspy 文件。现在,只要我要合并的所有点云的总大小超过 350 MB,我就会得到一个 MemoryError

我尝试使用不同的方法来编写点云文件,这样我就不必一次将所有点读入内存,但是失败了,因为 laspy 在编写点云文件时真的很奇怪,以下是我想通的一些事情:

laspy.File.points 的格式如下:
array([((24315,  12245, 12080, 0, 24, 0, 0, 0, 202, 23205, 24735, 21930),),
       ...,
       ((15155, -23292, -6913, 0, 56, 0, 0, 0, 343, 36975, 37230, 37485),)],
      dtype=[('point', [('X', '<i4'), ('Y', '<i4'), ('Z', '<i4'), ('intensity', '<u2'), ('flag_byte', 'u1'), ('raw_classification', 'u1'), ('scan_angle_rank', 'i1'), ('user_data', 'u1'), ('pt_src_id', '<u2'), ('red', '<u2'), ('green', '<u2'), ('blue', '<u2')])])
laspy.File.points的变量类型为numpy.ndarray laspy.File.points 的形状是 (&lt;numberOfRows&gt;,) => 一维数组,尽管它每行有 12 个值(?) 行的类型为numpy.void 为了写入laspy.File,您需要在写入模式下创建一个新文件,从现有文件复制标题并将 File.points 设置为与上述类型完全相同的 numpy 数组。设置点一次后,不能再设置,也就是说设置点时需要知道最终的行数。 您可以使用laspy.File.set_x(&lt;arrayOfXValues&gt;)(和类似方法)更改行的值,长度必须与laspy.File.points 相同

现在我的 PC 有 16 GB RAM,当我开始合并时,其中大约 10 GB 是空闲的。使用psutils 我得到了我的used 和available 内存,而且我的可用内存永远不会低于9 GB。使用psutil.Process(os.getpid()).memory_info().rss,我得到了这个进程使用的内存,它永远不会超过650 MB。

合并时,我读取第一个文件,然后遍历其他文件,逐个读取它们并调用numpy.append(combinedPoints, otherPointcloudPoints) 将所有点堆叠在一起。但是,当上面列出的条件为真时,这会引发 MemoryError

这是将多个点云合并到一个新点云的代码(这一切都发生在一个类PointCloudFileIOself.filelaspy.File 的一个实例)。 util.inMB 计算从字节到兆字节的大小。

    def mergePointClouds(self, listPaths, newPath):
        realSize = util.inMB(psutil.Process(os.getpid()).memory_info().rss)
        print("Process Memory used at start: :.2fMB".format(realSize))
        print("Available memory at start: :.2fMB".format(util.inMB(psutil.virtual_memory().available)))

        pointsOwn = self.file.points
        firstOtherReader = PointCloudFileIO(listPaths[0])
        pointsCombined = np.append(pointsOwn, firstOtherReader.file.points)

        realSize = util.inMB(psutil.Process(os.getpid()).memory_info().rss)
        print("Process Memory used after first merge: :.2fMB".format(realSize))
        print("Available memory after first merge: :.2fMB".format(util.inMB(psutil.virtual_memory().available)))

        for i in range(1, len(listPaths)):
            otherReader = PointCloudFileIO(listPaths[i])
            otherPoints = otherReader.file.points

            pointsCombined = np.append(pointsCombined, otherPoints)

            realSize = util.inMB(psutil.Process(os.getpid()).memory_info().rss)
            print("Process Memory used in loop: :.2fMB".format(realSize))
            print("Available memory in loop: :.2fMB | Used: :.2fMB | Percent: %".format(util.inMB(psutil.virtual_memory().available), util.inMB(psutil.virtual_memory().used), psutil.virtual_memory().percent))

        outFile = File(newPath, mode='w', header=self.file.header)
        outFile.points = pointsCombined
        outFile.close()

对于我拥有的几乎所有用例,这都可以正常工作。它将所有提供的点云合并到新文件中的新点云中。然而,当生成的点云有点太大时,尽管内存比需要的多,我得到一个MemoryError

这是我使用these pointclouds (download .laz files) 启动程序时的日志,您需要先使用 laszip 解压缩 .laz 文件,然后才能使用 laspy(至少在使用 Windows 时):

Process Memory used at start: 21.18MB
Available memory at start: 9793.35MB | Used: 6549.50MB | Percent: 40.1%
Process Memory used after first merge: 381.63MB
Available memory after first merge: 9497.64MB | Used: 6845.20MB | Percent: 41.9%
Process Memory used in loop: 559.52MB
Available memory in loop: 9309.36MB | Used: 7033.48MB | Percent: 43.0%
Process Memory used in loop: 637.05MB
Available memory in loop: 9301.00MB | Used: 7041.85MB | Percent: 43.1%
Traceback (most recent call last):
  File "optimization_test.py", line 7, in <module>
    f1.mergePointClouds(paths, "someShiet.las")
  File "C:\Users\viddie\Desktop\git\GeoLeo\geoleo\pointcloud.py", line 175, in mergePointClouds
    pointsCombined = np.append(pointsCombined, otherPoints)
  File "C:\Users\viddie\AppData\Local\Programs\Python\Python36-32\lib\site-packages\numpy\lib\function_base.py", line 5166, in append
    return concatenate((arr, values), axis=axis)
MemoryError

如果有人知道造成这种情况的原因,我们将不胜感激。

【问题讨论】:

请注意,错误出现在 concatenate 调用中。 np.append 只是一个只需要 2 个数组的覆盖函数。 concatenate 接受整个数组列表。无论如何,它每次调用都会创建一个全新的数组。它不会像列表追加那样就地修改基础。通常我们建议将数组收集在一个列表中,并在最后做一个concatenate。通常它会更快。我不能说它是否会避免 ME - 对于即将发生或稍后发生的大数据。 有了dtype,你就有了一个structured array, 1d 有多个字段。 itemsize 给出每条记录的大小(大约 4*12),以字节为单位。正如您所发现的,当您连接这些数组时,它们都必须具有相同的复合 `dtype。 有几种内存映射方法可以在磁盘上存储数组,但我让其他人解决了这个问题。 从路径中的Python36-32 看来,您使用的是 32 位版本的 Python。在这种情况下,您的可用内存是无关紧要的,因为 Windows 只分配比 32 位应用程序少得多的内存。 (见this post) 【参考方案1】:

以防万一操作实际上不适合内存,您可以将一些硬盘驱动器专用于内存。

For Windows

或者您可以在Ubuntu 上使用交换空间。

也许从那开始,直到你弄清楚如何减少内存消耗。或者至少这可以通过确保您确实有足够的内存来帮助您进行故障排除。

【讨论】:

以上是关于尽管有足够的 RAM,但 numpy.append 中的 MemoryError的主要内容,如果未能解决你的问题,请参考以下文章

尽管有足够的可用内存,但Postgres会出现内存错误

MongoDB、NUMA 硬件、页面错误但有足够的 RAM 用于工作集、触摸命令或 vmtouch/dd 未加载到内存中

尽管堆保持不变,Java RAM 会增加? [复制]

R中的关联规则没有足够的RAM [关闭]

如何使用函数 numpy.append

django 性能调优