【问题标题】:Space complexity of split() function in pythonpython中split()函数的空间复杂度
【发布时间】:2020-02-18 13:03:28
【问题描述】:

我有一个问题,如果以下代码执行到位或具有额外的空间复杂性。鉴于该句子最初是一个字符串。感谢感谢帮助

sentence = "hello world"

sentence = sentence.split()

【问题讨论】:

  • 源是字符串,结果是列表。这怎么能就地执行?
  • @GPhilo 列表中的子字符串可以使用与原始不可变字符串相同的后备数组。
  • python 字符串基本上是不可变的,不会对它们“原地”发生任何操作,无论您做什么都会创建另一个字符串(或者在您的情况下,完全是另一个对象 - 一个列表)跨度>
  • @tobias_k 不过,这不是就地的意思。
  • 没有明确的答案,但是在拆分一个非常非常长的字符串 (~5GB) 后,我的交互式 Python 会话(使用 Python 3.6.8 的 IPython 5.5)的内存消耗大约翻了一番。

标签: python split space-complexity


【解决方案1】:

在 python 中,字符串是不可变的对象,这意味着它们根本不能“就地”改变。对它们的所有操作本质上都会占用新的内存空间,并且希望旧的未使用的操作会被 python 的垃圾收集过程删除(如果没有更多对这些对象的引用)。一种亲自查看的方法是:

>>> a = 'hello world'
>>> id(a)
1838856511920
>>> b = a
>>> id(b)
1838856511920
>>> a += '!'
>>> id(a)
1838856512944
>>> id(b)
1838856511920

如您所见,当ba 引用相同的底层对象时,它们在内存中的id 是相同的,但是一旦其中一个发生更改,它现在就会有一个新的@987654325 @ - 内存中的新空间。保持不变的对象 (b) 仍然具有相同的 place-id。

在你的例子中检查它:

>>> sentence = "hello world"
>>> id(sentence)
1838856521584
>>> sentence = sentence.split()
>>> id(sentence)
1838853280840

我们可以再次看到这些对象没有占用相同的内存。我们可以进一步探索它们占用了多少空间:

>>> import sys
>>> sentence = "hello world"
>>> sys.getsizeof(sentence)
60
>>> sentence = sentence.split()
>>> sys.getsizeof(sentence)
160

【讨论】:

  • 虽然这一切都是正确的,但我认为您对这个问题的理解过于字面意思,或者过于关注“就地”措辞。
  • @tobias_k 你可能是对的,这就是我在最后添加尺寸比较的原因,因为也许这对他来说才是真正重要的因素。无论哪种方式,您的实验都展示了这两个方面
  • 我认为sys.getsizeof 的使用在这里不正确,因为它不能反映实际额外需要的内存。看我的例子。
  • sys.getsizeof 本身并不能证明任何事情,这是真的,但它可以衡量占用了多少内存空间,无论原因如何。这并不准确,因为在实际对象中实现__sizeof__ 是此处显示的结果。在这方面,您对内存大小的实际测试更好,我同意
【解决方案2】:

正如 cmets 中所述,操作不能“就地”,因为这意味着在同一个数据结构中,但您显然是从字符串创建新的数据结构(列表)。我假设您的实际问题是split 返回的子字符串是否将使用与原始不可变字符串相同的支持字符数组。1)

一个快速的实验似乎表明他们没有。

In [1]: s = (("A" * 100000) + " ") * 50000

In [2]: len(s)
Out[2]: 5000050000

In [3]: l = s.split()

在第一步之后,top 表明ipython 进程使用了​​我的内存的~30%,而在split 之后它使用了~60%,所以后备数组占用了大部分内存, 不重复使用。当然,这可能是特定于实现的。我使用的是 IPython 5.5.0(基于 Python 3.6.8),但使用 Python 2.7.15 也得到了相同的结果。这似乎也适用于字符串切片。

1) 正是因为字符串是不可变的,所以这是可能的,据我所知,其他语言(如 Java)也可以这样做,尽管我目前无法对其进行测试。)


注意:这里使用sys.getsizeof 有点误导,因为这似乎只衡量实际数据结构的大小,而不是其中包含的元素。

In [4]: sys.getsizeof(s)
Out[4]: 5000050049

In [5]: sys.getsizeof(l)
Out[5]: 433816

据此,列表只占用原始拆分字符串的一小部分空间,但如上所述,实际内存消耗增加了一倍。

【讨论】:

  • 我不认为这可以是特定于实现的,因为 python 中字符串的定义是不可变对象。除此之外,很好的演示!
  • @OferSadan 但正是因为它们是不可变的,它们可以实际上共享相同的后备数组(我会假设他们这样做) .不确定,但我认为 Java 例如这样做。
  • cpython str.split() 最终调用了 C 函数 split_whitespace。它有一个优化,如果字符串未拆分,则确实使用相同的支持,否则总是为每个跨度分配新字符串
猜你喜欢
  • 1970-01-01
  • 2017-09-04
  • 1970-01-01
  • 2016-12-20
  • 2018-06-27
  • 2019-04-21
  • 1970-01-01
  • 2016-03-20
  • 1970-01-01
相关资源
最近更新 更多