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

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

  1. Как работает система deadNode/aliveNode (общая картина)?
  2. В чем ключевая разница между живыми узлами и мертвыми узлами, хотя они оба представляют данные, хранящиеся в оперативной памяти, если я прав?
  3. Можно ли вручную настроить лимит оперативной памяти для буферизации? Под комментарием есть код, который считывает значение из params['NODE_CACHE_SLOTS']. Может ли это быть как-то указано пользователем? Например, если я хочу оставить немного оперативной памяти для других приложений, которым тоже нужна память?
  4. В каких ситуациях PyTables может дать сбой или значительно замедлиться при работе с большим объемом данных? В моем случае может превысить память в 100 раз, какие частые подводные камни в таких ситуациях?
  5. Какое использование PyTables в смысле размера, структуры данных, а также манипуляций с данными считается «правильным» для достижения наилучшей производительности?
  6. Документация рекомендует использовать .flush() после каждого базового .append() цикла . Насколько длинным может быть этот цикл? Я выполняю небольшой тест, сравнивая SQLite и PyTables в том, как они справляются с созданием огромной таблицы с парами ключ-значение из больших файлов CSV. И когда я использую .flush() реже в основном цикле, PyTables получает огромное ускорение. Итак, правильно ли .append() относительно большие куски данных, а затем использовать .flush()?

person Gill Bates    schedule 20.02.2013    source источник
comment
Вы не можете хранить контент в памяти, которая в 100 раз превышает доступную оперативную память. Однако PyTables может помочь вам получить доступ к данным фрагментами или применить функции к вашим данным с эффективным использованием памяти (иногда). Что вы пытаетесь сделать со своими данными?   -  person seandavi    schedule 20.02.2013


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

В 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. Теоретически он не должен падать, но мы живем не в теоретическом мире.

Редактировать:

На самом деле, обнаружив необходимость в pytables, я столкнулся с этот вопрос /a> в их часто задаваемых вопросах, которые могут решить некоторые из ваших проблем.

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

person Raufio    schedule 28.02.2013

Я не эксперт в PyTable1, но, скорее всего, это работает как подкачка памяти .

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

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

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

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


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

person Simon    schedule 23.02.2013

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

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

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

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

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

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

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

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

person chm    schedule 26.02.2013