繁体   English   中英

python成员str性能太慢了

[英]python member str performance too slow

在python 2.7.3中添加str类成员时遇到了一个奇怪的性能问题。 我知道访问局部变量更快,但是,在下面的问题中,两个循环之间的速度差异超过100倍。 访问a.accum_的那个开始快但速度慢,就好像str iadd是str(长度为n)的O(n ^ 2)。

有谁知道原因?

# Fast ( < 1sec):
accum = str()
for ii in range(1000000):
    if (ii % 10000) == 0:
        print 'fast cnt', ii
    accum += 'zzzzz\n'

# Much slower ( > 5 mins):
class Foo:
    pass
a = Foo()
a.accum_ = str()
for ii in range(1000000):
    if (ii % 10000) == 0:
        print 'slow cnt', ii
    a.accum_ += 'zzzzz\n'

对于第一个示例,很明显是单引用优化的情况(实际上有两个引用:一个来自对象本身,一个是LOAD_FAST ; unicode_concatenate将尝试将其减少为1,然后将控制传递给PyUnicode_Append )由CPython使用这个unicode_modifiable函数:

static int
unicode_modifiable(PyObject *unicode)
{
    assert(_PyUnicode_CHECK(unicode));
    if (Py_REFCNT(unicode) != 1)
        return 0;
    if (_PyUnicode_HASH(unicode) != -1)
        return 0;
    if (PyUnicode_CHECK_INTERNED(unicode))
        return 0;
    if (!PyUnicode_CheckExact(unicode))
        return 0;
#ifdef Py_DEBUG
    /* singleton refcount is greater than 1 */
    assert(!unicode_is_singleton(unicode));
#endif
    return 1;
}

但在第二种情况下,由于实例数据存储在Python dict而不是简单变量中,因此事情变得微不足道。

a.accum_ += 'foo'

实际上需要预取a.accum_的值并将其存储到堆栈中。 所以,现在的字符串有ATLEAST三个参考:一个是从实例字典,一个从DUP_TOP ,一个来自PyObject_GetAttr通过使用LOAD_ATTR 因此,Python无法优化这种情况,因为就地修改其中一个也会影响其他引用。

>>> class A:
    pass
...
>>> a = A()
>>> def func():
    a.str = 'spam'
    print a.str
    return '_from_func'
...
>>> a.str = 'foo'
>>> a.str += func()
spam

你会期望这里的输出是'spam_from_func' ,但它会有所不同,因为a.str的原始值是在func()之前由Python存储的。

>>> a.str
'foo_from_func'

字节代码:

>>> import dis
>>> def func_class():
        a = Foo()
        a.accum = ''
        a.accum += 'zzzzz\n'
...
>>> dis.dis(func_class)
  2           0 LOAD_GLOBAL              0 (Foo)
              3 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
              6 STORE_FAST               0 (a)

  3           9 LOAD_CONST               1 ('')
             12 LOAD_FAST                0 (a)
             15 STORE_ATTR               1 (accum)

  4          18 LOAD_FAST                0 (a)
             21 DUP_TOP
             22 LOAD_ATTR                1 (accum)
             25 LOAD_CONST               2 ('zzzzz\n')
             28 INPLACE_ADD
             29 ROT_TWO
             30 STORE_ATTR               1 (accum)
             33 LOAD_CONST               0 (None)
             36 RETURN_VALUE

请注意,这种优化是在2004左右完成的(CPython 2.4),以防止用户STORE_FAST a += ba = a + b ,因此它主要用于简单变量,仅当下STORE_FAST指令是STORE_FAST (局部变量) STORE_FAST ), STORE_DEREF (闭包)和STORE_NAME 它不是一般的解决方案, 在Python中执行此操作的最佳方法是使用str.join创建列表并加入其项目

CPython实现细节 :如果st都是字符串,一些Python实现(如CPython)通常可以对s = s + ts += t形式s = s + t赋值执行就地优化。 如果适用,此优化使得二次运行时间的可能性大大降低。 此优化依赖于版本和实现。 对于性能敏感的代码,最好使用str.join()方法,该方法确保跨版本和实现的一致的线性串联性能。

Python字符串是不可变的,因此不能__iadd__方法。 您在第一个示例中看到的是CPython解释器的微优化。 在第一个例子中,解释器注意到它有一个局部变量,其引用计数为1.因此,解释器可以在适当的位置修改字符串。 即使这违反了str的合同,但在执行程序期间的任何时候都不会明显违反此合同。

在后一个例子中,这个微优化没有实现,这就是它如此缓慢的原因。 看起来可以应用优化,所以我不确定它为什么没有被应用。

通常,如果构建字符串,则整理列表中的子字符串,然后使用str.join创建最终产品。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM