2009年9月30日水曜日

String concatenation in Python

文字列連結の効率の話
addのほうが早かったよなと思って、色々試してみた。

  1. def str_mul():  
  2.     return name * count  
  3.   
  4. def str_join():  
  5.     lst = []  
  6.     append = lst.append  
  7.     for i in xrange(count):  
  8.         append(name)  
  9.     return ''.join(lst)  
  10.   
  11. def str_map():  
  12.     lst = map(lambda x:name, xrange(count))  
  13.     return ''.join(lst)  
  14.   
  15. def list_comp():  
  16.     return ''.join(name for i in xrange(count))  
  17.   
  18. def str_add():  
  19.     s = ''  
  20.     for i in xrange(count):  
  21.         s += name  
  22.     return s  
  23.   
  24. import cStringIO  
  25. def cstring_io():  
  26.     io = cStringIO.StringIO()  
  27.     write = io.write  
  28.     for i in xrange(count):  
  29.         write(name)  
  30.     return io.getvalue()  
  31.   
  32. import StringIO  
  33. def string_io():  
  34.     io = StringIO.StringIO()  
  35.     write = io.write  
  36.     for i in xrange(count):  
  37.         write(name)  
  38.     return io.getvalue()  
  39.   
  40. from array import array  
  41. def str_array():  
  42.     a = array('c')  
  43.     add = a.fromstring  
  44.     for i in xrange(count):  
  45.         add(name)  
  46.     return a.tostring()  
  47.   
  48. from mmap import mmap  
  49. def str_mmap():  
  50.     m = mmap(-1, count * len(name))  
  51.     write = m.write  
  52.     for i in xrange(count):  
  53.         write(name)  
  54.     m.seek(0)  
  55.     return m.read(count * len(name))  
  56.   
  57. def main():  
  58.     global count, name  
  59.     func_list = (str_mul, str_join, str_add, string_io, cstring_io,  
  60.                  str_map, list_comp, str_array, str_mmap)  
  61.   
  62.     # test  
  63.     count = 2  
  64.     name = 'hello'  
  65.     assert all((name * count).__eq__(f()) for f in func_list)  
  66.   
  67.     import timeit  
  68.     for c in (101000):  
  69.         for b in (11000):  
  70.             for f in func_list:  
  71.                 count = c  
  72.                 name = 'hello' * b  
  73.                 print '%10s x %4s, "hello"*%4s: %9.4fms'%(  
  74.                     f.func_name, c, b, timeit.timeit(f, number=100)/100*1000)  
  75.   
  76. if __name__ == '__main__':  
  77.     main()  

   str_mul x   10, "hello"*   1:    0.0006ms
  str_join x   10, "hello"*   1:    0.0042ms
   str_add x   10, "hello"*   1:    0.0033ms
 string_io x   10, "hello"*   1:    0.0312ms
cstring_io x   10, "hello"*   1:    0.0072ms
   str_map x   10, "hello"*   1:    0.0059ms
 list_comp x   10, "hello"*   1:    0.0077ms
 str_array x   10, "hello"*   1:    0.0081ms
  str_mmap x   10, "hello"*   1:    0.0180ms
   str_mul x   10, "hello"*1000:    0.0108ms
  str_join x   10, "hello"*1000:    0.0131ms
   str_add x   10, "hello"*1000:    0.0126ms
 string_io x   10, "hello"*1000:    0.0411ms
cstring_io x   10, "hello"*1000:    0.0364ms
   str_map x   10, "hello"*1000:    0.0146ms
 list_comp x   10, "hello"*1000:    0.0167ms
 str_array x   10, "hello"*1000:    0.0377ms
  str_mmap x   10, "hello"*1000:    0.1105ms
   str_mul x 1000, "hello"*   1:    0.0017ms
  str_join x 1000, "hello"*   1:    0.2352ms
   str_add x 1000, "hello"*   1:    0.2235ms
 string_io x 1000, "hello"*   1:    2.4272ms
cstring_io x 1000, "hello"*   1:    0.5111ms
   str_map x 1000, "hello"*   1:    0.3591ms
 list_comp x 1000, "hello"*   1:    0.2296ms
 str_array x 1000, "hello"*   1:    0.5338ms
  str_mmap x 1000, "hello"*   1:    0.4778ms
   str_mul x 1000, "hello"*1000:    8.5971ms
  str_join x 1000, "hello"*1000:    8.3908ms
   str_add x 1000, "hello"*1000:   29.4590ms
 string_io x 1000, "hello"*1000:   10.6351ms
cstring_io x 1000, "hello"*1000:   28.7235ms
   str_map x 1000, "hello"*1000:    8.8407ms
 list_comp x 1000, "hello"*1000:    8.2300ms
 str_array x 1000, "hello"*1000:   38.9228ms
  str_mmap x 1000, "hello"*1000:   18.6053ms

addは50kB程度までは、文字列長や回数にかかわらず早い。
'str' * nをjoinが逆転するのは見てびっくり。
StringIOは1MBを超えたあたりで突然加速、理由不明。
cStringIOを余裕で追い抜き、100MBあたりではjoinに並ぶ。
ただし、1MBを超えるようなものはPythonで扱うべきではないかも。
for, map, list内包は勝ったり負けたり。
disってみたところ、早そうなのはfor-join。
psycoも使ってみたけど、速度はあまりかわらず。

結論:
addでもjoinでも扱いやすいほうを使おう。
速度はPythonを使う上では気にしない。

0 件のコメント: