遍历数组中的 10^8 个元素时,PC 无响应

Pc going unresponsive when looping over a 10^8 elements in an array

当我执行 python 代码时,它会循环大小为 10^8 的数组。电脑变得无响应,需要大约 10 分钟才能执行代码。 完成脚本后,它会滞后一段时间。

那么这个问题是由于处理器弱还是内存问题,解决它的唯一方法是升级内存?

脚本可以像这样简单:

arr = [x for x in range(pow(10,8))]
for i in range( len(arr) ):
   arr[i]+=1

我的规格是: 内存为 8 GB。 OS 是 Ubuntu。 Python 3.6。 处理器:英特尔酷睿 i7-3632QM 2.20GHZ

关于究竟发生了什么的更多细节:当我 运行 脚本时,我可以看到 python 进程的内存使用量不断上升。然后 PC 变得无响应。它不响应我给出的任何操作。如果有任何东西在后台播放,它就会停止,如果我移动鼠标,光标将不会移动。直到脚本真正完成。然后它变得反应灵敏但有一段时间非常滞后。如果我尝试将活动应用程序切换到另一个最小化的应用程序,则需要一些时间。就好像 PC 刚刚启动一样。一切恢复正常需要一点时间。

简短的回答是,您的应用程序变得无响应,因为您已经完全饱和了 CPU 对大型数据集执行的数十亿次操作。当您的程序卡在这些循环中时,它无法执行任何其他操作并且似乎已锁定。

首先,您使用 range() 创建了 1 亿个项目。单独这个操作不会很快,因为有很多项目。

接下来,您将使用列表推导式遍历这 1 亿个项目并构建一个全新的列表。理解似乎毫无意义,因为您只是传递范围内的值,但也许您只是为了示例而简化它。

最后,您将使用 for 循环再次遍历理解生成的新列表中的所有项目。

那是 3 个循环和 3 个列表;一个用于 range(),另一个用于列表理解,第三个是 for 循环。您正在做大量工作,多次创建庞大的列表。

将项目附加到列表的过程需要多次操作,在 1 亿个项目时,即 100Mhz * 操作数。例如,如果需要 10 次操作,您将看到大约 1Ghz 的处理时间。这些不是真正的基准测试,但它们说明了执行大量像这样的小操作如何快速增加大量 CPU 时间。不仅如此,在内存中多次复制至少 100MB 的数据也需要额外的时间。所有这些都会导致缺乏响应,因为您的 CPU 已完全饱和。

如果您绝对需要预先构建如此庞大的列表,请确保只循环一次并在那个时候完成您需要对该项目执行的所有工作。这将减少重新创建列表的次数并节省内存,因为需要同时存储在内存中的列表更少。

如果你真的需要一个递增的数字,你可以使用 generator 来计算。生成器效率更高,因为它们 "lazy";他们一次只 "yield" 一个值,而不是一次返回整个列表 .在 Python 2 中,xrange() 是一个范围生成器,它的工作方式与范围完全相同,只是它一次生成一个值,而不是一次创建整个列表并返回它。

for i in xrange(pow(10,8)):
    # do some work with the current value of i

在 Python 3 中,没有 xrange() 因为 range() 函数 returns 默认生成器(技术上它是 range 类型,但它通常以相同的方式行事)。

这里解释一下range()xrange()的区别

http://pythoncentral.io/how-to-use-pythons-xrange-and-range/

最后,如果你真的需要使用像这样的大列表,Numpy 库对 "sparse lists" 进行了各种优化,它像普通列表一样工作,但做了一些巧妙的技巧来存储看似数百万的内容有效的项目。

这里发生的事情很可能是分页 / 交换1。由于虚拟地址 space,您系统上的每个进程都可以寻址大量内存 - 远远超过您计算机中的物理内存。如果所有进程一起使用的内存比您实际可用的内存多,则操作系统有麻烦 - 一种方法是 分页:将某些进程的数据从内存移动到磁盘。

由于您的磁盘(即使是 SSD)比 RAM 慢几个数量级,因此系统会变得无响应。例如,OS 决定将包含鼠标光标位置的内存块移动到磁盘上。每次更新游标时,都会引入巨大的延迟。即使在消耗所有内存的进程完成后,将所有数据从磁盘加载回 RAM 也需要一些时间。

举例来说,在我的具有类似处理器 (i5-3320M) 的系统上,您的示例代码仅需 20 秒即可完成,而不会影响整体系统响应能力——这是因为我有 16 GiB RAM。很明显 而不是 关于 "CPU [being saturated] with billions of operations"。如果您有一个四核处理器,并且该代码仅使用一个线程,那么您就有很多空闲的计算周期。即使您要用完所有 CPU 个周期,系统通常也会非常灵敏,因为 OS 调度程序可以很好地平衡您的计算任务和进程移动之间的 CPU 个周期你的鼠标光标。

Python 特别容易出现这个问题,因为它使用的内存比必要的多。 Python 我系统上的 3.6.1 使用 ~4 GiB 作为 arr 中的数据——尽管 10^8 64 位整数只会使用 800 MB。那只是因为 python 中的所有东西都是一个对象。如果您首先不在内存中永久存储任何内容或使用 numpy,则可以提高内存效率。但要讨论这一点,需要一个更面向问题的代码示例。

1:分页和交换之间有differences,但现在可以互换使用。