小さなリストよりも小さな文字列を反復するのが遅いのはなぜですか?


132

私はtimeitで遊んでいて、小さな文字列に対して単純なリストの理解を行うと、小さな単一の文字列のリストに対して同じ操作を行うよりも時間がかかることに気づきました。説明はありますか?ほぼ1.35倍の時間です。

>>> from timeit import timeit
>>> timeit("[x for x in 'abc']")
2.0691067844831528
>>> timeit("[x for x in ['a', 'b', 'c']]")
1.5286479570345861

これを引き起こしている下位レベルで何が起こっていますか?

回答:


193

TL; DR

  • Python 2の場合、オーバーヘッドの多くを取り除くと、実際の速度の差は70​​%(またはそれ以上)に近づきます。

  • オブジェクトの作成に問題はありません。1文字の文字列がキャッシュされるため、どちらの方法でも新しいオブジェクトは作成されません。

  • 違いは明白ではありませんが、タイプと整形式に関して、文字列のインデックス付けに関する多数のチェックから作成された可能性があります。また、何を返却するかを確認する必要があるためと考えられます。

  • リストのインデックス作成は非常に高速です。



>>> python3 -m timeit '[x for x in "abc"]'
1000000 loops, best of 3: 0.388 usec per loop

>>> python3 -m timeit '[x for x in ["a", "b", "c"]]'
1000000 loops, best of 3: 0.436 usec per loop

これはあなたが見つけたものと一致しません...

その場合、Python 2を使用している必要があります。

>>> python2 -m timeit '[x for x in "abc"]'
1000000 loops, best of 3: 0.309 usec per loop

>>> python2 -m timeit '[x for x in ["a", "b", "c"]]'
1000000 loops, best of 3: 0.212 usec per loop

バージョン間の違いを説明しましょう。コンパイルされたコードを調べます。

Python 3の場合:

import dis

def list_iterate():
    [item for item in ["a", "b", "c"]]

dis.dis(list_iterate)
#>>>   4           0 LOAD_CONST               1 (<code object <listcomp> at 0x7f4d06b118a0, file "", line 4>)
#>>>               3 LOAD_CONST               2 ('list_iterate.<locals>.<listcomp>')
#>>>               6 MAKE_FUNCTION            0
#>>>               9 LOAD_CONST               3 ('a')
#>>>              12 LOAD_CONST               4 ('b')
#>>>              15 LOAD_CONST               5 ('c')
#>>>              18 BUILD_LIST               3
#>>>              21 GET_ITER
#>>>              22 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
#>>>              25 POP_TOP
#>>>              26 LOAD_CONST               0 (None)
#>>>              29 RETURN_VALUE

def string_iterate():
    [item for item in "abc"]

dis.dis(string_iterate)
#>>>  21           0 LOAD_CONST               1 (<code object <listcomp> at 0x7f4d06b17150, file "", line 21>)
#>>>               3 LOAD_CONST               2 ('string_iterate.<locals>.<listcomp>')
#>>>               6 MAKE_FUNCTION            0
#>>>               9 LOAD_CONST               3 ('abc')
#>>>              12 GET_ITER
#>>>              13 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
#>>>              16 POP_TOP
#>>>              17 LOAD_CONST               0 (None)
#>>>              20 RETURN_VALUE

毎回リストが作成されるため、リストのバリアントは遅くなる可能性が高いことがわかります。

これは

 9 LOAD_CONST   3 ('a')
12 LOAD_CONST   4 ('b')
15 LOAD_CONST   5 ('c')
18 BUILD_LIST   3

部。文字列バリアントには

 9 LOAD_CONST   3 ('abc')

これが違いを生むように見えることを確認できます:

def string_iterate():
    [item for item in ("a", "b", "c")]

dis.dis(string_iterate)
#>>>  35           0 LOAD_CONST               1 (<code object <listcomp> at 0x7f4d068be660, file "", line 35>)
#>>>               3 LOAD_CONST               2 ('string_iterate.<locals>.<listcomp>')
#>>>               6 MAKE_FUNCTION            0
#>>>               9 LOAD_CONST               6 (('a', 'b', 'c'))
#>>>              12 GET_ITER
#>>>              13 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
#>>>              16 POP_TOP
#>>>              17 LOAD_CONST               0 (None)
#>>>              20 RETURN_VALUE

