Рубрики
Без рубрики

Apache Arrow: Прочтите DataFrame с нулевой памятью

Теоретическое и практическое введение в формат файла стрел на прошлой неделе я увидел твит из … Tagged с помощью Python, DataScience, Tutorial.

Теоретическое и практическое введение в формат файла стрел

На прошлой неделе я увидел твит от Уэса МакКинни, вероятно, наиболее известный как создатель пакета Awesome Pandas:

Итак, когда я увидел, как он цитирует Уильяма Гибсона, я подумал, что должно быть что -то удивительное. Я не был разочарован.

Твит в начале цепочки был примерно Обнимающееся лицо , библиотека обработки естественного языка. Проект собирает наборы данных, которые можно использовать для обучения и сравнительного анализа моделей. И некоторые из этих наборов данных огромны. В первоначальном твите, Томас Вольф указывает на это со специальным форматом файла, он и Квентин Лхоэст теперь могут переходить через 17 ГБ данных менее чем за минуту с следом RAM 9 МБ 🤯

Первый вопрос, который пришел мне в голову: как? Какая магия здесь происходит?

Так что это был будущий Уэс МакКинни, о котором говорил.

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

Любая достаточно развитая технология неотличима от магии. – Артур С. Кларк, 3 -й закон

фото Джошуа Сортино на Неспособный

Плавно обменять данные

Первая подсказка была Томас Вольф, ссылаясь на Apache Arrow Анкет Apache Arrow – это проект, созданный Уэсом МакКинни, намереваясь создать интерфейс для обмена данными:

Apache Arrow-это платформа для развития межязывания для данных в памяти. Он определяет стандартизированный язык, независимый от языка, формат столбчатой памяти для плоских и иерархических данных, организованных для эффективных аналитических операций на современном аппаратном обеспечении. Он также предоставляет вычислительные библиотеки и потоковые сообщения с нулевой копией и межпроцессную связь. [1]

Что это значит?

Перед стрелкой стандартным способом обмена данными между любым приложением или библиотекой было хранить его на диск так или иначе. Итак, если .NET Основная библиотека хочет передать данные в Python для анализа данных, скорее всего, кто -то напишет файл (например, CSV, JSON, Parquet,…), а затем снова прочитал его с Python. Оба шага, письменность (сериализация) и чтение (десериализация) являются дорогостоящими и медленными – и чем больше набор данных, тем дольше требуется для завершения каждого из них.

Что если бы был способ обмениваться данными непосредственно через рукопожатие и нулевое копирование? Это может выглядеть так: .NET начнет общаться с Python, указывать на кучу данных в памяти и быть как: Привет, приятель, это твое сейчас Анкет И Python мог бы прямо прыгать на него, не провозящая его из одного места в другое. Разве это не было бы фантастически?

Вот что такое Apache Arrow.

Паркет секрет?

Это заставило меня задуматься – как я могу использовать стрелу? Глядя на исходный код обнимающего лица, я узнал, что проект использует Pyarrow Чтобы прочитать данные. До тех пор я ассоциировал Pyarrow с Parquet, высоко сжатым, столбчатым форматом хранения. Итак, Паркет тем, как Arrow обменивается данными? (Спойлер: это не так)

Традиционно данные хранятся на диске в ряду. Столковое хранилище родилось из -за необходимости анализа больших наборов данных и эффективно агрегировать их. Аналитика данных меньше заинтересована в строках данных (например, одна транзакция клиента, один журнал вызовов,…), но на их агрегации (например, общая сумма, потраченная клиентом, общий протокол вызовов по региону,…).

Строка VS -ориентированное хранилище (адаптировано из [4] с Набор данных Penguin Station Station )

Это привело к изменению ориентации: вместо того, чтобы хранить его строку, столбчатое хранилище организует столбец данных по столбцу.

Parquet – это колоннарный формат файла, который имеет два основных преимущества [4]:

  1. Очень сжатый: While .json или .csv Файлы по умолчанию несомнены, паркет сжимает данные и, следовательно, экономит много дискового пространства. Таблицы обычно имеют сочетание столбцов с большим количеством уникальных значений (высокая кардинальность; подумайте об уникальном идентификаторе пользователя ) и столбцов с несколькими уникальными значениями (низкая кардинальность; подумайте о country ). Чем ниже кардинальность, лучше сжатие (может) работать – подробнее об этом в следующем разделе Файл запросов/отключение фильтра:
  2. Чернослить ненужные данные, прежде чем читать их. Это улучшает время загрузки и оптимизирует потребление ресурсов. Если вам нужны только два столбца из таблицы в тысячу столбцов, вам не нужно сканировать все ряды, чтобы получить два атрибута- Вы прямо избираете столбец полностью

