2010-12-14 4 views

ответ

439

Если у вас есть только одна ссылка на строку, и вы конкатенируете другую строку до конца, CPython теперь имеет особые случаи и пытается расширить строку на месте.

Конечным результатом является то, что операция амортизируется O (n).

например.

s = "" 
for i in range(n): 
    s+=str(i) 

использовался как O (n^2), но теперь это O (n).

От источника (bytesobject.c):

void 
PyBytes_ConcatAndDel(register PyObject **pv, register PyObject *w) 
{ 
    PyBytes_Concat(pv, w); 
    Py_XDECREF(w); 
} 


/* The following function breaks the notion that strings are immutable: 
    it changes the size of a string. We get away with this only if there 
    is only one module referencing the object. You can also think of it 
    as creating a new string object and destroying the old one, only 
    more efficiently. In any case, don't use this if the string may 
    already be known to some other part of the code... 
    Note that if there's not enough memory to resize the string, the original 
    string object at *pv is deallocated, *pv is set to NULL, an "out of 
    memory" exception is set, and -1 is returned. Else (on success) 0 is 
    returned, and the value in *pv may or may not be the same as on input. 
    As always, an extra byte is allocated for a trailing \0 byte (newsize 
    does *not* include that), and a trailing \0 byte is stored. 
*/ 

int 
_PyBytes_Resize(PyObject **pv, Py_ssize_t newsize) 
{ 
    register PyObject *v; 
    register PyBytesObject *sv; 
    v = *pv; 
    if (!PyBytes_Check(v) || Py_REFCNT(v) != 1 || newsize < 0) { 
     *pv = 0; 
     Py_DECREF(v); 
     PyErr_BadInternalCall(); 
     return -1; 
    } 
    /* XXX UNREF/NEWREF interface should be more symmetrical */ 
    _Py_DEC_REFTOTAL; 
    _Py_ForgetReference(v); 
    *pv = (PyObject *) 
     PyObject_REALLOC((char *)v, PyBytesObject_SIZE + newsize); 
    if (*pv == NULL) { 
     PyObject_Del(v); 
     PyErr_NoMemory(); 
     return -1; 
    } 
    _Py_NewReference(*pv); 
    sv = (PyBytesObject *) *pv; 
    Py_SIZE(sv) = newsize; 
    sv->ob_sval[newsize] = '\0'; 
    sv->ob_shash = -1;   /* invalidate cached hash value */ 
    return 0; 
} 

Это достаточно легко проверить опытным путем.

 
$ python -m timeit -s"s=''" "for i in xrange(10):s+='a'" 
1000000 loops, best of 3: 1.85 usec per loop 
$ python -m timeit -s"s=''" "for i in xrange(100):s+='a'" 
10000 loops, best of 3: 16.8 usec per loop 
$ python -m timeit -s"s=''" "for i in xrange(1000):s+='a'" 
10000 loops, best of 3: 158 usec per loop 
$ python -m timeit -s"s=''" "for i in xrange(10000):s+='a'" 
1000 loops, best of 3: 1.71 msec per loop 
$ python -m timeit -s"s=''" "for i in xrange(100000):s+='a'" 
10 loops, best of 3: 14.6 msec per loop 
$ python -m timeit -s"s=''" "for i in xrange(1000000):s+='a'" 
10 loops, best of 3: 173 msec per loop 

Это важно однако отметить, что эта оптимизация не является частью спецификации Python. Насколько я знаю, это только в реализации cPython. Те же эмпирические тесты на pypy или jython, например, могут показать более высокую производительность O (n ** 2).

 
$ pypy -m timeit -s"s=''" "for i in xrange(10):s+='a'" 
10000 loops, best of 3: 90.8 usec per loop 
$ pypy -m timeit -s"s=''" "for i in xrange(100):s+='a'" 
1000 loops, best of 3: 896 usec per loop 
$ pypy -m timeit -s"s=''" "for i in xrange(1000):s+='a'" 
100 loops, best of 3: 9.03 msec per loop 
$ pypy -m timeit -s"s=''" "for i in xrange(10000):s+='a'" 
10 loops, best of 3: 89.5 msec per loop 

До сих пор так хорошо, но потом,

 
$ pypy -m timeit -s"s=''" "for i in xrange(100000):s+='a'" 
10 loops, best of 3: 12.8 sec per loop 

Уч даже хуже, чем квадратичная. Таким образом, pypy делает что-то, что хорошо работает с короткими строками, но плохо работает для больших строк.

+12

Интересно. Под «сейчас», вы имеете в виду Python 3.x? –

+7

@Steve, No. Это, по крайней мере, в 2.6, возможно, даже 2.5 –

+7

