スタックとして使うときのlist.extendの速度 - Python

何が原因でこうなるのか、あまりつかみ切れていないが、とりあえず発生することは事実。

通常のlist.extendの例

以下のコード、単に配列Aに配列Bの3の倍数のみを付与している。

実行時間は、extendの方が若干速い。何となく、リスト内包表記が速いことからも頷ける。

def append(A, B):
    for b in B:
        if b % 3 == 0:
            A.append(b)


def extend(A, B):
    A.extend(b for b in B if b % 3 == 0)


A = [0] * 1000
B = list(range(10 ** 6))
t1 = timeit('append(A, B)', number=100, globals=globals())
t2 = timeit('extend(A, B)', number=100, globals=globals())
print(t1)  # => 9.2940 
print(t2)  # => 8.5101

(ただ、今回は3の倍数としたが、条件に当てはまる要素が多いほどどちらも時間がかかるようになる)

スタックとして用いるときの list.extend の例

では、こちらはどうか。

木構造の深さ優先探索を行っているコードである。スタックに隣接頂点を追加しながら探索を深めていっている。

頂点は (コスト, 頂点ID, 親頂点ID) で情報を持ち、親頂点には戻らないようにしている。

結果は、extendを用いた方が、2倍以上遅い。先ほどと似たようなコードなのに、何が違うんだろう。。。

def dfs1(links, s):
    stack = [(0, s, -1)]
    while stack:
        cost, v, p = stack.pop()
        cost += 1
        stack.extend((cost, u, v) for u in links[v] if u != p)


def dfs2(links, s):
    stack = [(0, s, -1)]
    while stack:
        cost, v, p = stack.pop()
        cost += 1
        for u in links[v]:
            if u != p:
                stack.append((cost, u, v))


n = 200000
links = [set() for _ in range(n)]
for i in range(n - 1):
    links[i].add(i + 1)
    links[i + 1].add(i)

t1 = timeit('bfs1(links, 0)', globals=globals(), number=10)
t2 = timeit('bfs2(links, 0)', globals=globals(), number=10)
print(t1)  # => 1.1736078369431198
print(t2)  # => 0.49144828389398754

他に調べたこととして、

  • extend()の引数をgeneratorではなく、[ ] で括ってリストにした方が、幾分か速くなる(が、まだappendの方が速い)
  • 親子情報が既知で、if u != p で除かなくてよい場合でも、やはりextendの方が遅い
  • cost情報も取り払って「stack.extend(u for u in links[v])」にしても、あまり変わらず
  • stack.extend(links[v])」まですると、やっとappendより高速になるにはなった
    • が、ここまですると何の情報も無くなり、探索的には意味をなさなくなる

原因が一目には分からないが、とりあえずこのようなstack的な使い方をする場合は、extendは避けた方がよさそう。

キューとして用いるときの deque.extend の例

幅優先探索を行う際は List の代わりに collections.deque を用いて探索順を先入れ先出しにしたりするが、これも同様、extendの方が遅い結果となる。

  • 20万頂点の直線形の木を、端から端までBFSする時間(10回の平均)
    • extend: 0.1879s
    • append: 0.0843s
  • 20万頂点のスターグラフを、中心からBFSする時間(10回の平均)
    • extend: 0.1688s
    • append: 0.0818s

実験コード

programming_algorithm/python_tips/append_extend.txt · 最終更新: 2020/05/11 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0