【问题标题】:Pc going unresponsive when looping over a 10^8 elements in an array循环遍历数组中的 10^8 个元素时,PC 无响应
【发布时间】:2017-06-04 23:31:25
【问题描述】:

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

那么这是由于处理器薄弱造成的问题还是 Ram 问题,修复它的唯一方法是升级 ram?

脚本可以很简单:

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

我的规格是: 内存为 8 GB。 操作系统是 Ubuntu。 蟒蛇 3.6。 处理器:英特尔酷睿 i7-3632QM 2.20GHZ

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

【问题讨论】:

  • 与其他语言相比,Python 的链接速度很慢。尝试一些 golang、rust、c 或 c++。
  • 你可以用10 ** 8代替math.pow顺便说一句
  • @cat 这不是这里的慢操作。
  • @Soviut 很清楚,但这不是 JS
  • list(range(10**8)) 那么循环不会更快吗?或者对于给定的示例list(range(1,10**8+1)) 具有相同的最终结果

标签: python arrays performance cpu


【解决方案1】:

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

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

为了说明,在我的具有可比处理器 (i5-3320M) 的系统上,您的示例代码仅在 20 秒内完成,而不会影响整体系统响应能力 - 那是因为我有 16 GiB RAM。很明显,“CPU [正在饱和] 有数十亿次操作”,这不是。假设您有一个四核处理器,并且该代码只使用一个线程,那么您就有很多空闲的计算周期。即使您要用完所有 CPU 周期,系统通常也会非常灵敏,因为操作系统调度程序可以很好地平衡您的计算任务和移动鼠标光标的进程之间的 CPU 周期。

Python 尤其容易出现这个问题,因为它使用的内存超出了必要的范围。我系统上的 Python 3.6.1 对 arr 中的数据使用 ~4 GiB - 即使 10^8 64 位整数仅使用 800 MB。这只是因为 python 中的一切都是对象。如果您首先不将任何内容永久存储在内存中或使用numpy,则可以提高内存效率。但要讨论这个问题,需要一个更面向问题的代码示例。

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

【讨论】:

  • 是的,这很有意义。我知道这段代码在任何意义上都远非高效。它只是解决问题。
【解决方案2】:

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

首先,您正在使用 range() 创建 1 亿个项目。仅此操作不会很快,因为项目很多。

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

最后,您使用 for 循环再次循环从推导式中新生成的列表中的所有项目。

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

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

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

如果您真正需要的是一个递增的数字,您可以使用generator 进行计数。生成器的效率要高得多,因为它们是“懒惰的”;它们一次只“产生”一个值,而不是一次返回整个列表 .在 Python 2 中,xrange() 是一个范围生成器,其工作方式与范围完全相同,只是它一次生成一个值,而不是一次创建整个列表并返回它。

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

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

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

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

最后,如果你真的需要像这样使用巨大的列表,Numpy 库对“稀疏列表”进行了各种优化,它们的作用类似于常规列表,但会使用一些巧妙的技巧来有效地存储看似数百万的项目。

【讨论】:

  • First, you're creating 100 million items using range() 不,至少在 Python 3 中,情况并非如此
  • @cat 介意解释一下你的意思吗? “事实并非如此”并没有澄清任何事情。
  • 那么python 3中的range()会返回一个生成器吗?
  • Python 3 range() 不返回生成器,而是自定义 range 类型的实例。不过,它很懒,因为它并没有实现整个列表,所以人们经常会感到困惑。
  • 由于抢先式多任务处理,任何现代系统都可以在 CPU 满负荷下保持响应。因此,这个答案没有成效。
猜你喜欢
  • 1970-01-01
  • 2020-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-06
  • 1970-01-01
  • 2021-02-11
相关资源
最近更新 更多