これはちょうど生成します

 9 LOAD_CONST               6 (('a', 'b', 'c'))

タプルは不変なので。テスト:

>>> python3 -m timeit '[x for x in ("a", "b", "c")]'
1000000 loops, best of 3: 0.369 usec per loop

すばらしい、スピードアップ。

Python 2の場合:

def list_iterate():
    [item for item in ["a", "b", "c"]]

dis.dis(list_iterate)
#>>>   2           0 BUILD_LIST               0
#>>>               3 LOAD_CONST               1 ('a')
#>>>               6 LOAD_CONST               2 ('b')
#>>>               9 LOAD_CONST               3 ('c')
#>>>              12 BUILD_LIST               3
#>>>              15 GET_ITER            
#>>>         >>   16 FOR_ITER                12 (to 31)
#>>>              19 STORE_FAST               0 (item)
#>>>              22 LOAD_FAST                0 (item)
#>>>              25 LIST_APPEND              2
#>>>              28 JUMP_ABSOLUTE           16
#>>>         >>   31 POP_TOP             
#>>>              32 LOAD_CONST               0 (None)
#>>>              35 RETURN_VALUE        

def string_iterate():
    [item for item in "abc"]

dis.dis(string_iterate)
#>>>   2           0 BUILD_LIST               0
#>>>               3 LOAD_CONST               1 ('abc')
#>>>               6 GET_ITER            
#>>>         >>    7 FOR_ITER                12 (to 22)
#>>>              10 STORE_FAST               0 (item)
#>>>              13 LOAD_FAST                0 (item)
#>>>              16 LIST_APPEND              2
#>>>              19 JUMP_ABSOLUTE            7
#>>>         >>   22 POP_TOP             
#>>>              23 LOAD_CONST               0 (None)
#>>>              26 RETURN_VALUE        

奇妙なことに、リストの構築は同じですが、この方が高速です。Python 2は奇妙に速く動作しています。

内包表記を削除して、リタイムします。_ =それはアウトに最適化取得を防止することです。

>>> python3 -m timeit '_ = ["a", "b", "c"]'
10000000 loops, best of 3: 0.0707 usec per loop

>>> python3 -m timeit '_ = "abc"'
100000000 loops, best of 3: 0.0171 usec per loop

初期化はバージョン間の違いを説明するのに十分重要ではないことがわかります(これらの数は小さいです)。したがって、Python 3の理解は遅いと結論付けることができます。これは、Python 3がより安全なスコープを持つように理解を変更したため、理にかなっています。

さて、今度はベンチマークを改善します(反復ではないオーバーヘッドを削除しているだけです)。これにより、事前割り当てによってイテラブルの構築が削除されます。

>>> python3 -m timeit -s 'iterable = "abc"'           '[x for x in iterable]'
1000000 loops, best of 3: 0.387 usec per loop

>>> python3 -m timeit -s 'iterable = ["a", "b", "c"]' '[x for x in iterable]'
1000000 loops, best of 3: 0.368 usec per loop
>>> python2 -m timeit -s 'iterable = "abc"'           '[x for x in iterable]'
1000000 loops, best of 3: 0.309 usec per loop

>>> python2 -m timeit -s 'iterable = ["a", "b", "c"]' '[x for x in iterable]'
10000000 loops, best of 3: 0.164 usec per loop

呼び出しiterがオーバーヘッドであるかどうかを確認できます。

>>> python3 -m timeit -s 'iterable = "abc"'           'iter(iterable)'
10000000 loops, best of 3: 0.099 usec per loop

>>> python3 -m timeit -s 'iterable = ["a", "b", "c"]' 'iter(iterable)'
10000000 loops, best of 3: 0.1 usec per loop
>>> python2 -m timeit -s 'iterable = "abc"'           'iter(iterable)'
10000000 loops, best of 3: 0.0913 usec per loop

