PyTables, работающие с данными, размер которых во много раз превышает размер памяти
Я пытаюсь понять, как PyTables управляет данными, размер которых больше размера памяти. Вот комментарий в коде PyTables ( ссылка на GitHub):
# Nodes referenced by a variable are kept in `_aliveNodes`.
# When they are no longer referenced, they move themselves
# to `_deadNodes`, where they are kept until they are referenced again
# or they are preempted from it by other unreferenced nodes.
Также полезные комментарии можно найти внутри метода _getNode.
Похоже, что в PyTables есть очень умная система буферизации ввода-вывода, которая, как я понимаю, хранит данные, на которые ссылается пользователь, в быстрой оперативной памяти как "aliveNodes", сохраняет ссылки до и в настоящее время не ссылающиеся на данные как "deadNodes" для быстрого "восстановления" их при необходимости, и считывает данные с диска, если запрошенный ключ отсутствует как в мертвых, так и в живых категориях.
Мне нужны некоторые знания о том, как именно PyTables справляется с ситуациями при работе с данными, размер которых превышает объем доступной памяти. Мои конкретные вопросы:
- Как работает система deadNode/aliveNode (общая картина)?
- В чем ключевое отличие между aliveNodes/deadNodes, когда они оба представляют данные, хранящиеся в ОЗУ, если я прав?
- Можно ли настроить ограничение ОЗУ для буферизации вручную? Ниже комментария есть код, который читает значение из
params['NODE_CACHE_SLOTS']
, Может ли это быть как-то указано пользователем? Например, если я хочу оставить немного оперативной памяти для других приложений, которые тоже нуждаются в памяти? - В каких ситуациях PyTables может аварийно завершить работу или значительно замедлить работу с большим объемом данных? В моем случае может превысить память в 100 раз, какие распространенные подводные камни в таких ситуациях?
- Какое использование PyTables в смысле размера, структуры данных, а также манипуляций с данными считается "правильным" для достижения наилучшей производительности?
- Документы предлагает использовать
.flush()
после каждого основного.append()
цикл. Как долго этот цикл на самом деле может быть? Я выполняю небольшой тест, сравнивая SQLite и PyTables, как они могут справиться с созданием огромной таблицы с парами ключ-значение из больших CSV-файлов. И когда я использую.flush()
реже в основном цикле PyTables получает огромное ускорение. Так что - это правильно, чтобы.append()
относительно большие куски данных, а затем использовать.flush()
?
3 ответа
Структура памяти
Никогда не использовал pytables, но, глядя на исходный код:
class _Deadnodes(lrucacheExtension.NodeCache):
pass
Таким образом, похоже, что _deadnodes реализованы с использованием кеша LRU. LRU == "Наименее недавно использованный", что означает, что он сначала отбросит наименее используемый узел. источник здесь.
class _AliveNodes(dict):
...
Который они используют в качестве настраиваемого словаря узлов, которые работают и представлены на самом деле в программе.
очень упрощенный пример (узлы - это буквы, цифры в кеше указывают, насколько устаревшей является запись):
memory of 4, takes 1 time step
cache with size 2, takes 5 times steps
disk with much much more, takes 50 time steps
get node A //memory,cache miss load from disk t=50
get node B // "" t=100
get node C // "" t=150
get node D // "" t=200
get node E // "" t=250
get node A //cache hit load from cache t=255
get node F //memory, cache miss load from disk t=305
get node G //memory, cache miss load from disk t=355
get node E // in memory t=356 (everything stays the same)
t=200 t=250 t=255
Memory CACHE Memory CACHE Memory CACHE
A E A0 E B0
B B A
C C C
D D D
t=305 t=355
Memory CACHE Memory CACHE
E B1 E G0
A C0 A C1
F F
D G
Как вы знаете, в реальной жизни эти структуры огромны, и время, необходимое для доступа к ним, составляет циклы шины, поэтому 1/(часы вашего компьютера).
Сравнительно, время, необходимое для доступа к элементам, одинаково. Это довольно незначительно для памяти, немного больше для кеша и намного больше для диска. Чтение с диска - самая длинная часть всего процесса. диск и рука должны двигаться, и т. д. Это физический процесс, а не электронный процесс, так как он происходит не со скоростью света.
Здесь в pytables они делают что-то подобное. Они написали свой собственный алгоритм кеширования в Cython, который является посредником между живыми узлами (памятью) и полными данными (диском). Если отношение попаданий слишком низкое, похоже, что кэш будет отключен, а после определенного количества циклов снова включится.
В параметрах. DISABLE_EVERY_CYCLE
, ENABLE EVERY_CYCLE
а также LOWEST_HIT_RATIO
Переменные используются для определения количества циклов в LOWEST_HIT_RATIO, которые необходимо отключить после, и количества циклов, которые следует ожидать для повторного включения. Изменение этих значений не рекомендуется.
Главное, что вы должны извлечь из этого, это то, что если вам нужно выполнить обработку большого набора данных, убедитесь, что они находятся на одних и тех же узлах. Если вам это сойдет с рук, прочитайте чанк, выполните обработку этого чака, получите результаты, а затем загрузите другой чанк. Если вы загрузите блок A, получите другой блок B, а затем снова загрузите блок A, это вызовет наибольшую задержку. Работайте только с одним фрагментом данных за раз и сохраняйте доступ и запись к минимуму. Как только значение в _alivenodes
это быстро изменить, _deadnodes
немного медленнее, и ни один не намного медленнее.
NODE_CACHE_SLOTS
params['NODE_CACHE_SLOTS']
определяет размер множества мертвых узлов. Возвращаясь к параметрам. По умолчанию он равен 64. В нем говорится, что вы можете опробовать различные значения и отчитаться. Вы можете изменить значение в файле или сделать:
import parameters
parameters.NODE_CACHE_SLOTS = # something else
Это только ограничивает количество узлов, хранящихся в кэше. В прошлом, что вы ограничены размером кучи Python, чтобы увидеть это.
добавление / флеш
За append
, flush
гарантирует, что строки выводятся в таблицу. Чем больше данных вы перемещаете с этим, тем больше времени потребуется для перемещения данных из внутреннего буфера в структуру данных. Он вызывает модифицированные версии функции H5TBwrite_records с другим кодом обработки. Я предполагаю, что длительность вызова определяет, как долго будет выходной цикл.
Имейте в виду, что это все из исходного кода, и не учитывая никакой дополнительной магии, которую они пытаются сделать. Я никогда не использовал pytables. Теоретически, это не должно рухнуть, но мы не живем в теоретическом мире.
Редактировать:
На самом деле, находя потребность в pytables, я столкнулся с этим вопросом в их часто задаваемых вопросах, который может ответить на некоторые ваши вопросы.
Спасибо, что выставили мне таблицы, если я сталкивался .h5
файлы, прежде чем исследовать этот вопрос, я бы не знал, что делать.
Я не эксперт в PyTable1, но он, скорее всего, работает как подкачка памяти.
aliveNodes
жить в оперативной памяти в то время как deadNodes
вероятно хранятся на диске в файлах hdf5 (двоичный формат файла, используемый PyTables). Каждый раз, когда вам нужно получить доступ к части данных, она должна быть в оперативной памяти. Так что PyTable проверяет, есть ли он уже там (aliveNodes
) и возвращает его вам, если это так. В противном случае необходимо оживить deadNode
где данные живут. Поскольку объем оперативной памяти ограничен, он, вероятно, убьет (записать на диск) неиспользованный aliveNode
освободить место заранее.
Причиной этого процесса является, конечно, ограниченный объем оперативной памяти. Следствием этого является то, что на производительность влияет каждый раз, когда вам нужно поменять узел (убить узел и оживить другой).
Чтобы оптимизировать производительность, вы должны попытаться свести к минимуму обмен. Например, если ваши данные могут обрабатываться параллельно, вы можете загрузить каждый узел только один раз. Другой пример: представьте, что вам нужно перебрать каждый элемент огромной матрицы, которая разбита на сетку узлов. Тогда вам лучше избегать доступа к его элементам по строкам или столбцам, а не по узлам.
Конечно, PyTable обрабатывает это под капотом, поэтому вам не нужно контролировать то, что находится в каждом узле (но я призываю вас покопаться в этом NODE_CACHE_SLOTS
переменная, по крайней мере, чтобы понять, как это работает). Но, как правило, быстрее получить доступ к данным, которые являются смежными, а не разбросанными по всему месту. Как всегда, если производительность вашего приложения важна для ваших приложений, профилируйте свой код.
1 перевод: я почти ничего не знаю о PyTables
Я также не эксперт в PyTable, и Саймон, похоже, хорошо рассмотрел концепцию подкачки памяти, НО, если вам нужен конкретный пример алгоритма, предназначенного для обработки данных, которые слишком велики, чтобы поместиться в памяти, я бы порекомендовал посмотреть на внешний вид
Основная идея такова: вы не можете поместить все свои данные в память, но вам нужно их отсортировать. Тем не менее, вы можете разместить некоторые данные в памяти, в блоках размером k. Скажем, есть j таких блоков.
- Разбейте данные на блоки размером k.
- Для каждого блока перенесите его в память и отсортируйте (например, с помощью быстрой сортировки или чего-либо еще), а затем запишите его отсортированную версию на диск.
Теперь у нас есть j блоков отсортированных данных, которые мы хотим объединить в один длинный отсортированный фрагмент данных. Эта проблема звучит как слияние! Так,
- Вывести наименьшее значение из каждого j отсортированных блоков в память
- Найдите наименьшее из этих значений j. Это самая маленькая часть данных! Итак, запишите это на диск как начало нашего отсортированного набора данных.
- Замените вновь записанное значение следующим наименьшим значением из его блока в память (это "бит подкачки" памяти подкачки).
Теперь данные в памяти - наименьшее j, кроме тех, которые мы уже записали в окончательный отсортированный набор данных на диске. Таким образом, если мы повторяем этот процесс до тех пор, пока все данные не будут записаны в окончательный набор, они всегда будут отсортированы.
Итак, это всего лишь пример алгоритма, который использует перестановку памяти для обработки данных, слишком больших для размещения в памяти. Методы сортировки PyTable, вероятно, в этом направлении.
Бонус: вот несколько ссылок на дополнительные объяснения внешнего вида.