Hiểu hơn về interger và list trong Python và sử dụng Numpy để tiết kiệm memory hơn

numpy

Giả sử chúng ta muốn tạo ra một list chứa các số integer như sau:

arr = list(range(1000000))

Bạn đoán Python sẽ dùng bao nhiêu memory để lưu list arr? Hmm... Ở đại học ta được học integer là 4 bytes (tùy máy tính, tùy compiler), vậy tính ra với một triệu phần tử thì sẽ ~4MiB?

Trong thực tế, Python tốn ~36MiB để lưu arr và các số integer này. Sử dụng memory_profiler để profile memory usage của code.

Line #    Mem usage    Increment   Line Contents
================================================
     1   37.586 MiB   37.586 MiB   @profile
     2                             def test_list():
     3   74.184 MiB   36.598 MiB       arr = list(range(1000000))

Thực chất là thế nào?

Trong Python, mọi thứ đều là object vì thế mà sẽ tốn thêm 1 lượng bytes cho overhead memory.

In [1]: number = 1; number.__dir__()
Out[1]: 
['__repr__',
 '__hash__',
 '__getattribute__',
 '__lt__',
 '__le__',
 '__eq__',
 '__ne__',
 '__gt__',
...]

Như bạn thấy, trong đoạn code trên là các attribute (đã bị lược bớt cho đỡ dài) của object number. Hãy thử xem size của number là bao nhiêu bytes.

In [2]: number.__sizeof__()
Out[2]: 28

Oh, 28 bytes. Quay về với ví dụ ban đầu, sẽ là ~28MiB cho 1 triệu số integer. Vậy còn ~8MiB nữa đi đâu? ~8MiB đó thuộc về object arr. List trong python là 1 mảng các pointer (con trỏ) trỏ tới bất kỳ object nào. Với ví dụ của chúng ta sẽ là 1 triệu pointers. Trong hệ điều hành 64-bit, con trỏ chiếm 8 bytes.

In [3]: arr.__sizeof__()
Out[3]: 8000040

Sử dụng Numpy Array

Khác với kiểu list, numpy array không lưu reference tới bất kỳ Python object nào mà thay vào đó chỉ lưu các số mà thôi.

Profile đoạn code sau:

import numpy as np
arr = np.zeros((1000000,), dtype=np.uint32)
for i in range(1000000):
    arr[i] = i

ta được:

Line #    Mem usage    Increment   Line Contents
================================================
     8   37.711 MiB   37.711 MiB   @profile
     9                             def test_numpy():
    10   50.414 MiB   12.703 MiB       import numpy as np
    11                             
    12   50.414 MiB    0.000 MiB       arr = np.zeros((1000000,), dtype=np.uint32)
    13   54.586 MiB    0.000 MiB       for i in range(1000000):
    14   54.586 MiB    0.562 MiB           arr[i] = i

Vậy là chỉ tốn 4MiB cho arr, giống như đã được học. ?

Kết luận

Sự chênh lệch giữa 4MiB và 36MiB có thể là không lớn và ta có thể sống vui vẻ, nhưng giữa 4GiB và 36GiB lại là một chuyện rất lớn. Ngoài việc sử dụng numpy để tăng tốc độ trong tính toán, giờ đây ta đã có thêm lý do khác nữa: tiết kiệm memory.

Đọc thêm

#python #numpy