Optimasi Digunakan dengan Python: Daftar dan Tuple

Dalam Python, ada dua jenis yang sama - daftar (daftar) dan tuple (tuple). Perbedaan paling terkenal antara keduanya adalah bahwa tupel tidak dapat diubah.

Anda tidak dapat mengubah objek dalam tuple:

>>> a = (1,2,3) >>> a[0] = 10 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment 

Tapi Anda bisa memodifikasi objek yang bisa berubah di dalam tuple:

 >>> b = (1,[1,2,3],3) >>> b[1] [1, 2, 3] >>> b[1].append(4) >>> b (1, [1, 2, 3, 4], 3) 

Di dalam CPython (penerjemah standar), daftar dan tuple diimplementasikan sebagai selembar pointer (tautan) ke objek Python, mis. secara fisik mereka tidak menyimpan benda bersebelahan. Saat Anda menghapus objek dari daftar, referensi ke objek ini dihapus. Jika orang lain mengacu pada objek, itu akan terus berada dalam memori.

Tuples


Terlepas dari kenyataan bahwa tupel jauh lebih jarang terjadi dalam kode dan tidak begitu populer, ini adalah tipe yang sangat mendasar yang digunakan oleh Python untuk keperluan internal.

Anda mungkin tidak melihat, tetapi Anda menggunakan tuple ketika:

  • bekerja dengan argumen atau parameter (disimpan sebagai tupel)
  • mengembalikan dua atau lebih variabel dari suatu fungsi
  • iterate kunci nilai dalam kamus
  • gunakan pemformatan string

Biasanya, skrip paling sederhana menggunakan ribuan tupel:

 >>> import gc >>> def type_stats(type_obj): ... count = 0 ... for obj in gc.get_objects(): ... if type(obj) == type_obj: ... count += 1 ... return count ... >>> type_stats(tuple) 3136 >>> type_stats(list) 659 >>> import pandas >>> type_stats(tuple) 6953 >>> type_stats(list) 2455 

Daftar kosong vs tuple kosong


Sebuah tuple kosong berfungsi seperti singleton, mis. selalu ada hanya satu tuple kosong di memori skrip Python yang sedang berjalan. Semua tupel kosong hanya merujuk ke objek yang sama, ini dimungkinkan karena tupel tidak berubah. Pendekatan ini menghemat banyak memori dan mempercepat proses bekerja dengan tuple kosong.

 >>> a = () >>> b = () >>> a is b True >>> id(a) 4409020488 >>> id(b) 4409020488 >>> #  CPython,  id    . 

Tetapi ini tidak berfungsi dengan daftar, karena mereka dapat diubah:

 >>> a = [] >>> b = [] >>> a is b False >>> id(a) 4465566920 >>> id(b) 4465370632 

Mengoptimalkan alokasi memori untuk tupel


Untuk mengurangi fragmentasi memori dan mempercepat pembuatan tupel, Python menggunakan kembali tupel lama yang telah dihapus. Jika sebuah tuple terdiri dari kurang dari 20 elemen dan tidak lagi digunakan, maka alih-alih menghapusnya, Python menempatkannya dalam daftar khusus, yang menyimpan tuple gratis untuk digunakan kembali.

Daftar ini dibagi menjadi 20 kelompok, di mana masing-masing kelompok adalah daftar tupel ukuran n, di mana n adalah 0 hingga 20. Setiap kelompok dapat menyimpan hingga 2.000 tupel gratis. Grup pertama menyimpan hanya satu elemen dan merupakan daftar satu tuple kosong.

 >>> a = (1,2,3) >>> id(a) 4427578104 >>> del a >>> b = (1,2,4) >>> id(b) 4427578104 

Pada contoh di atas, kita dapat melihat bahwa a dan b memiliki alamat yang sama dalam memori. Ini karena kami mengambil tuple gratis dengan ukuran yang sama.

Mengoptimalkan alokasi memori untuk daftar


Karena daftar dapat berubah, optimasi yang sama seperti dalam kasus tupel tidak dapat lagi diputar. Meskipun demikian, daftar menggunakan pengoptimalan serupa yang ditujukan pada daftar kosong. Jika daftar kosong dihapus, maka itu juga dapat digunakan kembali di masa depan.

 >>> a = [] >>> id(a) 4465566792 >>> del a >>> b = [] >>> id(b) 4465566792 

Ubah Ukuran Daftar


Untuk menghindari overhead daftar perubahan ukuran terus-menerus, Python tidak mengubah ukurannya setiap kali diperlukan. Alih-alih, setiap daftar memiliki satu set sel tambahan yang disembunyikan bagi pengguna, tetapi nantinya dapat digunakan untuk elemen baru. Segera setelah sel tersembunyi berakhir, Python menambahkan ruang ekstra untuk elemen baru. Dan dia melakukannya dengan margin yang baik, jumlah sel tersembunyi dipilih berdasarkan ukuran daftar saat ini - semakin besar, semakin banyak slot tersembunyi tambahan untuk elemen baru.

Optimalisasi ini sangat membantu ketika Anda mencoba menambahkan banyak elemen dalam satu lingkaran.

Pola pertumbuhan ukuran daftar terlihat seperti ini: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...

Misalnya, jika Anda ingin menambahkan elemen baru ke daftar dengan 8 elemen, maka tidak akan ada sel bebas di dalamnya dan Python akan segera memperluas ukurannya menjadi 16 sel, di mana 9 di antaranya akan ditempati dan terlihat oleh pengguna.

Rumus ukuran Python:

 >>> def get_new_size(n_items): ... new_size = n_items + (n_items // 2 ** 3) ... if n_items < 9: ... new_size += 3 ... else: ... new_size += 6 ... ... return new_size ... >>> get_new_size(9) 16 

Kecepatan


Jika kita membandingkan kedua jenis ini dalam kecepatan, maka rata-rata di rumah sakit, tupel sedikit lebih cepat daripada daftar. Raymond Hettinger memiliki penjelasan yang bagus untuk perbedaan kecepatan pada stackoverflow .

PS: Saya penulis artikel ini, kamu bisa bertanya.

Source: https://habr.com/ru/post/id417783/


All Articles