Tuples Python'daki listelerden daha mı verimli?


Yanıtlar:


172

disModül, bir işlev için bayt kodu demonte ve küpe ve listeler arasındaki farkı görmek için yararlıdır.

Bu durumda, bir öğeye erişmenin aynı kodu oluşturduğunu ancak bir demet atamanın bir liste atamaktan çok daha hızlı olduğunu görebilirsiniz.

>>> def a():
...     x=[1,2,3,4,5]
...     y=x[2]
...
>>> def b():
...     x=(1,2,3,4,5)
...     y=x[2]
...
>>> import dis
>>> dis.dis(a)
  2           0 LOAD_CONST               1 (1)
              3 LOAD_CONST               2 (2)
              6 LOAD_CONST               3 (3)
              9 LOAD_CONST               4 (4)
             12 LOAD_CONST               5 (5)
             15 BUILD_LIST               5
             18 STORE_FAST               0 (x)

  3          21 LOAD_FAST                0 (x)
             24 LOAD_CONST               2 (2)
             27 BINARY_SUBSCR
             28 STORE_FAST               1 (y)
             31 LOAD_CONST               0 (None)
             34 RETURN_VALUE
>>> dis.dis(b)
  2           0 LOAD_CONST               6 ((1, 2, 3, 4, 5))
              3 STORE_FAST               0 (x)

  3           6 LOAD_FAST                0 (x)
              9 LOAD_CONST               2 (2)
             12 BINARY_SUBSCR
             13 STORE_FAST               1 (y)
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE

66
Hata, aynı bayt kodunun kesinlikle üretilmesi, aynı işlemlerin C (ve dolayısıyla cpu) düzeyinde gerçekleşmesi anlamına gelmez. Bir sınıf oluşturmayı deneyin ListLikebir ile __getitem__korkunç yavaş, sonra sökmeye şey yapar x = ListLike((1, 2, 3, 4, 5)); y = x[2]. Bayt kodu, liste örneğinden çok yukarıdaki grup örneğine benzer, ancak performansın benzer olacağına gerçekten inanıyor musunuz?
mzz

2
Bazı türlerin diğerlerinden daha verimli olduğunu söylüyorsunuz. Bu mantıklıdır, ancak liste ve grup nesillerinin yükü, aynı veri türünün listeleri ve tuples olduklarına dair uyarı ile ilgili veri türüne dik gibi görünmektedir.
Mark Harrison

11
Kod satırı sayısı gibi bayt kodlarının sayısı, yürütme hızı (ve dolayısıyla verimlilik ve performans) ile çok az ilişkilidir.
martineau

18
Op sayımından herhangi bir şey çıkarabileceğiniz öneri yanlış yönlendirilmiş olsa da, bu anahtar farkı gösterir: sabit tupler bayt kodunda olduğu gibi saklanır ve kullanıldığında sadece referans verilir, oysa listelerin çalışma zamanında oluşturulması gerekir.
poolie

6
Bu cevap bize Python'un grup sabitlerini kabul ettiğini göstermektedir. Bunu bilmek güzel! Ancak değişken değerlerden bir demet veya liste oluşturmaya çalışırken ne olur?
Tom

211

Genel olarak, tuple'lerin biraz daha hızlı olmasını bekleyebilirsiniz. Bununla birlikte, özel durumunuzu kesinlikle test etmelisiniz (eğer fark programınızın performansını etkileyebilirse - "erken optimizasyon tüm kötülüklerin köküdür").

Python bunu çok kolaylaştırıyor: timeit senin arkadaşın.

$ python -m timeit "x=(1,2,3,4,5,6,7,8)"
10000000 loops, best of 3: 0.0388 usec per loop

$ python -m timeit "x=[1,2,3,4,5,6,7,8]"
1000000 loops, best of 3: 0.363 usec per loop

ve...

$ python -m timeit -s "x=(1,2,3,4,5,6,7,8)" "y=x[3]"
10000000 loops, best of 3: 0.0938 usec per loop