>>> python2 -m timeit -s 'iterable = ["a", "b", "c"]' 'iter(iterable)'
10000000 loops, best of 3: 0.0854 usec per loop

いいえ、違います。特にPython 3の場合、違いは小さすぎます。

それで、不要なオーバーヘッドをさらに削除しましょう...全体を遅くします!目的は、反復を長くして、時間がオーバーヘッドを隠すことです。

>>> python3 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' '[x for x in iterable]'
100 loops, best of 3: 3.12 msec per loop

>>> python3 -m timeit -s 'import random; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' '[x for x in iterable]'
100 loops, best of 3: 2.77 msec per loop
>>> python2 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' '[x for x in iterable]'
100 loops, best of 3: 2.32 msec per loop

>>> python2 -m timeit -s 'import random; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' '[x for x in iterable]'
100 loops, best of 3: 2.09 msec per loop

これは実際にはそれほど変わっていませんが、少しは役に立ちました。

だから、理解を取り除く。質問の一部ではないオーバーヘッドです:

>>> python3 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'for x in iterable: pass'
1000 loops, best of 3: 1.71 msec per loop

>>> python3 -m timeit -s 'import random; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' 'for x in iterable: pass'
1000 loops, best of 3: 1.36 msec per loop
>>> python2 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'for x in iterable: pass'
1000 loops, best of 3: 1.27 msec per loop

>>> python2 -m timeit -s 'import random; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' 'for x in iterable: pass'
1000 loops, best of 3: 935 usec per loop

それはもっとそれに似ています!を使用dequeして反復することで、わずかに速くなります。基本的には同じですが、高速です:

>>> python3 -m timeit -s 'import random; from collections import deque; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 777 usec per loop

>>> python3 -m timeit -s 'import random; from collections import deque; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 405 usec per loop
>>> python2 -m timeit -s 'import random; from collections import deque; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 805 usec per loop

>>> python2 -m timeit -s 'import random; from collections import deque; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 438 usec per loop

印象的なのは、Unicodeがバイト文字列と競合できることです。私たちは、明示的に試みることによってこれを確認することができますbytesし、unicode両方に:

  • bytes

    >>> python3 -m timeit -s 'import random; from collections import deque; iterable = b"".join(chr(random.randint(0, 127)).encode("ascii") for _ in range(100000))' 'deque(iterable, maxlen=0)'                                                                    :(
    1000 loops, best of 3: 571 usec per loop
    
    >>> python3 -m timeit -s 'import random; from collections import deque; iterable =         [chr(random.randint(0, 127)).encode("ascii") for _ in range(100000)]' 'deque(iterable, maxlen=0)'
    1000 loops, best of 3: 394 usec per loop
    >>> python2 -m timeit -s 'import random; from collections import deque; iterable = b"".join(chr(random.randint(0, 127))                 for _ in range(100000))' 'deque(iterable, maxlen=0)'
    1000 loops, best of 3: 757 usec per loop
    
    >>> python2 -m timeit -s 'import random; from collections import deque; iterable =         [chr(random.randint(0, 127))                 for _ in range(100000)]' 'deque(iterable, maxlen=0)'
    1000 loops, best of 3: 438 usec per loop

    ここでは、Python 3が実際にはPython 2 より高速であることがわかります。

  • unicode

    >>> python3 -m timeit -s 'import random; from collections import deque; iterable = u"".join(   chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
    1000 loops, best of 3: 800 usec per loop
    
    >>> python3 -m timeit -s 'import random; from collections import deque; iterable =         [   chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
    1000 loops, best of 3: 394 usec per loop
    >>> python2 -m timeit -s 'import random; from collections import deque; iterable = u"".join(unichr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
    1000 loops, best of 3: 1.07 msec per loop
    
    >>> python2 -m timeit -s 'import random; from collections import deque; iterable =         [unichr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
    1000 loops, best of 3: 469 usec per loop

    繰り返しになりますが、これは予想されますが、Python 3の方が高速です。str Python 3では多くの注意が払われました)。

実際、これは unicode - bytes差は非常に小さく、印象的です。

