6

我在 C++ 中有一个缓冲区对象,它继承自std::vector<char>. 我想将此缓冲区转换为 Python 字符串,以便我可以通过 Twisted 的 protocol.transport.write 通过网络将其发送出去。

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

def scpychar(buf, n):
    s = ''
    for i in xrange(0, n):
        s += buf[i]
    return s

(2) 制作一个 char 数组(因为我知道缓冲区有多大),填充它并将其转换为字符串

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]调用时都必须创建一个新的字符串对象,并复制旧字符串的内容。所以我期待(2)比(1)快。但是如果我使用 timeit 进行测试,我发现 (1) 实际上是 (2) 的两倍。

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

std::vector<char>从 a 转换为 Python 字符串的更有效方法的奖励积分。

4

2 回答 2

3

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

尝试

''.join(buf)

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

于 2013-08-13T22:13:34.660 回答
0

导入 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 

  >>   62 LOAD_FAST                2 (a)
         65 LOAD_ATTR                2 (tostring)
         68 CALL_FUNCTION            0

对比

         38 INPLACE_ADD         
         39 STORE_FAST 

加载很慢。CALL_FUNCTION 很慢。

我在一个月前看到有问题,这''.join(b)是将字符数组加入一个字符串的最快方法。

于 2013-08-13T21:58:27.933 回答