$ python -m timeit -s "x=[1,2,3,4,5,6,7,8]" "y=x[3]"
10000000 loops, best of 3: 0.0649 usec per loop

Bu durumda, örnekleme neredeyse demet için daha hızlı bir büyüklük sırasıdır, ancak öğe erişimi aslında liste için biraz daha hızlıdır! Bu yüzden birkaç başlık oluşturuyorsanız ve bunlara birçok kez erişiyorsanız, bunun yerine listeleri kullanmak daha hızlı olabilir.

Tabii ki bir öğeyi değiştirmek istiyorsanız, bir öğeyi değiştirmek için tamamen yeni bir grup oluşturmanız gerektiğinden liste kesinlikle daha hızlı olacaktır (tupler değişmez olduğu için).


3
Testleriniz hangi python sürümü ile yapıldı!
Matt Joiner

2
Başka bir ilginç bir test var - python -m timeit "x=tuple(xrange(999999))"vs python -m timeit "x=list(xrange(999999))". Tahmin edilebileceği gibi, bir listeyi gerçekleştirmek bir listeden biraz daha uzun sürer.
Hamish Grubijan

3
Grup erişiminin liste erişiminden daha yavaş olması tuhaf görünüyor. Ancak, Windows 7 bilgisayarımdaki Python 2.7'de fark sadece% 10'dur, bu yüzden önemsizdir.
ToolmakerSteve

51
FWIW, liste erişimi Python 2'deki grup erişiminden daha hızlıdır, ancak yalnızca Python / ceval.c'deki BINARY_SUBSCR'deki listeler için özel bir durum söz konusudur. Python 3'te, bu optimizasyon gitti ve grup erişimi liste erişiminden biraz daha hızlı hale geliyor.
Raymond Hettinger

3
@yoopoo, ilk test milyon kez bir liste oluşturur, ancak ikincisi bir kez bir liste oluşturur ve bu listeye milyon kez erişir. -s "SETUP_CODE"Gerçek Zamanlı kodun önce çalıştırılır.
leewz

203

özet

Tuples neredeyse her kategorideki listelerden daha iyi performans gösterir :

1) Tuples sabit katlanabilir .

2) Tuples kopyalanmak yerine yeniden kullanılabilir.

3) Tuples kompakt ve aşırı tahsis etmeyin.

4) Tuples doğrudan elemanlarına referans verir.

Tuples sabit katlanabilir

Sabitler, Python'un gözetleme deliği optimize edici veya AST-optimize edici ile önceden hesaplanabilir. Öte yandan listeler sıfırdan oluşturuluyor:

    >>> from dis import dis

    >>> dis(compile("(10, 'abc')", '', 'eval'))
      1           0 LOAD_CONST               2 ((10, 'abc'))
                  3 RETURN_VALUE   

    >>> dis(compile("[10, 'abc']", '', 'eval'))
      1           0 LOAD_CONST               0 (10)
                  3 LOAD_CONST               1 ('abc')
                  6 BUILD_LIST               2
                  9 RETURN_VALUE 

Tuples kopyalanmasına gerek yok

Koşmak tuple(some_tuple)hemen geri döner. Tupller değişmez olduğu için kopyalanmaları gerekmez:

>>> a = (10, 20, 30)
>>> b = tuple(a)
>>> a is b
True

Buna karşılık, list(some_list)tüm verilerin yeni bir listeye kopyalanmasını gerektirir:

>>> a = [10, 20, 30]
>>> b = list(a)
>>> a is b
False

Tuples fazla tahsis etmiyor

Bir demetin boyutu sabit olduğundan, append () işlemlerini verimli hale getirmek için aşırı tahsis edilmesi gereken listelerden daha kompakt olarak saklanabilir .

Bu, tuples'e güzel bir alan avantajı sağlar:

>>> import sys
>>> sys.getsizeof(tuple(iter(range(10))))
128
>>> sys.getsizeof(list(iter(range(10))))
200

İşte Objects / listobject.c'nin listelerin ne yaptığını açıklayan yorumu :