それでは、この1つのケースを分析してみましょう。私にとっては速くて便利です。

>>> python3 -m timeit -s 'import random; from collections import deque; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 777 usec per loop

>>> python3 -m timeit -s 'import random; from collections import deque; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 405 usec per loop

私たちは実際にティム・ピーターの10倍の賛成回答を除外できます!

>>> foo = iterable[123]
>>> iterable[36] is foo
True

これらは新しいオブジェクトではありません!

しかし、これは言及する価値があります:インデックス作成コスト。違いはインデックス付けにある可能性が高いので、反復を削除してインデックスを付けます。

>>> python3 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'iterable[123]'
10000000 loops, best of 3: 0.0397 usec per loop

>>> python3 -m timeit -s 'import random; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' 'iterable[123]'
10000000 loops, best of 3: 0.0374 usec per loop

違いは小さいように見えますが、コストの少なくとも半分はオーバーヘッドです。

>>> python3 -m timeit -s 'import random; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' 'iterable; 123'
100000000 loops, best of 3: 0.0173 usec per loop

速度の違いはそれを非難することを決定するのに十分です。おもう。

それでは、なぜリストのインデックス作成がこれほど高速になるのでしょうか。

さて、それについては後で説明しますが、おそらく、インターンされた文字列(または、個別のメカニズムの場合はキャッシュされた文字)をチェックすることです。これは最適な速度よりも遅くなります。しかし、私はソースをチェックしに行きます(私はCで快適ではありませんが...):)。


だからここにソースがあります:

static PyObject *
unicode_getitem(PyObject *self, Py_ssize_t index)
{
    void *data;
    enum PyUnicode_Kind kind;
    Py_UCS4 ch;
    PyObject *res;

    if (!PyUnicode_Check(self) || PyUnicode_READY(self) == -1) {
        PyErr_BadArgument();
        return NULL;
    }
    if (index < 0 || index >= PyUnicode_GET_LENGTH(self)) {
        PyErr_SetString(PyExc_IndexError, "string index out of range");
        return NULL;
    }
    kind = PyUnicode_KIND(self);
    data = PyUnicode_DATA(self);
    ch = PyUnicode_READ(kind, data, index);
    if (ch < 256)
        return get_latin1_char(ch);

    res = PyUnicode_New(1, ch);
    if (res == NULL)
        return NULL;
    kind = PyUnicode_KIND(res);
    data = PyUnicode_DATA(res);
    PyUnicode_WRITE(kind, data, 0, ch);
    assert(_PyUnicode_CheckConsistency(res, 1));
    return res;
}

上から歩いて、いくつかのチェックがあります。これらは退屈です。次に、いくつかの割り当てがありますが、これも退屈です。最初の興味深い行は

ch = PyUnicode_READ(kind, data, index);

しかし、インデックスを付けることで隣接するC配列から読み取るので、それが高速であることを願っています。結果chは256未満になるため、キャッシュされた文字をで返しますget_latin1_char(ch)

実行します(最初のチェックを削除します)

kind = PyUnicode_KIND(self);
data = PyUnicode_DATA(self);
ch = PyUnicode_READ(kind, data, index);
return get_latin1_char(ch);

どこ

#define PyUnicode_KIND(op) \
    (assert(PyUnicode_Check(op)), \
     assert(PyUnicode_IS_READY(op)),            \
     ((PyASCIIObject *)(op))->state.kind)

(これはアサートがデバッグで無視されるため(退屈なので、高速であることを確認でき((PyASCIIObject *)(op))->state.kind)ます)、間接参照であり、Cレベルのキャストです(私はそう思います)。

#define PyUnicode_DATA(op) \
    (assert(PyUnicode_Check(op)), \
     PyUnicode_IS_COMPACT(op) ? _PyUnicode_COMPACT_DATA(op) :   \
     _PyUnicode_NONCOMPACT_DATA(op))

(これも同様の理由で退屈です。マクロ(Something_CAPITALIZED)がすべて高速であると想定しています)、

