Python缓冲区复制速度-为什么数组比字符串慢? < P> C++中有一个缓冲对象,它继承自 STD::vector < /代码>。我想将这个缓冲区转换成Python字符串,这样我就可以通过Twisted的protocol.transport.write通过网络发送它

Python缓冲区复制速度-为什么数组比字符串慢? < P> C++中有一个缓冲对象,它继承自 STD::vector < /代码>。我想将这个缓冲区转换成Python字符串,这样我就可以通过Twisted的protocol.transport.write通过网络发送它,python,arrays,string,performance,stdvector,Python,Arrays,String,Performance,Stdvector,我想到的两种方法是(1)制作一个字符串并逐字符填充: def scpychar(buf, n): s = '' for i in xrange(0, n): s += buf[i] return s (2)制作一个字符数组(因为我知道缓冲区有多大),填充它并将其转换为字符串 def scpyarr(buf, n): a = array.array('c','0'*n) for i in xrange(0, n): a[i]

我想到的两种方法是(1)制作一个字符串并逐字符填充:

def scpychar(buf, n):
    s = ''
    for i in xrange(0, n):
        s += buf[i]
    return s
(2)制作一个字符数组(因为我知道缓冲区有多大),填充它并将其转换为字符串

def scpyarr(buf, n):
    a = array.array('c','0'*n)
    for i in xrange(0, n):
        a[i] = buf[i]
    return a.tostring()
我认为(1)必须在每次调用
s+=buf[I]
时创建一个新的string对象,并复制旧字符串的内容。所以我希望(2)比(1)快。但是如果我用timeit测试这个,我发现(1)的速度实际上是(2)的两倍

我想知道是否有人能解释为什么(1)更快


std::vector
转换为Python字符串的更有效方法的额外积分。

导入dis并查看dis.dis(scpyarr)和dis.dis(scpychar)。 scpychar的解释器操作数较少

>>> import dis
>>> def scpyarr(buf, n):
...     a = array.array('c','0'*n)
...     for i in xrange(0, n):
...         a[i] = buf[i]
...     return a.tostring()
... 
>>> dis.dis(scpyarr)
  2           0 LOAD_GLOBAL              0 (array)
              3 LOAD_ATTR                0 (array)
              6 LOAD_CONST               1 ('c')
              9 LOAD_CONST               2 ('0')
             12 LOAD_FAST                1 (n)
             15 BINARY_MULTIPLY     
             16 CALL_FUNCTION            2
             19 STORE_FAST               2 (a)

  3          22 SETUP_LOOP              37 (to 62)
             25 LOAD_GLOBAL              1 (xrange)
             28 LOAD_CONST               3 (0)
             31 LOAD_FAST                1 (n)
             34 CALL_FUNCTION            2
             37 GET_ITER            
        >>   38 FOR_ITER                20 (to 61)
             41 STORE_FAST               3 (i)

  4          44 LOAD_FAST                0 (buf)
             47 LOAD_FAST                3 (i)
             50 BINARY_SUBSCR       
             51 LOAD_FAST                2 (a)
             54 LOAD_FAST                3 (i)
             57 STORE_SUBSCR        
             58 JUMP_ABSOLUTE           38
        >>   61 POP_BLOCK           

  5     >>   62 LOAD_FAST                2 (a)
             65 LOAD_ATTR                2 (tostring)
             68 CALL_FUNCTION            0
             71 RETURN_VALUE        
>>> def scpychar(buf, n):
...     s = ''
...     for i in xrange(0, n):
...         s += buf[i]
...     return s
... 
>>> dis.dis(scpychar)
  2           0 LOAD_CONST               1 ('')
              3 STORE_FAST               2 (s)

  3           6 SETUP_LOOP              37 (to 46)
              9 LOAD_GLOBAL              0 (xrange)
             12 LOAD_CONST               2 (0)
             15 LOAD_FAST                1 (n)
             18 CALL_FUNCTION            2
             21 GET_ITER            
        >>   22 FOR_ITER                20 (to 45)
             25 STORE_FAST               3 (i)

  4          28 LOAD_FAST                2 (s)
             31 LOAD_FAST                0 (buf)
             34 LOAD_FAST                3 (i)
             37 BINARY_SUBSCR       
             38 INPLACE_ADD         
             39 STORE_FAST               2 (s)
             42 JUMP_ABSOLUTE           22
        >>   45 POP_BLOCK           

  5     >>   46 LOAD_FAST                2 (s)
             49 RETURN_VALUE        
>>> 
比较一下:

         51 LOAD_FAST                2 (a)
         54 LOAD_FAST                3 (i)
         57 STORE_SUBSCR 
加上

vs

加载速度慢。调用函数的速度很慢


一个月前,我在问题中看到,
”。join(b)
是将字符数组连接到一个字符串中的最快方法。

CPython有时可以优化字符串
+=
,如果它可以确定没有人保留对旧字符串的引用,那么它就可以就位。算法(1)可能触发了优化,因此它不会受到二次运行时间的影响。但是,这种行为并不能保证,其他Python实现可能不支持它

试一试


它应该在任何Python实现上提供线性时间性能,不像(1),而且比(2)更快。

我没有否决,但可以理解为什么会这样。指令的数量与性能的先验相关性很弱。这实际上不是指令的数量,而是内存消耗和CPU时间。@SeanMcLaughlin但我看到scpyarr中的重指令数量更多。这个答案没有说明算法(1)的预期二次运行时@user2357112两倍的繁重指令-两倍的时间。怎么了?我不认为join会接受向量,但它接受。酷!有趣的是,你有关于cpython就地修改的参考资料吗?@RyanHaining:见注6。
  >>   62 LOAD_FAST                2 (a)
         65 LOAD_ATTR                2 (tostring)
         68 CALL_FUNCTION            0
         38 INPLACE_ADD         
         39 STORE_FAST 
''.join(buf)