Сжатие

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

  • Как Parquet допускает файл до таких маленьких размеров?
  • Где Пакет отличается от стрелы?

Переверните монету

Представьте, что вы переворачиваете монету десять раз и записываете результат:

[Head, Head, Head, Head, Tail, Tail, Tail, Head, Tail, Tail]

Теперь попробуйте сказать результат вслух? Скорее всего, вы сократите это и скажете что -то вроде «4 раза головы, 3 раза хвоста, голова и 2 раза хвоста» :

[4 x Head, 3 x Tail, Head, 2 x Tail]

Это сжатие в действии (описанный алгоритм называется кодирование длины длиной. [8]). Мы склонны, естественно, видим схему и сокращенно. Алгоритм сжатия тоже это делает – только с более сырой вычислительной мощностью и сложными правилами. Но этого примера должно быть достаточно, чтобы помочь нам понять ключевое различие: в то время как .CSV вызывает буквальный подход и вызывает каждую запись, паркет сокращенно (без потери информации).

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

[5 x Head, 5 x Tail]

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

Стрелка сжимается?

При этом мы получили некоторую интуицию того, почему паркетные файлы настолько малы по сравнению с несжатыми файлами .CSV. Но как это связано с стрелой?

Оказывается, это именно одно из ключевых различий. Паркет хранится высокоэффективным способом на диске. А с помощью отключения фильтра вы можете уменьшить объем чтения данных (то есть, выбрав только те столбцы, которые вам действительно нужны). Но если вы хотите выполнить операции на данных, вашему компьютеру все еще необходимо распаковать сжатую информацию и внести ее в память. [2]

Стрелка, с другой стороны, является Нанесенный памятью формат. В блоге Уэс МакКинни суммирует его под следующим образом:

«Конструкция сериализации стрелки обеспечивает« заголовок данных », который описывает точные местоположения и размеры всех буферов памяти для всех столбцов в таблице. Это означает, что вы можете карту памяти огромных, более высоких наборов данных, и оценивать алгоритмы в стиле Pandas на них на месте, не загружая их в память, как вам нужно с пандами сейчас. Вы могли бы прочитать 1 мегабайт из середины таблицы 1 терабайта, и вы платите только стоимость выполнения этих случайных чтений на общую сумму 1 мегабайт ». [6]

Короче говоря, приложения могут непосредственно работать в наборе данных, хранящемся на диске, без необходимости полностью загрузить его в память. Если вы помните начальный твит – это именно то, что там происходило.

Практика: сравнение производительности

Теперь давайте рассмотрим эти форматы данных. В качестве примера набора данных я использую Набор данных Penguin Station Station . Поскольку он содержит только 350 строк, я буду перебрать его до 1 миллиона, чтобы разница в производительности стала более очевидной:

Написание файлов

В качестве следующего шага я пишу файлы на диск в трех форматах:

  • CSV (DataFrame с пропущенными значениями)
  • Паркет (DataFrame с пропущенными значениями)
  • Стрелка (DataFrame с и без пропущенных значений)

Обратите внимание, что стрелка может быть преобразована в панды без распределения копии памяти) при определенных условиях. Один из них: не должно быть значений NAN. Чтобы сравнить производительность между и без нулевого заполнения, я пишу файл со стрелками один раз с отсутствием числовых значений и без отсутствия.

Полученные размеры файлов:

Сравнение размера файла