#define PyUnicode_READ(kind, data, index) \
    ((Py_UCS4) \
    ((kind) == PyUnicode_1BYTE_KIND ? \
        ((const Py_UCS1 *)(data))[(index)] : \
        ((kind) == PyUnicode_2BYTE_KIND ? \
            ((const Py_UCS2 *)(data))[(index)] : \
            ((const Py_UCS4 *)(data))[(index)] \
        ) \
    ))

(これにはインデックスが含まれますが、実際にはまったく遅くありません)

static PyObject*
get_latin1_char(unsigned char ch)
{
    PyObject *unicode = unicode_latin1[ch];
    if (!unicode) {
        unicode = PyUnicode_New(1, ch);
        if (!unicode)
            return NULL;
        PyUnicode_1BYTE_DATA(unicode)[0] = ch;
        assert(_PyUnicode_CheckConsistency(unicode, 1));
        unicode_latin1[ch] = unicode;
    }
    Py_INCREF(unicode);
    return unicode;
}

それは私の疑いを確認します:

  • これはキャッシュされます:

    PyObject *unicode = unicode_latin1[ch];
  • これは速いはずです。if (!unicode)それはこの場合、文字通り同等ですので、実行されません

    PyObject *unicode = unicode_latin1[ch];
    Py_INCREF(unicode);
    return unicode;

正直なところ、assertsをテストした後(それらを無効にすることで[ Cレベルのアサートで機能すると思います ...])、もっとも遅いのは次の部分だけです。

PyUnicode_IS_COMPACT(op)
_PyUnicode_COMPACT_DATA(op)
_PyUnicode_NONCOMPACT_DATA(op)

どれが:

#define PyUnicode_IS_COMPACT(op) \
    (((PyASCIIObject*)(op))->state.compact)

(以前と同じように高速)、

#define _PyUnicode_COMPACT_DATA(op)                     \
    (PyUnicode_IS_ASCII(op) ?                   \
     ((void*)((PyASCIIObject*)(op) + 1)) :              \
     ((void*)((PyCompactUnicodeObject*)(op) + 1)))

(マクロIS_ASCIIが高速の場合は高速)、および

#define _PyUnicode_NONCOMPACT_DATA(op)                  \
    (assert(((PyUnicodeObject*)(op))->data.any),        \
     ((((PyUnicodeObject *)(op))->data.any)))

(これもアサートと間接とキャストの高速なので)。

だから私たちは(うさぎの穴)にダウンしています:

PyUnicode_IS_ASCII

それは

#define PyUnicode_IS_ASCII(op)                   \
    (assert(PyUnicode_Check(op)),                \
     assert(PyUnicode_IS_READY(op)),             \
     ((PyASCIIObject*)op)->state.ascii)

うーん...それはあまりにも速いようです...


さて、わかりましたが、それをと比較してみましょうPyList_GetItem。(うん、ティム・ピーターズにもっと仕事をしてくれてありがとう:P)

PyObject *
PyList_GetItem(PyObject *op, Py_ssize_t i)
{
    if (!PyList_Check(op)) {
        PyErr_BadInternalCall();
        return NULL;
    }
    if (i < 0 || i >= Py_SIZE(op)) {
        if (indexerr == NULL) {
            indexerr = PyUnicode_FromString(
                "list index out of range");
            if (indexerr == NULL)
                return NULL;
        }
        PyErr_SetObject(PyExc_IndexError, indexerr);
        return NULL;
    }
    return ((PyListObject *)op) -> ob_item[i];
}

エラー以外のケースでは、これが実行されるだけであることがわかります。

PyList_Check(op)
Py_SIZE(op)
((PyListObject *)op) -> ob_item[i]

どこPyList_Check

#define PyList_Check(op) \
     PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_LIST_SUBCLASS)

