สิ่งอันดับมีประสิทธิภาพมากกว่ารายการใน Python หรือไม่


225

มีความแตกต่างของประสิทธิภาพระหว่างสิ่งอันดับและรายการเมื่อพูดถึงการสร้างอินสแตนซ์และการดึงองค์ประกอบ?



2
หากคุณมีความสนใจในหน่วยความจำที่ใช้ระหว่างประเภทที่แตกต่างกันดูพล็อตนี้ที่ฉันทำ: stackoverflow.com/a/30008338/2087463
tmthydvnprt

คำตอบ:


172

disโมดูล disassembles รหัสไบต์สำหรับฟังก์ชั่นและเป็นประโยชน์ที่จะเห็นความแตกต่างระหว่างสิ่งอันดับและรายการ

ในกรณีนี้คุณจะเห็นว่าการเข้าถึงองค์ประกอบสร้างรหัสที่เหมือนกัน แต่การกำหนดสิ่งอันดับจะเร็วกว่าการกำหนดรายการ

>>> 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
ก่อนหน้านี้เพียงว่ารหัสไบต์เดียวกันถูกสร้างขึ้นอย่างสมบูรณ์ไม่ได้หมายความว่าการดำเนินการเดียวกันจะเกิดขึ้นในระดับ C (และดังนั้น cpu) ลองสร้างชั้นเรียนListLikeกับที่ทำบางสิ่งที่น่ากลัวช้าถอดแยกชิ้นส่วนแล้ว__getitem__ x = ListLike((1, 2, 3, 4, 5)); y = x[2]bytecode จะเป็นเหมือนตัวอย่างของ tuple ด้านบนมากกว่าตัวอย่างของรายการ แต่คุณเชื่อหรือไม่ว่านั่นหมายความว่าประสิทธิภาพจะคล้ายกัน
mzz

2
ดูเหมือนว่าคุณกำลังพูดว่าบางประเภทมีประสิทธิภาพมากกว่าประเภทอื่น ๆ นั่นเป็นเหตุผล แต่ค่าใช้จ่ายในรายการและรุ่น tuple นั้นดูเหมือนว่าจะเป็นแบบฉากกับชนิดข้อมูลที่เกี่ยวข้องโดยมีข้อแม้ว่าพวกเขาเป็นรายการและสิ่งอันดับของชนิดข้อมูลเดียวกัน
Mark Harrison

11
จำนวนไบต์รหัสเช่นจำนวนบรรทัดรหัสมีความสัมพันธ์เล็กน้อยกับความเร็วของการดำเนินการ (และประสิทธิภาพและประสิทธิภาพ)
martineau

18
แม้ว่าข้อเสนอแนะที่คุณสามารถสรุปสิ่งใด ๆ จากการนับ ops เข้าใจผิดนี้จะแสดงความแตกต่างที่สำคัญ: tuples คงที่จะถูกเก็บไว้เช่นในรหัสไบต์และอ้างอิงเพียงเมื่อใช้ในขณะที่รายการจะต้องสร้างที่รันไทม์
poolie

6
คำตอบนี้แสดงให้เราเห็นว่า Python ยอมรับค่าคงที่ tuple ดีจังที่รู้! แต่จะเกิดอะไรขึ้นเมื่อพยายามสร้าง tuple หรือรายการจากค่าตัวแปร
ทอม

211

โดยทั่วไปคุณอาจคาดหวังว่าสิ่งอันดับจะเร็วขึ้นเล็กน้อย อย่างไรก็ตามคุณควรทดสอบกรณีเฉพาะของคุณอย่างแน่นอน (หากความแตกต่างอาจส่งผลกระทบต่อประสิทธิภาพการทำงานของโปรแกรมของคุณ - โปรดจำไว้ว่า "การเพิ่มประสิทธิภาพก่อนวัยอันควรเป็นรากฐานของความชั่วร้ายทั้งหมด")

Python ทำให้ง่ายมาก: timeitคือเพื่อนของคุณ