Вы указали функцию 'PyString_ConcatAndDel', но включили комментарий для' _PyString_Resize'. Кроме того, комментарий действительно не устанавливает ваше требование относительно Big-O –

8

Если вам нужно сделать много операций добавления для создания большой строки, вы можете использовать StringIO или cStringIO. Интерфейс подобен файлу. т.е.: write, чтобы добавить текст к нему.

Если вы просто добавляете две строки, просто используйте +.

25
str1 = "Hello" 
str2 = "World" 
newstr = " ".join((str1, str2)) 

Это соединяет str1 и str2 с пространством в качестве разделителей. Вы также можете сделать "".join(str1, str2, ...). str.join() выполняет итерацию, поэтому вам нужно будет поместить строки в список или кортеж.

Это примерно так же эффективно, как и для встроенного метода.

222

Не следует преждевременно оптимизировать. Если у вас нет никаких оснований полагать, что есть скорость узкого места, вызванные строковыми сцепления, то просто придерживаться + и +=:

s = 'foo' 
s += 'bar' 
s += 'baz' 

Это сказало, если вы стремитесь к чему-то вроде StringBuilder в Java канонического Python идиома является добавлять элементы в список, а затем использовать str.join, чтобы объединить их все в конце:

l = [] 
l.append('foo') 
l.append('bar') 
l.append('baz') 

s = ''.join(l) 
+0

Я не знаю, какие последствия скорости построения ваших строк в списках, а затем. join() их, но я считаю, что это самый чистый способ. У меня также были большие успехи с использованием нотации% s в строке для SQL-шаблона, который я написал. – richo

+18

@ Richo Использование .join более эффективно. Причина в том, что строки Python неизменяемы, поэтому многократно используя s + = more будет выделять множество последовательно больших строк. .join будет генерировать конечную строку за один проход от ее составных частей. – Ben

+5

@Ben, было значительное улучшение в этой области - см. Мой ответ –

34

не делать.

То есть, в большинстве случаев вам лучше генерировать целую строку за один раз, а не добавлять существующую строку.

Например, не делают: obj1.name + ":" + str(obj1.count)

Вместо: используйте "%s:%d" % (obj1.name, obj1.count)

Это будет легче читать и более эффективным.

+30

Прошу прощения, нет ничего проще читать, чем (строка + строка), как первый пример, второй пример может быть более эффективным, но не более читаемый – ExceptionSlayer

+19

@ExceptionSlayer, строка + строка довольно проста. Но ''

" + message_text + "
"', я нахожу менее читаемым и подверженным ошибкам, затем '"
{message_text}
".format (classname = class_name, message_text = message_text, id = generateUniqueId())' –

+0

Это совсем не помогает, m пытается сделать грубый эквивалент, скажем, «string. = verifydata()« PHP/perl »или аналогичный. – Shadur

9

Это действительно зависит от вашего приложения. Если вы перебираете сотни слов и хотите добавить их в список, лучше - .join(). Но если вы собрали длинное предложение, вам лучше использовать +=.

3

В принципе, никакой разницы. Единственная стойкая тенденция в том, что Python, кажется, становится медленнее с каждой версией ... :(


Список

%%timeit 
x = [] 
for i in range(100000000): # xrange on Python 2.7 
    x.append('a') 
x = ''.join(x) 

Python 2,7

1 петля, лучше 3: 7.34 s за петлю

Python 3,4

1 цикл, лучше всего 3: 7.99 с на петле

Python 3,5

1 цикл, лучше всего из 3: 8,48 s за петлю

Python 3,6

1 цикл, лучше всего из 3: 9,93 с на петле


Строка

%%timeit 
x = '' 
for i in range(100000000): # xrange on Python 2.7 
    x += 'a' 

Python 2.7:

1 цикл, лучше всего из 3: 7,41 с за петли

Python 3.4

1 цикл, лучшие из 3: 9,08 с на петле

Python 3,5

1 цикл, лучшие из 3: 8,82 с на петле

Python 3,6

1 цикл, лучше всего 3: 9,24 s в петле

+1

Я думаю, это зависит. Я получаю '1.19 s' и' 992 ms' соответственно на Python2.7 –

+0

@JohnLaRooy Да, вы правы. Я отредактировал ответ. – ostrokach

4
a='foo' 
b='baaz' 

a.__add__(b) 

out: 'foobaaz' 
+0

Код хороший, но это поможет получить сопроводительное объяснение. Зачем использовать этот метод, а не другие ответы на этой странице? – cgmb

+4

Использование 'a .__ add __ (b)' идентично написанию 'a + b'. Когда вы объединяете строки, используя оператор '+', Python будет вызывать метод '__add__' в строке слева, передавая правую боковую строку в качестве параметра. – Addie

Смежные вопросы