TABS TABS !!!)(issue215875分で修正され、マージされました。ええと...ええ。くそー。彼らはスキートを恥にさらした。

#define Py_SIZE(ob)             (((PyVarObject*)(ob))->ob_size)
#define PyType_FastSubclass(t,f)  PyType_HasFeature(t,f)
#ifdef Py_LIMITED_API
#define PyType_HasFeature(t,f)  ((PyType_GetFlags(t) & (f)) != 0)
#else
#define PyType_HasFeature(t,f)  (((t)->tp_flags & (f)) != 0)
#endif

したがって、これがPy_LIMITED_APIオンでない限り、これは通常は非常に簡単です(2つの間接参照といくつかのブール値チェック)。その場合、... ???

次に、インデックス付けとキャスト(((PyListObject *)op) -> ob_item[i])があれば完了です。

そのため、リストのチェックは間違いなく少なく、速度のわずかな違いは確かに関連性があることを意味します。


一般に、(->)Unicodeには型チェックと間接参照があるだけだと思います。ポイントが足りないようですが、どうですか?


17
あなたはコードを自己説明的に提示しています。あなたは結論としてスニペットを提示しています。残念ながら、私は本当にそれを追跡することはできません。何が悪いのかを見つけるためのアプローチがしっかりしていると言っているわけではありませんが、より簡単に理解できればいいと思います。
PascalVKooten 2014年

2
改善を試みましたが、それをより明確にする方法がわかりません。私はCを記述していないため、これはコードの高レベルの分析であり、全体的な概念のみが重要です。
Veedrac、2014年

@Nitを追加しました。それが不足していると感じたら教えてください。残念ながら、私は実際には答えがわからないことも強調しています(* gasp *)。
Veedrac、2014年

3
私はあなたの答えを受け入れる前に別の日にこれをあげます(もっと具体的なポップアップが見たいです)。非常に興味深く、よく研究された答えをありがとう。
Sunjay Varma 2014

4
移動しているターゲットを狙っていることに注意してください;-)この実装は、Python 2とPython 3の間だけでなく、リリースごとにも異なります。たとえば、現在の開発トランクでは、get_latin1_char()トリックはには存在せずunicode_getitem()、下位レベルに存在しますunicode_char。そのため、現在、別のレベルの関数呼び出しがあります-使用しないかどうか(使用するコンパイラーおよび最適化フラグによって異なります)。この詳細レベルでは、信頼できる回答はありません;-)
Tim Peters

31

ほとんどのコンテナーオブジェクト(リスト、タプル、辞書など)を反復処理すると、イテレーターがコンテナー内のオブジェクト配信します。

ただし、文字列を反復処理する場合、配信される文字ごとに新しいオブジェクトを作成する必要があります。文字列はリストと同じ意味での「コンテナ」ではありません。文字列内の個々の文字は、反復によってこれらのオブジェクトが作成されるまで、個別のオブジェクトとして存在しません。


3
これは本当だとは思いません。で確認できisます。それ正しいように聞こえますが、私は実際にそうであるとは思いません。
Veedrac、2014年

@Veedracの答えを見てください。
クリスチャン

3
stringobject.cは、__getitem__文字列の場合、格納されている1文字の文字列のテーブルから結果を取得するだけなので、それらの割り当てコストは1回しか発生しないことを示しています。
user2357112はモニカの2014

10
@ user2357112、そうです、Python 2のプレーンな文字列は重要なポイントです。Python 3では、すべての文字列は「公式に」Unicodeであり、より多くの詳細が含まれます(Veedracの回答を参照)。たとえば、Python 3ではafter s = chr(256)s is chr(256)返されますFalse-データ値をトリガーするカバーの下に特別なケースの山が存在するため、タイプを知るだけでは十分ではありません。
Tim Peters

1

文字列のイテレータを作成すると、オーバーヘッドが発生する可能性があります。一方、インスタンス化時には、配列にはすでにイテレータが含まれています。

編集:

>>> timeit("[x for x in ['a','b','c']]")
0.3818681240081787
>>> timeit("[x for x in 'abc']")
0.3732869625091553

これは2.7を使用して実行されましたが、私のMacブックプロi7では。これは、システム構成の違いの結果である可能性があります。


ストレートイテレータを使用するだけでも、文字列は大幅に遅くなります。timeit( "[x for x in it]"、 "it = iter( 'abc')")= 0.34543599384033535; timeit( "[x for x in it]"、 "it = iter(list( 'abc'))")= 0.2791691380446508
Sunjay Varma
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.