我正在编写一个解析器来检测一堆源文件中的某些标识符。随后将通过添加前缀来重命名标识符以使其唯一。这个重命名过程只能在处理完所有文件后发生,因为它们中的一些是链接的(例如,类的类和实例化需要获得相同的类标识符)。
此过程的第一步是临时删除所有不必要的代码(字符串和括号内容+行/块注释),以使解析更容易,更省时。这些片段被剪切存储在deque中作为named元组,具有以下结构:(索引,值)。在解析器和重命名器完成它们的工作之后,这些部分被粘贴回原始位置(由于文件更改而产生偏移)。
以前的代码处理速度很快,但是当我尝试通过将所有修剪过的部分插回文件内容来重建文件时出现问题:
while self.trimmedCode:
key, value = self.trimmedCode.pop()
parsedContent = ''.join((parsedContent[:key],value,parsedContent[key:]))
某些文件包含大量字符串/注释,使重建过程非常缓慢(150,000次插入时为+6分钟)。我的问题?如何使索引的插入更有效?
由于字符串是不可变的,因此我尝试通过在执行所有插入之前将字符串转换为字符列表来实现性能提升。这将while循环的速度提高了大约10%。但是,后续的连接操作会使获得的优势无效:
charList = list(parsedContent)
while self.trimmedCode:
key, value = self.trimmedCode.pop()
charList[key:key] = value
parsedContent = ''.join(charList)
我的问题:有没有更有效的方法来完成这项任务(使用Python 2.7)?
相关的探查器统计数据:
Info:buildRenamedCopy重建文件并包含while循环,其中insertString执行连接操作。此测试在一组较小的文件(+ - 600个文件)
ncalls tottime percall cumtime percall filename:lineno(function)
1284 9.998 0.008 137.834 0.107 file.py:146(buildRenamedCopy)
180923 59.810 0.000 110.459 0.001 file.py:142(insertString)
182213 50.652 0.000 50.657 0.000 {method 'join' of 'str' objects}
答案 0 :(得分:4)
代码缓慢的原因是插入列表是O(N)
(其中N
是列表的长度)。这是因为插入点之后列表中的所有值都需要移动,以便为您要插入的新值腾出空间。
您可以通过使用新字符串(插入的值加上前一个字符)替换给定位置的现有值来解决此问题,而不是执行增加列表的切片分配。这仅适用于每个key
小于之前的所有O(N^2)
的情况(也就是说,如果您按降序迭代键)。
以下是您的代码的最低修改版本,该版本应该具有改进的渐近性能(从O(N)
到charList = list(parsedContent)
while self.trimmedCode:
key, value = self.trimmedCode.pop()
charList[key] = value + charList[key] # the change is here! No O(N) slice assignment!
parsedContent = ''.join(charList)
):
while
注意,您也可以使用更简单,更清晰的pop
循环替换for
和for key, value in reversed(trimmedCode):
:
def insert_gen(orig_string, insertions):
prev_key = 0
for key, value in insertions:
yield orig_string[prev_key:key] # yield text from the previous insert to this one
yield value # yield the "inserted" text
prev_key = key
yield orig_string[prev_key:] # yield trailing text (after last insert)
如果使用生成器函数生成要在较大块中连接的字符串序列,而不是将原始字符串拆分为单个字符,则可能会获得更好的性能。这不是渐近性能变化,但可以提供大的常数因子改善。这是一次尝试:
parsedContent = "".join(insert_gen(parsedContent, self.trimmedCode))
您可以这样使用它:
{{1}}
答案 1 :(得分:1)
您可以通过使用单个字符串累加器在最后加入来获得算法收益。
有些事情:
lastkey = 0
accumulator = []
while self.trimmedCode:
key, value = self.trimmedCode.pop()
accumulator.extend((parsedContent[lastkey:key], value))
lastkey = key
accumulator.append(parsedContent[lastkey:])
parsedContent = ''.join(accumulator)
它可能比你现在正在做的快得多。对于额外的点,使用生成器而不是累加器,正如Blckknght建议的那样。
但如果速度不够快,你应该花时间查看Cython,或者尝试一些现有的数据结构,这可能对这种情况更有效。我会尝试gapbuffer。
答案 2 :(得分:0)
使用+
代替''.join()
来连接字符串可以获得一些速度:
while self.trimmedCode:
key, value = self.trimmedCode.pop()
parsedContent = parsedContent[:key] + value + parsedContent[key:]