$ 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

และ...

$ 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

ดังนั้นในกรณีนี้การสร้างอินสแตนซ์เกือบจะเป็นลำดับความสำคัญที่เร็วกว่าสำหรับ tuple แต่การเข้าถึงไอเท็มนั้นค่อนข้างเร็วกว่าสำหรับรายการ! ดังนั้นหากคุณกำลังสร้างสิ่งอันดับสองและเข้าถึงพวกเขาหลาย ๆ ครั้งมันอาจจะเร็วกว่าที่จะใช้รายการแทน

แน่นอนถ้าคุณต้องการเปลี่ยนรายการรายการจะเร็วขึ้นแน่นอนเนื่องจากคุณต้องสร้าง tuple ใหม่ทั้งหมดเพื่อเปลี่ยนรายการหนึ่งรายการ (เนื่องจาก tuples ไม่มีการเปลี่ยนแปลง)


3
Python เวอร์ชันใดที่คุณทดสอบด้วย!
Matt Joiner

2
มีอีกการทดสอบที่น่าสนใจคือ - VSpython -m timeit "x=tuple(xrange(999999))" python -m timeit "x=list(xrange(999999))"ตามที่คาดไว้อาจใช้เวลานานขึ้นเล็กน้อยในการทำให้สิ่งอันดับเป็นรูปธรรมกว่ารายการ
Hamish Grubijan

3
ดูเหมือนว่าแปลกประหลาดที่การเข้าถึงทูเปิลนั้นช้ากว่าการเข้าถึงแบบรายการ อย่างไรก็ตามการลองใช้ Python 2.7 บนพีซีที่ใช้ Windows 7 ของฉันความแตกต่างนั้นมีเพียง 10% เท่านั้นดังนั้นไม่สำคัญ
ToolmakerSteve

51
FWIW การเข้าถึงรายการเร็วกว่าการเข้าถึงแบบ tuple ใน Python 2 แต่เนื่องจากมีกรณีพิเศษสำหรับรายการใน BINARY_SUBSCR ใน Python / ceval.c ใน Python 3 การเพิ่มประสิทธิภาพนั้นหายไปและการเข้าถึงแบบทูเปิลจะเร็วกว่าการเข้าถึงแบบรายการเล็กน้อย
Raymond Hettinger

3
@yoopoo การทดสอบครั้งแรกสร้างรายการหนึ่งล้านครั้ง แต่รายการที่สองสร้างรายการหนึ่งครั้งและเข้าถึงได้หนึ่งล้านครั้ง -s "SETUP_CODE"จะดำเนินการก่อนที่จะหมดเวลารหัสที่เกิดขึ้นจริง
leewz

203

สรุป

สิ่งอันดับมีแนวโน้มที่จะทำงานได้ดีกว่ารายการในเกือบทุกหมวดหมู่:

1) Tuples สามารถคงพับ

2) Tuples สามารถนำกลับมาใช้ใหม่ได้แทนที่จะคัดลอก

3) สิ่งอันดับมีขนาดกะทัดรัดและไม่จัดสรรเกิน

4) Tuples อ้างอิงองค์ประกอบของพวกเขาโดยตรง

สิ่งอันดับสามารถพับอย่างต่อเนื่อง

ค่าคงที่ Tuples ของค่าคงที่สามารถคำนวณได้ล่วงหน้าโดยเครื่องมือเพิ่มประสิทธิภาพช่องมองของ Python หรือ AST-optimizer ในทางกลับกันรายการที่ได้รับการสร้างขึ้นจากศูนย์:

    >>> 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 ไม่จำเป็นต้องคัดลอก

วิ่งtuple(some_tuple)กลับทันที เนื่องจากสิ่งอันดับเป็นสิ่งที่ไม่เปลี่ยนรูปแบบจึงไม่จำเป็นต้องคัดลอก:

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

ในทางตรงกันข้ามlist(some_list)ต้องมีการคัดลอกข้อมูลทั้งหมดไปยังรายการใหม่:

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

