当我们使用以下代码时,我们的代码需要10分钟来虹吸68000条记录:
new_file = new_file + line + string
但是,当我们执行以下操作时,只需1秒:
^{pr2}$代码如下:
for line in content:
import time
import cmdbre
fname = "STAGE050.csv"
regions = cmdbre.regions
start_time = time.time()
with open(fname) as f:
content = f.readlines()
new_file_content = ""
new_file = open("CMDB_STAGE060.csv", "w")
row_region = ""
i = 0
for line in content:
if (i==0):
new_file_content = line.strip() + "~region" + "\n"
else:
country = line.split("~")[13]
try:
row_region = regions[country]
except KeyError:
row_region = "Undetermined"
new_file_content += line.strip() + "~" + row_region + "\n"
print (row_region)
i = i + 1
new_file.write(new_file_content)
new_file.close()
end_time = time.time()
print("total time: " + str(end_time - start_time))
我用python编写的所有代码都使用第一个选项。这只是基本的字符串操作。。。我们正在从文件中读取输入,对其进行处理并将其输出到新文件。我百分之百地肯定第一种方法比第二种方法要长大约600倍,但为什么呢?在
正在处理的文件是csv,但使用~而不是逗号。我们在这里所做的就是使用这个csv,它有一个国家的列,并添加一个国家地区的列,例如拉丁美洲和加勒比海、欧洲、中东和非洲、北美等。。。cmdbre区域只是一本字典,以所有~200个国家为关键字,每个地区为值。在
一旦我改为追加字符串操作。。。循环在1秒内完成,而不是10分钟。。。csv中有68000条记录。在
实际上,两种方法的速度都一样慢,但对于某些优化来说,这实际上是官方Python运行时(cPython)上的实现细节。在
Python中的字符串是不可变的,这意味着当您执行“str1+str2”操作时,Python必须创建第三个string对象,并将str1和str2中的所有内容复制到它身上,无论这些部分有多大。在
inplace操作符允许Python使用一些内部优化,这样str1中的所有数据都不必再被复制一次,甚至可能还允许一些缓冲区空间用于进一步的连接选项。在
当人们了解了语言的工作原理后,从小字符串构建大文本体的方法是用所有字符串创建一个Python列表,循环结束后,对传入所有字符串组件的
str.join
方法进行一次调用。即使是在Python实现中,这一速度也会一直很快,而且不依赖于能够触发的优化。在CPython(引用解释器)对就地字符串连接进行了优化(当附加到的字符串没有其他引用时)。在执行}操作,因此更难对其进行优化)。在
+
时,它无法可靠地应用此优化,只有+=
(+
涉及两个实时引用,赋值目标和操作数,而前者不涉及{但您不应该依赖于此,根据PEP 8:
根据问题编辑进行更新:是的,您破坏了优化。连接了许多字符串,而不仅仅是一个字符串,Python从左到右求值,因此它必须首先执行最左边的连接。因此:
完全不同于:
^{pr2}$因为前者将所有的新的片段连接在一起,然后一次将它们附加到累加器字符串中,而后者必须使用不涉及
new_file_content
本身的临时表从左到右计算每个加法。为了清晰起见,添加了parens,就像你做的那样:因为在到达类型之前,它实际上不知道类型,所以它不能假设所有这些都是字符串,所以优化不会起作用。在
如果您将第二位代码更改为:
或者稍微慢一点,但仍然比慢代码快很多倍,因为它保持了CPython优化:
所以对于CPython来说,积累是显而易见的,您可以解决性能问题。但坦率地说,您应该在执行这样的逻辑追加操作时使用
+=
;+=
是有原因的,它为维护者和解释器提供了有用的信息。除此之外,就DRY而言,这是一个很好的实践;为什么不需要为变量命名两次呢?在当然,根据PEP8指南,即使在这里使用
+=
也是不好的形式。在大多数具有不可变字符串的语言中(包括大多数非cpythonpython解释器),重复的字符串连接是Schlemiel the Painter's Algorithm的一种形式,这会导致严重的性能问题。正确的解决方案是构建一个list
的字符串,然后join
将它们放在一起,例如:这通常在CPython字符串连接选项可用时更快,而且在非CPython Python解释器上也会非常快,因为它使用可变的
list
来有效地累积结果,然后允许''.join
预计算字符串的总长度,一次分配最后一个字符串(而不是沿途增量调整大小),然后只填充一次。在旁注:对于您的特定情况,您根本不应该累积或串联。您有一个输入文件和一个输出文件,可以逐行处理。每次添加或积累文件内容时,只需将它们写出来(我在编写过程中为PEP8遵从性和其他一些小的样式改进整理了代码):
实施细节深入研究
对于那些好奇于实现细节的人来说,CPython字符串concat优化是在字节码解释器中实现的,而不是在
str
类型本身上实现的(技术上,PyUnicode_Append
执行变异优化,但它需要解释器的帮助来修复引用计数,以便它知道它可以安全地使用优化;如果没有解释器的帮助,只有C扩展模块才能从优化中受益)。在当解释器detects that both operands are the Python level ^{} type (在C层,在Python3中,它仍然被称为} function ,它检查下一条指令是否是三条基本
PyUnicode
,这是一个2.x天的遗留问题,不值得更改),它调用a special ^{STORE_*
指令之一。如果是,并且目标与左操作数相同,则会清除目标引用,这样PyUnicode_Append
将只看到对操作数的单个引用,从而允许它使用单个引用调用str
的优化代码。在这意味着不你不仅可以通过
当相关变量不是顶级(全局、嵌套或局部)名称时,也可以中断它。如果您在操作一个object属性、
list
索引、dict
值等,甚至+=
也帮不了你,它不会看到“简单的STORE
”,因此它不会清除目标引用,所有这些都会导致超流、不到位行为:它还特定于
str
类型;在python2中,优化对unicode
对象没有帮助,在python3中,它对bytes
对象没有帮助,在这两个版本中,它都不会为str
的子类进行优化;这些子类总是走慢路。在基本上,对于不熟悉Python的人来说,在最简单的常见情况下,优化是尽可能好的,但是对于稍微复杂的情况,优化不会带来严重的麻烦。这只是强化了PEP8的建议:如果您可以通过正确的操作并使用
str.join
在每个解释器上对任何存储目标运行得更快,那么取决于解释器的实现细节是个坏主意。在相关问题 更多 >
编程相关推荐