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 имеет очень умную систему буферизации ввода-вывода, которая, как я понимаю, хранит данные, на которые ссылается пользователь в быстрой оперативной памяти в виде "живыхNodes", продолжает ссылаться на ранее и в настоящее время неопубликованные данные как "deadNodes" для быстрого "оживления", когда это необходимо, и считывает данные с диска, если запрошенный ключ отсутствует как в мертвых, так и в живых категориях.

Мне нужно знать, как именно PyTables справляется с ситуациями при работе с данными, большими, чем доступная память. Мои конкретные вопросы:

  • Как работает система deadNode/aliveNode (общее изображение)?
  • Какое ключевое различие между liveNodes/deadNodes, пока они оба представляют данные, хранящиеся в ОЗУ, если они правы?
  • Можно ли ограничить RAM для буферизации вручную? Ниже комментария есть код, который считывает значение из params['NODE_CACHE_SLOTS']. Может ли это быть каким-то образом указано пользователем? Например, если я хочу оставить ОЗУ для других приложений, которые также нуждаются в памяти?
  • В каких ситуациях PyTables может сбой или значительное замедление при работе с большой суммой данных? В моем случае может превышать память в 100 раз, каковы распространенные ошибки в таких ситуациях?
  • Какое использование PyTables в значении размера, структуры данных, а также манипуляции с данными, считающимися "правильными" для достижения наилучшей производительности?
  • Документы предлагают использовать .flush() после каждого базового цикла .append(). Как долго этот цикл может быть на самом деле? Я выполняю небольшой тест, сравнивая SQLite и PyTables в том, как они могут обрабатывать создание огромной таблицы с парами ключ-значение из больших CSV файлов. И когда я использую .flush(), реже в основном цикле, PyTables получает огромное ускорение. Итак - правильно ли, .append() относительно большие куски данных, а затем используйте .flush()?

Ответ 1

Структура памяти

Никогда не использовал pytables, но смотрел исходный код:

class _Deadnodes(lrucacheExtension.NodeCache):
    pass

Итак, похоже, что _deadnodes реализованы с использованием кеша LRU. LRU == "Наименее недавно использовавшийся", что означает, что сначала выкинет наименее используемый node. источник здесь.

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, который является средним человеком между живыми узлами (памятью) и полными данными (диском). Если слишком низкий коэффициент попадания, то похоже, что кеш будет отключен, и после определенного количества циклов он снова включится.

В parameters.py переменные DISABLE_EVERY_CYCLE, ENABLE EVERY_CYCLE и LOWEST_HIT_RATIO используются для определения количества циклов в LOWEST_HIT_RATIO для отключения после и количества циклов ожидания для повторного включения. Изменение этих значений не рекомендуется.

Главное, что вы должны извлечь из этого, заключается в том, что если вам нужно обрабатывать большой набор данных, убедитесь, что они находятся на одних и тех же узлах. Если вам это удастся, прочитайте в куске, сделайте обработку на этом патроне, получите свои результаты, затем загрузите еще один кусок. Если вы загрузите кусок A, получите еще один кусок B, затем снова загрузите кусок A, это вызовет наибольшую задержку. Работайте только на одном фрагменте данных и сохраняйте доступ и записывайте до минимума. После того, как значение находится в _alivenodes, его можно быстро изменить, _deadnodes немного медленнее, и ни один из них не намного медленнее.

NODE_CACHE_SLOTS

params['NODE_CACHE_SLOTS'] определяет размер набора мертвых узлов. Отслеживая его обратно на parameters.py, он по умолчанию равен 64. Он указывает, что вы можете попробовать разные значения и отчитываться. Вы можете либо изменить значение в файле, либо сделать:

import parameters
parameters.NODE_CACHE_SLOTS = # something else

Это ограничивает количество узлов, хранящихся в кеше. В прошлом вы ограничены размером кучи python, чтобы установить, что см. .

добавить/вровень

Для append, flush гарантирует, что строки выводятся в таблицу. Чем больше данных вы перемещаете с этим, тем больше времени потребуется, чтобы данные переместились из внутреннего буфера в структуру данных. Он вызывает измененные версии функции H5TBwrite_records с другим кодом обработки. Я предполагаю, что продолжительность вызова зависит от продолжительности цикла вывода.

Имейте в виду, что все это из исходного кода, и не рассматривая какую-либо дополнительную магию, которую они пытаются сделать. Я никогда не использовал pytables. Теоретически он не должен падать, но мы не живем в теоретическом мире.

Edit:

Собственно, нахожу потребность в pytables самостоятельно, я встретил этот вопрос в своем ответе, который может ответить на некоторые ваши проблемы.

Спасибо за разоблачение pytables для меня, если бы я встретил файлы .h5, прежде чем исследовать этот вопрос, я бы не знал, что делать.

Ответ 2

Я не эксперт в PyTable 1 но он скорее всего работает как swap memory.

aliveNodes живут в ОЗУ, а deadNodes, вероятно, хранятся на диске в файлах hdf5 (формат двоичного файла, используемый PyTables). Каждый раз, когда вам нужно получить доступ к части данных, она должна находиться в ОЗУ. Итак, PyTable проверяет, есть ли он там (aliveNodes) и возвращает его вам, если это так. В противном случае ему нужно оживить deadNode, где живут данные. Поскольку ОЗУ ограничено, он, вероятно, убьет (напишет на диск) неиспользуемый aliveNode, чтобы сделать какую-то комнату заранее.

Причиной этого процесса является, конечно, ограниченный размер ОЗУ. Следствием этого является то, что производительность влияет каждый раз, когда вам нужно поменять node (убить a node и оживить другое).

Чтобы оптимизировать производительность, вы должны попытаться свести к минимуму свопинг. Например, если ваши данные могут обрабатываться параллельно, вы можете загрузить каждый node только один раз. Другой пример: представьте, что вам нужно перебирать каждый элемент огромной матрицы, которая разбивается на сетку узлов. Тогда вам лучше избегать доступа к его элементам по строке или столбцу, а скорее node на node.

Конечно, PyTable обрабатывает это под капотом, поэтому вам не нужно контролировать, что есть в каждом node (но я призываю вас копаться вокруг этой переменной NODE_CACHE_SLOTS, по крайней мере, чтобы понять, как она работает). Но, как правило, быстрее обращаться к данным, которые смежны, а не разбросаны по всему месту. Как всегда, если время выполнения является важной проблемой для вашего приложения (приложений), профайл вашего кода.


1 Перевод: я почти ничего не знаю о PyTables

Ответ 3

Я также не эксперт в PyTable, и Саймон, похоже, хорошо освещал концепцию памяти подкачки, НО, если вы хотите конкретный пример алгоритма, предназначенного для обработки данных, слишком больших для размещения в памяти, я бы рекомендуем посмотреть внешний вид.

Основная идея такова: вы не можете вместить все свои данные в память, но вам нужно ее отсортировать. Однако вы можете поместить некоторые данные в память, в блоки размером k. Скажем, что есть такие блоки.

  • Разделить данные на блоки размера k.
  • Для каждого блока, принесите его в память и отсортируйте (например, используя quicksort или что-то еще), затем напишите его отсортированную версию на диск.

Теперь у нас есть j блоков отсортированных данных, которые мы хотим объединить в один длинный отсортированный фрагмент данных. Эта проблема звучит как mergesort! Таким образом,

  • Принесите самое низкое значение из каждого из отсортированных блоков j в память.
  • Найти наименьшее из этих значений j. Это самая маленькая часть данных! Итак, напишите это на диск где-то в качестве начала нашего отсортированного набора данных.
  • Замените вновь записанное значение следующим наименьшим значением из своего блока в память (это бит "свопинга" в swap-памяти).

Теперь данные в памяти являются наименьшими j, за исключением тех, которые мы уже вписали в окончательный отсортированный набор данных на диске. Поэтому, если мы повторим этот процесс, пока все данные не будут записаны в окончательный набор, он всегда будет отсортирован.

Итак, это просто пример алгоритма, который использует обмен памяти для обработки данных, которые слишком велики для размещения в памяти. Методы сортировки PyTable, вероятно, находятся в этих строках.

Бонус: Здесь некоторые ссылки больше объяснений внешней сортировки.