สิ่งอันดับไม่จัดสรรมากเกินไป

เนื่องจากขนาดของ tuple ได้รับการแก้ไขจึงสามารถจัดเก็บได้แน่นกว่ารายการที่ต้องจัดสรรมากเกินไปเพื่อให้การดำเนินการผนวก ()มีประสิทธิภาพ

สิ่งนี้ให้ประโยชน์พื้นที่ที่ดี tuples:

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

นี่คือความคิดเห็นจากObjects / listobject.cที่อธิบายว่ารายการกำลังทำอะไรอยู่:

/* 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.
 */

สิ่งอันดับอ้างอิงถึงองค์ประกอบของพวกเขาโดยตรง

การอ้างอิงไปยังวัตถุถูกรวมโดยตรงในวัตถุ tuple ในทางตรงกันข้ามรายการมีเลเยอร์พิเศษของการอ้อมไปยังอาร์เรย์ของพอยน์เตอร์ภายนอก

สิ่งนี้ให้ประโยชน์ความเร็วสูงเล็กน้อยสำหรับการค้นหาแบบจัดทำดัชนีและแยกบรรจุ:

$ 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

นี่คือวิธีการ(10, 20)จัดเก็บสิ่งอันดับ:

    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;

นี่คือวิธีการ[10, 20]จัดเก็บรายการ:

    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;

โปรดทราบว่าวัตถุ tuple รวมตัวชี้ข้อมูลสองตัวโดยตรงในขณะที่วัตถุรายการมีชั้นของการเปลี่ยนทิศทางทางอ้อมไปยังอาร์เรย์ภายนอกที่ถือตัวชี้ข้อมูลสองตัว


19
ในที่สุดใครบางคนใส่ C structs!
osman

1
Internally, tuples are stored a little more efficiently than lists, and also tuples can be accessed slightly faster. คุณจะอธิบายผลลัพธ์จากคำตอบของ dF ได้อย่างไร?
DRz

5
เมื่อทำงานกับรายการองค์ประกอบ ~ 100k ~ 100k การย้ายโครงสร้างนี้ไปยังสิ่งอันดับลดเวลาในการค้นหาลงโดยคำสั่งหลายขนาดสำหรับการค้นหาหลายรายการ ฉันเชื่อว่าสิ่งนี้เกิดขึ้นเนื่องจากตำแหน่งแคชที่มากขึ้นของ tuple เมื่อคุณเริ่มใช้ tuple เนื่องจากการลบเลเยอร์ที่สองของการเปลี่ยนเส้นทางที่คุณแสดง
Horta

tuple(some_tuple)จะส่งคืนsome_tupleตัวเองหากsome_tupleสามารถแฮชได้เมื่อเนื้อหานั้นไม่เปลี่ยนรูปและแฮช มิฉะนั้นtuple(some_tuple)ส่งคืน tuple ใหม่ ตัวอย่างเช่นเมื่อsome_tupleมีรายการที่ไม่แน่นอน
Luciano Ramalho

Tuples นั้นไม่เร็วขึ้นเสมอพิจารณา `` `t = () สำหรับ i ในช่วง (1,100): t + = il = [] สำหรับ i ในช่วง (1,1000): a.append (i)` `` อันที่สอง เร็วขึ้น
melvil james

32

Tuples ที่ไม่เปลี่ยนรูปเป็นหน่วยความจำที่มีประสิทธิภาพมากกว่า รายการเพื่อประสิทธิภาพหน่วยความจำ overallocate เพื่ออนุญาตให้ผนวกโดยไม่มีค่าคงreallocที่ ดังนั้นหากคุณต้องการวนซ้ำตามลำดับค่าคงที่ในรหัสของคุณ (เช่นfor direction in 'up', 'right', 'down', 'left':) คุณควรใช้สิ่งอันดับเนื่องจาก tuples ดังกล่าวจะถูกคำนวณล่วงหน้าในเวลารวบรวม

