wake-up-neo.com

Wie langsam ist die String-Verkettung von Python vs. str.join?

Aufgrund der Kommentare in meiner Antwort zu diesem Thread wollte ich wissen, was der Geschwindigkeitsunterschied zwischen dem +=-Operator und ''.join() ist

Was ist also der Geschwindigkeitsvergleich zwischen den beiden?

47
Wayne Werner

Von: Effiziente String-Verkettung

Methode 1: 

def method1():
  out_str = ''
  for num in xrange(loop_count):
    out_str += 'num'
  return out_str

Methode 4: 

def method4():
  str_list = []
  for num in xrange(loop_count):
    str_list.append('num')
  return ''.join(str_list)

Jetzt stelle ich fest, dass sie nicht streng repräsentativ sind, und die vierte Methode hängt an einer Liste an, bevor ich sie durchläuft und jedem Element beitrete. Dies ist jedoch ein gutes Zeichen.

Der String-Join ist deutlich schneller als die Verkettung.

Warum? Zeichenfolgen sind unveränderlich und können nicht geändert werden. Um eine zu ändern, muss eine neue Darstellung erstellt werden (eine Verkettung der beiden).

alt text

71

Mein ursprünglicher Code war falsch, es scheint, dass die Verkettung von + normalerweise schneller ist (insbesondere bei neueren Versionen von Python auf neuerer Hardware).

Die Zeiten sind wie folgt:

Iterations: 1,000,000       

Python 3.3 unter Windows 7, Core i7

String of len:   1 took:     0.5710     0.2880 seconds
String of len:   4 took:     0.9480     0.5830 seconds
String of len:   6 took:     1.2770     0.8130 seconds
String of len:  12 took:     2.0610     1.5930 seconds
String of len:  80 took:    10.5140    37.8590 seconds
String of len: 222 took:    27.3400   134.7440 seconds
String of len: 443 took:    52.9640   170.6440 seconds

Python 2.7 unter Windows 7, Core i7

String of len:   1 took:     0.7190     0.4960 seconds
String of len:   4 took:     1.0660     0.6920 seconds
String of len:   6 took:     1.3300     0.8560 seconds
String of len:  12 took:     1.9980     1.5330 seconds
String of len:  80 took:     9.0520    25.7190 seconds
String of len: 222 took:    23.1620    71.3620 seconds
String of len: 443 took:    44.3620   117.1510 seconds

Unter Linux Mint, Python 2.7, etwas langsamerer Prozessor

String of len:   1 took:     1.8840     1.2990 seconds
String of len:   4 took:     2.8394     1.9663 seconds
String of len:   6 took:     3.5177     2.4162 seconds
String of len:  12 took:     5.5456     4.1695 seconds
String of len:  80 took:    27.8813    19.2180 seconds
String of len: 222 took:    69.5679    55.7790 seconds
String of len: 443 took:   135.6101   153.8212 seconds

Und hier ist der Code:

from __future__ import print_function
import time

def strcat(string):
    newstr = ''
    for char in string:
        newstr += char
    return newstr

def listcat(string):
    chars = []
    for char in string:
        chars.append(char)
    return ''.join(chars)

def test(fn, times, *args):
    start = time.time()
    for x in range(times):
        fn(*args)
    return "{:>10.4f}".format(time.time() - start)

def testall():
    strings = ['a', 'long', 'longer', 'a bit longer', 
               '''adjkrsn widn fskejwoskemwkoskdfisdfasdfjiz  oijewf sdkjjka dsf sdk siasjk dfwijs''',
               '''this is a really long string that's so long
               it had to be triple quoted  and contains lots of
               superflous characters for kicks and gigles
               @!#(*_#)(*$(*!#@&)(*E\xc4\x32\xff\x92\x23\xDF\xDFk^%#$!)%#^(*#''',
              '''I needed another long string but this one won't have any new lines or crazy characters in it, I'm just going to type normal characters that I would usually write blah blah blah blah this is some more text hey cool what's crazy is that it looks that the str += is really close to the O(n^2) worst case performance, but it looks more like the other method increases in a perhaps linear scale? I don't know but I think this is enough text I hope.''']

    for string in strings:
        print("String of len:", len(string), "took:", test(listcat, 1000000, string), test(strcat, 1000000, string), "seconds")

testall()
7
Wayne Werner

Die vorhandenen Antworten sind sehr gut geschrieben und recherchiert, aber hier ist eine weitere Antwort für die Ära von Python 3.6, da wir jetzt literal string interpolation (AKA, f- strings) haben:

>>> import timeit
>>> timeit.timeit('f\'{"a"}{"b"}{"c"}\'', number=1000000)
0.14618930302094668
>>> timeit.timeit('"".join(["a", "b", "c"])', number=1000000)
0.23334730707574636
>>> timeit.timeit('a = "a"; a += "b"; a += "c"', number=1000000)
0.14985873899422586

Mit CPython 3.6.5 durchgeführter Test an einem Retina MacBook Pro 2012 mit einem Intel Core i7 bei 2,3 GHz.

Dies ist keinesfalls ein formaler Benchmark, aber es sieht so aus, als wäre die Verwendung von f- strings ungefähr so ​​performant wie die Verwendung von +=-Verkettung. Jegliche verbesserten Metriken oder Vorschläge sind natürlich willkommen.

4
Jules

Ich habe die letzte Antwort umgeschrieben. Könnten Sie bitte Ihre Meinung dazu mitteilen, wie ich sie getestet habe?

import time

start1 = time.clock()
for x in range (10000000):
    dog1 = ' and '.join(['spam', 'eggs', 'spam', 'spam', 'eggs', 'spam','spam', 'eggs', 'spam', 'spam', 'eggs', 'spam'])

end1 = time.clock()
print("Time to run Joiner = ", end1 - start1, "seconds")


start2 = time.clock()
for x in range (10000000):
    dog2 = 'spam'+' and '+'eggs'+' and '+'spam'+' and '+'spam'+' and '+'eggs'+' and '+'spam'+' and '+'spam'+' and '+'eggs'+' and '+'spam'+' and '+'spam'+' and '+'eggs'+' and '+'spam'

end2 = time.clock()
print("Time to run + = ", end2 - start2, "seconds")

HINWEIS: Dieses Beispiel ist in Python 3.5 geschrieben. Range () verhält sich wie das vorherige xrange ().

Die Ausgabe, die ich bekam:

Time to run Joiner =  27.086106206103153 seconds
Time to run + =  69.79100515996426 seconds

Persönlich bevorzuge ich '' .join ([]) dem 'Plusser-Weg', weil es sauberer und lesbarer ist. 

0
Gerard Kool

Das ist es, was dumme Programme zum Testen entworfen haben :)

Verwenden Sie Plus

import time

if __== '__main__':
    start = time.clock()
    for x in range (1, 10000000):
        dog = "a" + "b"

    end = time.clock()
    print "Time to run Plusser = ", end - start, "seconds"

Ausgabe von:

Time to run Plusser =  1.16350010965 seconds

Jetzt mitmachen ....

import time
if __== '__main__':
    start = time.clock()
    for x in range (1, 10000000):
        dog = "a".join("b")

    end = time.clock()
    print "Time to run Joiner = ", end - start, "seconds"

Ausgabe von:

Time to run Joiner =  21.3877386651 seconds

Also auf Python 2.6 unter Windows würde ich sagen, dass + etwa 18 Mal schneller ist als Join :)

0
bwawok