【问题标题】:Python buffer copy speed - why is array slower than string?Python缓冲区复制速度 - 为什么数组比字符串慢?
【发布时间】:2013-08-13 21:41:56
【问题描述】:

我有一个继承自std::vector<char> 的 C++ 缓冲区对象。我想将此缓冲区转换为 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> 转换为 Python 字符串的更有效方式的奖励积分。

【问题讨论】:

    标签: python arrays string performance stdvector


    【解决方案1】:

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

    试试

    ''.join(buf)
    

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

    【讨论】:

    • 我不认为 join 会接受向量,但确实如此。酷!
    • 有意思,你有关于 cpython 就地修改的参考吗?
    • @RyanHaining:见注 6 here
    【解决方案2】:

    导入 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) 是将字符数组加入一个字符串的最快方法。

    【讨论】:

    • 我没有投反对票,但可以看到它发生的原因。指令数量与性能的关系是先验的。
    • 这并不是真正的指令数量,而是内存消耗和 CPU 时间。
    • @SeanMcLaughlin 但我看到 scpyarr 中 heavy 指令的数量更多。
    • 这个答案没有说明算法(1)的预期二次运行时间。
    • @user2357112 两次繁重的指令 - 两次以上的时间。怎么了?
    猜你喜欢
    • 2011-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-07
    • 2019-09-08
    • 1970-01-01
    • 1970-01-01
    • 2018-03-09
    相关资源
    最近更新 更多