ความเร็วในการเข้าถึงควรเท่ากัน (ทั้งคู่ถูกเก็บไว้เป็นอาร์เรย์ต่อเนื่องกันในหน่วยความจำ)

แต่alist.append(item)เป็นที่ต้องการมากatuple+= (item,)เมื่อคุณจัดการกับข้อมูลที่ไม่แน่นอน จำไว้ว่า tuples มีวัตถุประสงค์เพื่อใช้เป็นเรคคอร์ดโดยไม่มีชื่อฟิลด์


1
เวลารวบรวมในไพ ธ อนคืออะไร?
balki

1
@balki: เวลาที่แหล่งไพ ธ อนคอมไพล์ไปยัง bytecode (ซึ่ง bytecode อาจถูกบันทึกเป็นไฟล์. py [co])
tzot

การอ้างอิงจะดีถ้าเป็นไปได้
Grijesh Chauhan

9

คุณควรพิจารณาarrayโมดูลในไลบรารีมาตรฐานหากรายการทั้งหมดในรายการหรือ tuple ของคุณเป็นประเภท C เดียวกัน จะใช้หน่วยความจำน้อยลงและสามารถเร็วขึ้น


15
มันจะใช้หน่วยความจำน้อยลง แต่เวลาในการเข้าถึงอาจจะช้าลงเล็กน้อยแทนที่จะเร็วกว่า การเข้าถึงองค์ประกอบต้องใช้ค่าที่รวบรวมไว้เพื่อยกเลิกการทำกล่องเป็นจำนวนเต็มจริงซึ่งจะทำให้กระบวนการช้าลง
ไบรอัน

5

นี่คือมาตรฐานเล็ก ๆ น้อย ๆ เพียงเพื่อประโยชน์ของมัน ..

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)

ลองหาค่าเฉลี่ยเหล่านี้กัน:

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

คุณสามารถเรียกมันได้ว่าเกือบจะไม่ลงเอย

แต่แน่นอนว่าสิ่งอันดับใช้101.239%เวลาหรือ1.239%เวลาพิเศษในการทำงานเมื่อเทียบกับรายการ


4

สิ่งอันดับควรมีประสิทธิภาพมากกว่าเล็กน้อยและเนื่องจากรายการนั้นเร็วกว่ารายการเนื่องจากไม่มีการเปลี่ยนแปลง


4
ทำไมคุณถึงบอกว่าการเปลี่ยนไม่ได้ทั้งในตัวของมันเองและเพิ่มประสิทธิภาพ? โดยเฉพาะอย่างยิ่งประสิทธิภาพของการสร้างอินสแตนซ์และการดึงข้อมูล?
Blair Conrad

1
ดูเหมือนว่าคำตอบของ Mark ด้านบนของฉันได้ครอบคลุมคำแนะนำในการถอดประกอบของสิ่งที่เกิดขึ้นภายใน Python คุณจะเห็นว่าการสร้างอินสแตนซ์ใช้คำสั่งน้อยลงอย่างไรก็ตามในกรณีนี้การค้นคืนนั้นเหมือนกัน
ctcherry

สิ่งอันดับที่ไม่เปลี่ยนรูปมีการเข้าถึงที่รวดเร็วกว่ารายการที่ไม่แน่นอน
noobninja

-6

สาเหตุหลักที่ทำให้ Tuple มีประสิทธิภาพในการอ่านมากขึ้นก็เพราะว่ามันไม่เปลี่ยนรูป

ทำไมวัตถุที่ไม่เปลี่ยนรูปแบบจึงอ่านง่าย

เหตุผลคือสิ่งอันดับสามารถเก็บไว้ในแคชหน่วยความจำซึ่งแตกต่างจากรายการ โปรแกรมจะอ่านจากตำแหน่งหน่วยความจำเสมอเนื่องจากสามารถเปลี่ยนแปลงได้ (สามารถเปลี่ยนแปลงได้ตลอดเวลา)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.