Большинству из нас сказали, что массивы numpy имеют более высокую производительность по сравнению со списками Python, но знаете почему?

На следующем графике показана эффективность использования двух случайных массивов / списков и их сложения. Мы ясно видим, что эта операция в numpy практически одинакова для 100 элементов и 10 000 000 элементов. В то время как python растет быстрыми темпами! Чтобы понять, почему, мы должны сначала быстро взглянуть на то, как работает ОЗУ и как массивы хранятся в ОЗУ по сравнению со списками.

RAM - это рабочая память нашего компьютера. Все ваши процессы от потоковой передачи YouTube до чтения этой статьи находятся в оперативной памяти. Вы можете представить RAM как книжную полку в вашем доме. Каждая книга - это область памяти, в которой хранится такое значение, как int, double или ссылка на другое место в памяти (также известное как указатель), но об этом позже. Каждый байт имеет адрес, обеспечивающий быстрый доступ к любой точке данных. Отсюда и название - оперативная память.

Массивы Numpy хранят один определенный тип данных, а количество элементов указывается заранее. Это необходимо, потому что они хранятся как один непрерывный блок памяти. Это как энциклопедии, расположенные на вашей книжной полке в алфавитном порядке. Это изображение представляет собой массив типа 1 байт int и длины 5. Зная начальную ячейку памяти массива, мы можем просто добавить индекс, чтобы немедленно перейти к любому элементу.

Списки Python так не хранятся. Вам не нужно указывать, сколько элементов вы добавите в свой список, и вам не нужно определять тип данных. Это возможно, потому что списки Python на самом деле являются массивами указателей. Указатель - это ссылка на объект в памяти. Когда вы извлекаете первый элемент в своем списке, python выполняет два шага: во-первых, извлекает указатель. Во-вторых, перейдите в ячейку памяти указателя, чтобы наконец получить желаемый объект. Этот дополнительный поиск в памяти способствует значительному снижению производительности списков.

Становится хуже. Ваш процессор имеет небольшой объем локальной памяти, известный как кеш. Чтобы избежать повторных обращений к ОЗУ, ЦП будет извлекать и сохранять блоки непрерывной памяти в кэше. Представьте, что ваша прикроватная тумбочка - это тайник, а ваша книжная полка - это оперативная память. Когда вы идете за первой книгой в своем наборе энциклопедий, вы решаете забрать их все обратно в тайник тумбочки. Ведь это удобно, потому что все они рядом. Теперь вы можете быстро получить доступ ко всем книгам, не вставая! Со списками мы не получаем этого преимущества. Данные разбросаны по всей вашей оперативной памяти и указаны с помощью указателей. Каждый раз, когда нам нужен фрагмент данных, мы должны получить указатель и затем найти его. Как будто наши энциклопедии разбросаны, и нам пришлось бегать по библиотеке нашего особняка в поисках книг!

Тема памяти и того, как хранится каждая структура данных, может очень быстро стать очень плотной, но, надеюсь, это объяснение высокого уровня даст вам мысленное представление о том, что происходит под капотом Python. Главный вывод - понимание плюсов и минусов массивов и списков и когда их использовать. Проще говоря, используйте массивы для больших наборов данных одного типа. Если вам интересна тема, попробуйте изучить, как это можно сделать еще быстрее на графическом процессоре!