效率:字符串切片与自定义功能

时间:2014-06-16 14:55:56

标签: python string performance

我有一个长度为5*10^6的大字符串。

我必须通过将其分成16个字符的块来进行一些处理。我使用自定义函数来分割字符串,假设它的性能优于拼接方法。

功能如下:

def spliceSplitter(s):
     sum = 0
     while len(s) > 0:
             block = s[:16]
             # Assuming the process to be done with data block is calculating its length.
             sum += len(block)
             s = s[16:]
     return sum

自定义功能:

def normalSplitter(s):
     sum = 0
     l = len(s)
     data =""
     for i in xrange(l):
             if i%16 == 0:
                     # Assuming the process to be done with data block is calculating its length.
                     sum += len(data)
                     data = ""
             data += s[i]
     return sum+len(data)

我在他们两个上都使用了cProfiler,结果如下(以秒为单位的时间):

String Length     |  Splice Splitter   |  Normal Splitter
---------------------------------------------------------
5000000           |  289.0             |  1.274 
500000            |  0.592             |  0.134
50000             |  0.25              |  0.28
5000              |  0.001             |  0.003 

我按如下方式生成字符串:

s = ''.join([str(random.randint(1,9)) for x in xrange(5000000)])

我的问题:

  • 是否有一种pythonic方法可以获得与Custom Normal Splitter相同或更高的效率?也许可以事先拆分整个字符串,将其存储到列表中,然后迭代地操作它。
  • 为什么Splice Splitter的性能更适合较小的字符串? (只是好奇这个)

注意:我必须执行的process(data)没有返回值。

修改

使用Yield和改进的Splice Splitter,性能得到以下结果:

String Length     |  Splice Splitter   |  Normal Splitter  |  Yield/Generator
-------------------------------------------------------------------------------
5000000           |  0.148             |  1.274            |  0.223
500000            |  0.016             |  0.134            |  0.29
50000             |  0.003             |  0.28             |  0.005
5000              |  ~0.000            |  0.003            |  ~0.000

代码:

def pythonicSplitter(s):
     gen = (s[i:i+16] for i in xrange(0,len(s),16))
     sum = 0
     for data in gen:
             sum += len(data)
     return sum
def spliceSplitter(s):
    sum = 0
    for x in xrange(0, len(s), 16):
         block = s[x:x+16]
         # Assuming the process to be done with data block is calculating its length.
         sum += len(block)
    return sum

提高效果的原因:

  • Splice Splitter使用splice重复在每次迭代中创建新字符串。正如帕特里克科林的回答所指出的s = s[16:]。这导致时间复杂度为~O(N^2)
  • 一旦用s替换了字符串s[x:x+16]的重复创建,代码复杂性就会降低到O(N*16),从而大幅提升其性能。 Yield / Generator函数执行相同的操作(pythonicSplitter()),但由于生成器(迭代器)的大量调用,完成操作所需的时间略大于Splice Splitter中使用的那个。
  • Normal Splitter也通过创建长度为16的块来做同样的事情。但是由于在Python中字符串是不可变的,创建这些块的时间复杂度远远大于内置优化的块。切片功能。

1 个答案:

答案 0 :(得分:4)

我猜这行:s = s[16:]导致s被覆盖每次循环迭代,复制整个字符串。 block = s[:16]也在复制一个字符串,所以你基本上在每次循环迭代时将字符串写入内存两次。 data = ""中的normalSplitter()也可能确保您一次不会在内存中保留超过16个字符串的字符,并且您永远不会对整个字符串执行任何复制操作。

这推动了很多数据,并且,我希望,导致你开始在最大的字符串上获得缓存未命中(尽管显然较小的字符串能够舒适地适应缓存)。尝试使用解决方案like this one

def newSplitter(s, n=16):
    for i in xrange(0, len(s), n):
        yield l[i:i+n]