TL;DR: In terms of performance, if list.append()
is feasible, use it over list.insert()
.
Additionally, collections.deque
could yield better results in terms of append
ing and popping an elements on either end.
If you are using CPython and okay with making changes to existing toolchain, consider using PyPy for better performance.
I had some issue with performance of adding elements to a large list in Python. So, I conducted performance tests with undermentioned methods for appending elements to the list.
CPython's implementation of list is dynamic arrays. Which comes with an amortized O(1) time complexity for append
, O(n) for insert
, and O(1) for accessing.
Considering the cost, if adding an element to the end of the list is desirable, append
seems to be the obvious choice. However, the cost for append
is O(1) amortized, not plain O(1).
Along with the built-in list
data type, collections.deque
(doubly ended queue) was also included in the performance test. It is a list-like container that supports fast appends and pops on either end.
If you want to further understand the Python list implementation, this post might be useful. @Lesya summarized the post here.
Result
insert
vs append
Unless insert
is a must, use append
for adding elements to the list. For a list size of 0.1M, insert
took more than a second on my laptop.
Focus of performance test was appending elements to the (end of the) list.
Adding an element to the end of the list
Both insert
and append
yielded a near-linear trend in processing time for various sizes of the list. However, regardless of the list size differences, append
showed about 8% faster processing time than insert
to the end of the list.
collections.deque
showed over 20% faster processing time for list sizes over 1M. Smaller list sizes showed dismissible difference.
CPython vs PyPy
For a list size of 0.01M, append
was 6.24X faster (429µs vs 68.7µs) on PyPy. deque
showed even bigger increase of 16.05X faster (419 µs vs 26.1 µs). However, it may be worth mentioning that PyPy uses JIT and different gc mechanism. Depends on how codes are written, increment of speed may not be achievable.
Conclusion
For repetitive appending of elements to the data object, for list data type, it is recommended to use append
. And, if collections.deque
suits your needs, use it.
If you are using CPython, and okay with making changes to existing toolchain, consider using PyPy for better performance.
You might also want to consider following two packages, skiplist that support performant append
-like functionality, and blist, an implementation of orderedstructs
, which provides better performance for modifying large lists.
Output
Env: CPython 3.10.9
list length = 0.01M
lst_insert: 481 µs ± 2.32 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
lst_insert_batch: 522 µs ± 1.75 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
lst_append: 429 µs ± 1.31 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
lst_append_batch: 473 µs ± 801 ns per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
deque_append: 419 µs ± 2.46 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
list length = 0.1M
lst_insert: 4.61 ms ± 21.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
lst_insert_batch: 5.03 ms ± 27.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
lst_append: 4.21 ms ± 29.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
lst_append_batch: 4.68 ms ± 65.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
deque_append: 4.14 ms ± 21.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
list length = 1M
lst_insert: 59.6 ms ± 415 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
lst_insert_batch: 67.4 ms ± 199 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
lst_append: 55.2 ms ± 273 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
lst_append_batch: 60.2 ms ± 305 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
deque_append: 44.5 ms ± 201 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
list length = 10M
lst_insert: 621 ms ± 3.24 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
lst_insert_batch: 813 ms ± 19.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
lst_append: 573 ms ± 3.18 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
lst_append_batch: 721 ms ± 3.34 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
deque_append: 445 ms ± 2.39 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
list length = 100M
lst_insert: 6.04 s ± 89.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
lst_insert_batch: 8.04 s ± 43.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
lst_append: 5.53 s ± 32.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
lst_append_batch: 7.34 s ± 41.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
deque_append: 4.48 s ± 7.25 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Env: PyPy 3.9-v7.3.11
list length = 0.01M
lst_insert: 78.8 µs ± 802 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
lst_insert_batch: 167 µs ± 85.6 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
lst_append: 68.7 µs ± 1.48 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
lst_append_batch: 164 µs ± 88.4 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
deque_append: 26.1 µs ± 284 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
Code used
import timeit
def lst_insert(len, v=0):
lst = []
for i in range(len):
lst.insert(i, v)
return lst
def lst_insert_batch(len, v=0, factor=10):
lst = []
tmp_lst = []
for _ in range(factor):
for i in range(int(len/factor)):
tmp_lst.insert(i, v)
lst += tmp_lst
tmp_lst = []
return lst
def lst_append(len, v=0):
lst = []
for i in range(len):
lst.append(v)
return lst
def lst_append_batch(len, v=0, factor=10):
lst = []
tmp_lst = []
for _ in range(factor):
for i in range(int(len/factor)):
tmp_lst.append(v)
lst += tmp_lst
tmp_lst = []
return lst
def deque_append(len, v=0):
from collections import deque
dq = deque(maxlen=len)
for i in range(len):
dq.append(v)
return dq
for len in [1e4, 1e5, 1e6, 1e7, 1e8]:
len = int(len)
%timeit lst_insert(len)
%timeit lst_insert_batch(len)
%timeit lst_append(len)
%timeit lst_append_batch(len)
%timeit deque_append(len)