/* This over-allocates proportional to the list size, making room
 * for additional growth.  The over-allocation is mild, but is
 * enough to give linear-time amortized behavior over a long
 * sequence of appends() in the presence of a poorly-performing
 * system realloc().
 * The growth pattern is:  0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
 * Note: new_allocated won't overflow because the largest possible value
 *       is PY_SSIZE_T_MAX * (9 / 8) + 6 which always fits in a size_t.
 */

Tuples doğrudan öğelerine atıfta bulunur

Nesnelere yapılan başvurular doğrudan bir tuple nesnesine dahil edilir. Bunun aksine, listeler harici bir işaretçi dizisine fazladan bir dolaylama katmanına sahiptir.

Bu, tuples'e endeksli aramalar ve paketten çıkarma için küçük bir hız avantajı sağlar:

$ python3.6 -m timeit -s 'a = (10, 20, 30)' 'a[1]'
10000000 loops, best of 3: 0.0304 usec per loop
$ python3.6 -m timeit -s 'a = [10, 20, 30]' 'a[1]'
10000000 loops, best of 3: 0.0309 usec per loop

$ python3.6 -m timeit -s 'a = (10, 20, 30)' 'x, y, z = a'
10000000 loops, best of 3: 0.0249 usec per loop
$ python3.6 -m timeit -s 'a = [10, 20, 30]' 'x, y, z = a'
10000000 loops, best of 3: 0.0251 usec per loop

İşte tanımlama grubu nasıl (10, 20)saklanır:

    typedef struct {
        Py_ssize_t ob_refcnt;
        struct _typeobject *ob_type;
        Py_ssize_t ob_size;
        PyObject *ob_item[2];     /* store a pointer to 10 and a pointer to 20 */
    } PyTupleObject;

İşte liste nasıl [10, 20]saklanır:

    PyObject arr[2];              /* store a pointer to 10 and a pointer to 20 */

    typedef struct {
        Py_ssize_t ob_refcnt;
        struct _typeobject *ob_type;
        Py_ssize_t ob_size;
        PyObject **ob_item = arr; /* store a pointer to the two-pointer array */
        Py_ssize_t allocated;
    } PyListObject;

Liste nesnesinin, iki veri işaretçisini tutan harici bir diziye ek bir dolaylı katmanı varken, tuple nesnesinin iki veri işaretçisini doğrudan içerdiğini unutmayın.


19
Sonunda, birisi C yapılarını koyar!
osman

1
Internally, tuples are stored a little more efficiently than lists, and also tuples can be accessed slightly faster. O halde dF.'nin cevabından elde edilen sonuçları nasıl açıklayabilirsiniz?
DRz

5
~ 100 element listesinin ~ 50k listesiyle çalışırken, bu yapıyı tuples'e taşımak, çoklu aramalar için arama sürelerini birden çok büyüklükte azaltır. Bunun, gösterdiğiniz ikinci dolaylama katmanının kaldırılması nedeniyle tuple kullanmaya başladığınızda tupleın daha büyük önbellek lokalitesinden kaynaklandığına inanıyorum.
horta

tuple(some_tuple)Sadece döner some_tupleeğer kendini some_tuplehashable-zaman içeriği verilmiştir yinelemeli değişmez ve hashable olduğunu. Aksi takdirde, tuple(some_tuple)yeni bir demet döndürür. Örneğin, some_tupledeğiştirilebilir öğeler içerdiğinde.
Luciano Ramalho

Tuples her zaman daha hızlı değildir. (1,100) aralığında i için `` t = () değerini düşünün: (1,1000) aralığında i için t + = il = []: a. Ek (i) `` `` daha hızlı
melvil james

32

Değişmez olan tüller daha fazla bellek verimlidir; Verimlilik için, sabit reallocs olmadan ekleme yapılmasına izin vermek üzere belleği fazladan konumlandırır . Bu nedenle, kodunuzdaki sabit bir değer dizisini tekrarlamak istiyorsanız (örn. for direction in 'up', 'right', 'down', 'left':), Tuples tercih edilir, çünkü bu tür tuples derleme zamanında önceden hesaplanır.