Паркет, как и ожидалось, наименьший файл – несмотря на случайную последовательность (до написания файла не произошла, он сияет с коэффициентом сжатия 80%. Стрелка лишь немного меньше, чем CSV. Причина в том, что CSV хранит даже числовые значения в виде строк, которые потребляют больше места на дисков. Во всех случаях разность размеров между файлами с отсутствующими значениями и без незначительной (<0,5 МБ).

Время чтения

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

  • CSV
  • Паркет
  • Стрелка с файлом API ( usfile (...) )
  • Стрелка в виде памяти API ( memore_map (...) ) с отсутствующими значениями/NAN
  • Стрелка в виде памяти API ( memore_map (...) ) без пропущенных значений

Время каждой из трех функций, возникают следующие результаты:

Сравнение производительности: время, необходимое для чтения столбца и расчета среднего значения

Неудивительно, что CSV является самым медленным вариантом. Требуется чтение 200 МБ, проанализировать текст, отбросить все столбцы, кроме длины флиппера, а затем вычислить среднее значение.

Пакет в ~ 60 раз быстрее, так как нет необходимости проанализировать весь файл – только требуемые столбцы.

Стрелка с отсутствующими значениями в ~ 3 раза быстрее, чем парке, и почти в 200 раз быстрее, чем CSV. Как и Parquet, стрелка может ограничиваться чтением только указанного столбца. Что делает его быстрее, так это то, что нет необходимости распаковать столбец.

Обратите внимание, что разница между считываемыми файлами со стрелками с чтением памяти с нулевым заполнением и без нее означала еще одно повышение производительности в ~ 3 раза (то есть нулевая копия в общей сложности в 600 раз быстрее, чем CSV и в 9 раз быстрее, чем паркет).

Что удивительно: стрелка с API файла даже медленнее, чем Parquet. В чем дело?

Потребление памяти

Чтобы ответить на это, давайте посмотрим на потребление памяти. Сколько оперативной памяти потребляет каждый файл, если мы прочитаем один столбец?

Вот результаты:

Сравнение производительности: память, потребляемая для чтения столбца

Наиболее примечательно: стрелка с API File потребляет 189 МБ – что в значительной степени всего размер файла (даже если мы читаем только один столбец?!). Ответ заключается в Документация :

«[…] Osfile выделяет новую память на каждое чтение, например, объекты файла Python». [3]

С помощью OSFILE Весь файл был прочитан в память в первую очередь. Теперь очевидно, почему эта операция была медленнее, чем паркет, и потребляет больше всего памяти!

Однако, используя функцию отображения памяти и с заполненными значениями NAN, DataFrame Pandas был создан непосредственно поверх файла сохраненной стрелки. Нет копирования: 0 МБ ОЗУ! Неудивительно, что это был самый быстрый вариант.

Вы можете найти все Jupyter notepbook здесь 📝

Вывод

Для меня есть много, чтобы узнать о стрелке. Что я узнал до сих пор: вы не можете иметь свой торт и съесть его тоже. Существует компромисс между [7]:

  • Оптимизировать для дискового пространства/долгосрочного хранения на диске → Паркет
  • Оптимизировать для обмена данными и быстрого поиска → стрелка

Повышение производительности как паркета, так и стрелы значительно по сравнению с CSV. При хранении стрелы на диск она потребляет больше хранилища, чем паркета. Тем не менее, Arrow бьет Parquet с точки зрения производительности чтения – как с точки зрения времени, так и с точки зрения потребления памяти. Представленные примеры (вычисляют среднее значение из одной колонки/чтения) – это только царапают на поверхности – я ожидаю, что с более сложными запросами и более крупными наборами данных стрелка будет больше.

Пока стрелка читается с функцией отображения памяти, производительность считывания невероятна. Лучший случай – это когда набор данных не имеет отсутствующих значений/NANS. Затем Pyarrow может сделать свою магию и позволить вам работать на столе, едва потребляя память.

Будущее действительно уже здесь – и это удивительно!

Подпишись на меня в Твиттере

Я буду продолжать писать о Python, данных и технологии – я был бы рад Познакомьтесь в Твиттере

Большое спасибо Yingying Для тщательного обзора и отличных отзывов! 👏

Источники

[1] Apache Arrow, Целевая страница (2020), веб -сайт Apache Arrow

[2] Apache Arrow, FAQ (2020), веб -сайт Apache Arrow

[3] Apache Arrow, Файлы на диск и отображение памяти (2020), документация Apache Arrow Python Bindings

[4] J. Ledem, Apache Arrow и Parquet Apache: Зачем нам нужны разные проекты для столбчатых данных, на диске и в памяти (2017), Kdnuggets

[5] J. Ledem, Сторонная дорожная карта: Apache Parquet и Apache Arrow (2017), Дремо

[6] В. МакКинни, Apache Arrow и «10 вещей, которые я ненавижу в пандах» (2017), блог

[7] В. МакКинни, Некоторые комментарии к блогу Даниэля Абади о Apache Arrow (2017), блог

[8] Википедия, Кодирование длины пробега (2020), Википедия

Оригинал: “https://dev.to/simicd/apache-arrow-read-dataframe-with-zero-memory-h81”