Erişim hızları aynı olmalıdır (her ikisi de bellekte bitişik diziler olarak saklanır).

Ancak, değiştirilebilir verilerle uğraşırken alist.append(item)çok tercih edilir atuple+= (item,). Unutmayın, tuples alan adları olmayan kayıtlar olarak düşünülmelidir.


1
Python'da derleme zamanı nedir?
balki

1
@balki: python kaynağının bayt koduna derlendiği zaman (bu bayt kodu .py [co] dosyası olarak kaydedilebilir).
tzot

Mümkünse bir alıntı harika olurdu.
Grijesh Chauhan

9

arrayListenizdeki veya gruptaki tüm öğeler aynı C türündeyse, standart kütüphanedeki modülü de göz önünde bulundurmalısınız . Daha az bellek alır ve daha hızlı olabilir.


15
Daha az bellek alacaktır, ancak erişim süresi muhtemelen daha hızlı değil, biraz daha yavaş olacaktır. Bir öğeye erişmek, paketlenmiş değerin kutudan çıkarılmasını gerektirir ve bu da işlemi yavaşlatır.
Brian

5

İşte yeni bir kıyaslama daha, sadece uğruna ..

In [11]: %timeit list(range(100))
749 ns ± 2.41 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [12]: %timeit tuple(range(100))
781 ns ± 3.34 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [1]: %timeit list(range(1_000))
13.5 µs ± 466 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [2]: %timeit tuple(range(1_000))
12.4 µs ± 182 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [7]: %timeit list(range(10_000))
182 µs ± 810 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [8]: %timeit tuple(range(10_000))
188 µs ± 2.38 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [3]: %timeit list(range(1_00_000))
2.76 ms ± 30.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [4]: %timeit tuple(range(1_00_000))
2.74 ms ± 31.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [10]: %timeit list(range(10_00_000))
28.1 ms ± 266 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [9]: %timeit tuple(range(10_00_000))
28.5 ms ± 447 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Bunları ortalayalım:

In [3]: l = np.array([749 * 10 ** -9, 13.5 * 10 ** -6, 182 * 10 ** -6, 2.76 * 10 ** -3, 28.1 * 10 ** -3])

In [2]: t = np.array([781 * 10 ** -9, 12.4 * 10 ** -6, 188 * 10 ** -6, 2.74 * 10 ** -3, 28.5 * 10 ** -3])

In [11]: np.average(l)
Out[11]: 0.0062112498000000006

In [12]: np.average(t)
Out[12]: 0.0062882362

In [17]: np.average(t) / np.average(l)  * 100
Out[17]: 101.23946713590554

Neredeyse sonuçsuz diyebilirsiniz.

Ama tabii, küpe aldı 101.239%saat veya 1.239%listelere göre işi yapmak için ekstra zaman.


4

Tuples, değişmez oldukları için listelerden biraz daha verimli ve bu nedenle listelerden daha hızlı olmalıdır.


4
Neden kendi başına değişmezliğin verimliliği arttırdığını söylüyorsunuz? Özellikle örnekleme ve erişimin verimliliği?
Blair Conrad

1
Mark'ın benim üstündeki cevabı, Python'un içinde neler olduğuna dair demonte talimatları kapsıyor gibi görünüyor. Örneklemenin daha az talimat aldığını görebilirsiniz, ancak bu durumda geri alma görünüşte aynıdır.
ctcherry

değişmez tuples değişebilir listelerden daha hızlı erişime sahiptir
noobninja

-6

Tuple'in okumada çok verimli olmasının temel nedeni değişmez olmasıdır.

Değişmez nesneleri neden okumak kolaydır?

Nedeni tuples, listelerin aksine bellek önbelleğinde saklanabilir. Program her zaman değiştirilebilir olduğu için listelerin bellek konumundan okur (herhangi bir zamanda değişebilir).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.