JSON — это широко распространённый формат, применяемый для хранения информации, основанной на обычном тексте. Он поддерживается самыми разными системами, обеспечивая их взаимодействие. Чаще всего это — веб-приложения и большие языковые модели (Large Language Model, LLM). Хотя JSON-данные удобны для восприятия человеком, их сложно обрабатывать, используя инструменты из сфер Data Science (наука о данных) и Data Engineering (инженерия данных).

JSON-данные часто существуют в виде JSON-строк (формат JSON Lines), отделённых друг от друга символами перевода строки (NDJSON, Newline-Delimited JSON). NDJSON используется для представления записей, входящих в состав набора данных. Часто первым этапом обработки данных является чтение файлов формата JSON Lines и преобразование их в объекты DataFrame (датафрейм).
В это материале мы сравним производительность и функционал API, доступных в Python и применяемых для преобразования формата JSON Lines в датафреймы. В частности, речь пойдёт о следующих библиотеках:
pandas
DuckDB
pyarrow
Мы продемонстрируем возможности средства для чтения JSON из cudf.pandas. В число этих возможностей входит хорошее масштабирование производительности и высокая пропускная способность. Особенно это касается обработки данных со сложной схемой. Мы, кроме того, поговорим о различных опциях средства для чтения JSON из cuDF, которые улучшают совместимость этого инструмента с Apache Spark. Эти опции дают Python-пользователям возможность решать проблемы с кавычками, с некорректными записями, проблемы, касающиеся смешения типов, а так же — позволяют бороться с другими аномалиями, характерными для JSON.
Сравнение парсинга и чтения JSON
Когда говорят об обработке JSON-данных — важно различать такие понятия, как парсинг (parsing) и чтение (reading).
Парсеры JSON
JSON-парсеры — такие, как simdjson, преобразуют буферы, содержащие символьные данные, в векторы токенов. Эти токены представляют логические компоненты JSON-данных, включая имена полей, значения, сведения о начале и конце массивов и коллекций. Парсинг — это чрезвычайно важный этап извлечения информации из JSON-данных. Серьёзные силы были потрачены на исследования, направленные на достижение высокой пропускной способности JSON-парсеров.
Для того чтобы использовать в конвейерах обработки данных информацию, представленную в формате JSON Lines, токены часто необходимо преобразовать в датафрейм, или в многоколоночный формат наподобие Apache Arrow.
Средства для чтения JSON
Инструменты для чтения JSON — такие, как pandas.read_json
, преобразует входные символьные данные в объекты DataFrame, организованные по столбцам и строкам. Работа средств для чтения JSON начинается с парсинга. Затем, кроме прочего, производится обнаружение границ записей, определяются столбцы верхнего уровня и вложенные дочерние столбцы структур или списков. Также выводятся типы данных, обрабатываются поля, в которых отсутствуют значения, и поля, в которых записано значение null.
Средства для чтения JSON преобразуют неструктурированные символьные данные в структурированные датафреймы, обеспечивая совместимость JSON-данных с приложениями, которые от них зависят.
Измерение производительности средств для чтения данных формата JSON Lines
JSON Lines — это гибкий формат для представления данных. Вот некоторые важные свойства JSON-данных:
Количество записей на файл.
Количество столбцов верхнего уровня.
Глубина структур или списков, вложенных в каждый столбец.
Типы данных.
Распределение длин строк.
Доля отсутствующих ключей.
В этом исследовании мы используем фиксированное количество записей (200 тысяч), а количество столбцов меняется в пределах от 2 до 200. Здесь мы исследуем широкий диапазон сложных схем данных. Мы работаем со следующими четырьмя типами данных:
list<int>
иlist<str>
с двумя дочерними элементами.struct<int>
иstruct<str>
с одним дочерним элементом.
В Таблице 1 показаны первые два столбца первых двух записей для различных типов данных, включая list<int>
, list<str>
, struct<int>
и struct<str>
.
Таблица 1. Примеры символьных данных в формате JSON Lines
Тип данных | Примеры записей |
|
|
|
|
|
|
|
|
Данные о производительности были собраны для ветки 25.02 cuDF, а так же — с использованием следующих версий других библиотек: pandas 2.2.3, duckdb 1.1.3 и pyarrow 17.0.0. Для запуска тестов применялось следующее аппаратное обеспечение: видеоускоритель NVIDIA H100 Tensor Core 80 GB HBM3 и процессор Intel Xeon Platinum 8480CL с 2 ТиБ оперативной памяти. Данные о времени выполнения операций брались на третьем (из трёх) повторении теста. Делалось это для того, чтобы исключить дополнительную нагрузку на систему, вызываемую инициализацией, и чтобы обеспечить присутствие файла с входными данными в страничном кеше операционной системы.
Помимо сбора данных о cudf.pandas, выполняемого без изменения кода, мы собрали данные о производительности из pylibcudf — API Python для libcudf — вычислительного ядра CUDA, написанного на C++. При запусках тестов с применением pylibcudf использовался асинхронный доступ к памяти CUDA через RAPIDS Memory Manager (RMM). Значения пропускной способности системы вычислялись на основе сведений о размере входного JSON-файла и о времени, потраченном на чтение данных при третьем повторении теста.
Вот примеры кода, в котором вызываются различные функции чтения данных формата JSON Lines:
# pandas и cudf.pandas
import pandas as pd
df = pd.read_json(file_path, lines=True)
# DuckDB
import duckdb
df = duckdb.read_json(file_path, format='newline_delimited')
# pyarrow
import pyarrow.json as paj
table = paj.read_json(file_path)
# pylibcudf
import pylibcudf as plc
s = plc.io.types.SourceInfo([file_path])
opt = plc.io.json.JsonReaderOptions.builder(s).lines(True).build()
df = plc.io.json.read_json(opt)
Производительность средств для чтения данных формата JSON Lines
В целом можно сказать, что мы получили широкий спектр характеристик производительности средств для чтения JSON, доступных в Python. Время выполнения задачи варьируется от 1,5 секунд до почти 5 минут.
В Таблице 2 показаны итоговые данные о времени, полученные при исследовании нескольких конфигураций средств для чтения JSON при обработке 28 входных файлов общим объёмом 8,2 Гб.
Чтение JSON с помощью cudf.pandas даёт примерно 133-кратное ускорение в сравнении с использованием стандартных средств pandas. А в сравнении с pandas, где применяется движок pyarrow, cudf.pandas даёт 60-кратное ускорение.
DuckDB и pyarrow тоже показывают хорошую производительность — речь идёт примерно о 60 секундах при использовании DuckDB, и о 6,9 секундах для pyarrow после подстройки параметра
block_size
, определяющего размер блоков.Быстрее всего оказалась библиотека pylibcudf, показав время в 1,5 секунды, что примерно в 4,6 раза быстрее, чем у библиотеки pyarrow после подстройки размера блоков.
Таблица 2. Итоговые данные о времени чтения JSON-данных из 28 входных файлов
Обозначение средства для чтения JSON-данных | Время выполнения теста (секунд) | Комментарий |
cudf.pandas | 2,1 | Использование -m cudf.pandas из командной строки |
pylibcudf | 1,5 | |
pandas | 281 | |
pandas-pa | 130 | Использование движка pyarrow |
DuckDB | 62,9 | |
pyarrow | 15,2 | |
pyarrow-20MB | 6,9 | Использование значения параметра |
В Таблице 2 приведены общие сведения об испытаниях, где используются 2, 5, 10, 20, 50, 100 и 200 столбцов и следующие типы данных: list<int>
, list<str>
, struct<int>
и struct<str>
.
Присмотревшись к данным, разбитым по типам и по количеству столбцов, мы обнаружили, что производительность средств для чтения JSON варьируется в широком диапазоне значений. Производительность зависит от особенностей входных данных и от применяемой библиотеки. Так, речь идёт о диапазоне от 40 Мб/с до 3 Гб/с для библиотек, использующих процессор, и о диапазоне от 2 до 6 Гб/с для библиотеки cuDF, задействующей видеоускоритель.
На Рис. 1 даны сведения о пропускной способности систем при обработке данных. Эти сведения показаны для файлов, содержащих 200 тысяч строк и от 2 до 200 столбцов. При этом размер входных данных варьируется от примерно 10 Мб до 1,5 Гб.

Функция cudf.pandas read_json показала пропускную способность в районе2–5 Гб/с. Пропускная способность растёт при росте количества столбцов и при росте размеров входных данных. Мы, кроме того, обнаружили, что типы данных, содержащиеся в столбцах, лишь незначительно воздействуют на пропускную способность. Библиотека pylibcudf показывает пропускную способность примерно на 1–2 Гб/с выше, чем cuDF-python. Это так из-за снижения дополнительной нагрузки на систему, создаваемой механизмами Python и pandas.
В случае с функцией read_json из pandas измеренная пропускная способность составила 40–50 Мб/с при использовании стандартного движка UltraJSON (в наших материалах он называется pandas-uj). Использование движка pyarrow (engine="pyarrow"
) дало ускорение вплоть до 70–100 Мб/с за счёт применения более быстрого парсера JSON (pandas-pa). Производительность средства для чтения данных из pandas, похоже, ограничена необходимостью создания списков и словарей Python для каждого элемента таблицы.
Функция DuckDB read_json показала пропускную способность в районе 0,5–1 Гб/с для типов list<str>
и struct<str>
, а для типов list<int>
и struct<int>
измерения дали значения, меньшие 0,2 Гб/с. Пропускная способность не сильно зависела от количества столбцов в обрабатываемых файлах.
Исследуя функцию pyarrow read_json, мы обнаружили, что пропускная способность составляет 2–3 Гб/с для файлов с 5–20 столбцами. С ростом количества столбцов до 50 и выше пропускная способность падает. Типы обрабатываемых данных влияют на эту функцию слабее, чем количество столбцов в файлах и размер входных данных. При количестве столбцов, равном 200, и при размере записи, составляющей примерно 5 Кб на строку, пропускная способность упала до 0,6 Гб/с.
Увеличение значения параметра pyarrow block_size
до 20 Мб (pyarrow-20MB) привело к росту пропускной способности при обработке файлов, содержащих 100 и более столбцов, но при этом упала пропускная способность для файлов с менее чем 50 столбцами.
В целом можно сказать, что производительность DuckDB зависит, преимущественно, от типов данных. А cuDF и pyarrow, в основном, реагируют на количество столбцов в файлах и на размер входных данных. Библиотеки cudf.pandas и pylibcudf, использующие видеоускоритель, показали более высокую пропускную способность при обработке сложных схем данных, содержащих списки и структуры. Особенно это касается тех случаев, когда размеры входных данных превышали 50 Гб.
Параметры средств для чтения данных формата JSON Lines
Принимая во внимание то, что данные формата JSON имеют текстовую природу, такие данные часто включают в себя аномалии. Это приводит к появлению некорректных JSON-записей, или к тому, что данные не очень хорошо соответствуют структуре объектов DataFrame. Среди этих JSON-аномалий можно отметить поля с одинарными кавычками, обрезанные или повреждённые записи, смешение данных, относящихся к структурам и спискам. Когда что-то подобное встречается в файлах — это может нарушить работу конвейера обработки данных на этапе чтения JSON.
Вот несколько примеров JSON-аномалий:
# 'Одинарные кавычки'
# в имени поля "a" используются одинарные кавычки, а не двойные
s = '{"a":0}\n{\'a\':0}\n{"a":0}\n'
# 'Некорректные записи'
# вторая запись некорректна
s = '{"a":0}\n{"a"\n{"a":0}\n'
# 'Смешение типов'
# в столбце "a" появляется то список, то коллекция значений
s = '{"a":[0]}\n{"a":[0]}\n{"a":{"b":0}}\n'
Для того чтобы воспользоваться дополнительными опциями средства для чтения JSON в cuDF, мы рекомендуем применять cuDF-Python (import cudf
) и pylibcudf. Если в данных появляются строковые значения или имена полей, выделенные одинарными кавычками, можно воспользоваться имеющейся в cuDF опцией, позволяющей преобразовать одинарные кавычки в двойные. cuDF поддерживает эту возможность для того, чтобы обеспечить совместимость с опцией allowSingleQuotes, которая по умолчанию включена в Apache Spark.
Если в данных встречаются некорректные записи — и cuDF и DuckDB позволяют бороться с ними с помощью опций, позволяющих решать проблему через замену этих записей на null. Когда включена обработка ошибок, то если при обработке записи генерируется ошибка парсинга, все столбцы соответствующей строки будут заменены на null.
Если в одном и том же поле появляются и значения-списки, и значения-структуры, cuDF позволяет бороться с этим, давая нам опцию переопределения схемы dtype, применение которой позволяет приводить проблемные данные к строковому типу. В DuckDB используется похожий подход, предусматривающий вывод типа данных для JSON-значений
Библиотека pandas, при обработке смешанных типов, возможно, даёт нам самый точный инструмент, используя для представления входных данных Python-списки и словари.
Вот пример использования cuDF-Python и pylibcudf, где показана настройка опций средств чтения JSON. Сюда входит и переопределение схемы dtype для столбца с именем “a”. Подробности об этом смотрите в документации по функциям cudf.read_json и pylibcudf.io.json.read_json.
В случае с pylibcudf настройка объекта JsonReaderOptions
может выполняться либо до, либо после build
.
# cuDF-python
import cudf
df = cudf.read_json(
file_path,
dtype={"a":str},
on_bad_lines='recover',
lines=True,
normalize_single_quotes=True
)
# pylibcudf
import pylibcudf as plc
s = plc.io.types.SourceInfo([file_path])
opt = (
plc.io.json.JsonReaderOptions.builder(s)
.lines(True)
.dtypes([("a",plc.types.DataType(plc.types.TypeId.STRING), [])])
.recovery_mode(plc.io.types.JSONRecoveryMode.RECOVER_WITH_NULL)
.normalize_single_quotes(True)
.build()
)
df = plc.io.json.read_json(opt)
В Таблице 3 приведены сводные данные о поведении различных средств для чтения JSON с Python-API при их встрече с распространёнными JSON-аномалиями. Крестик указывает на то, что функция чтения данных вызывает исключение, а галочка означает, что библиотека способна успешно вернуть DataFrame. В будущих версиях библиотек эти особенности поведения функций могут измениться.
Таблица 3. Результаты работы разных средств для чтения JSON-файлов, содержащих аномалии, в число которых входит использование одинарных кавычек, некорректные записи и смешение типов данных
Одинарные кавычки | Некорректные записи | Смешанные типы | |
cuDF-Python, pylibcudf | ✔️ Приводит к двойным кавычкам | ✔️ Устанавливает в null | ✔️ Представляет в виде строки |
pandas | ❌ Исключение | ❌ Исключение | ✔️ Представляет в виде Python-объекта |
pandas (engine="pyarrow“) | ❌ Исключение | ❌ Исключение | ❌ Исключение |
DuckDB | ❌ Исключение | ✔️ Устанавливает в null | ✔️ Представляет в виде строкового JSON-типа |
pyarrow | ❌ Исключение | ❌ Исключение | ❌ Исключение |
cuDF поддерживает несколько дополнительных опций средства для чтения JSON-данных. Они чрезвычайно важны для обеспечения совместимости с соглашениями, принятыми в Apache Spark. Теперь эти опции доступны и пользователям Python. Вот некоторые из них:
Правила проверки корректности значений для чисел и строк.
Пользовательские разделители записей.
Отбор необходимых столбцов по схеме, предоставленной в dtype.
Настройка представления значений NaN.
Подробности об этом вы найдёте в документации к C++-API libcudf (json_reader_options).
В материале GPU-Accelerated JSON Data Processing with RAPIDS вы можете найти дополнительные сведения о чтении данных из нескольких источников для организации эффективной обработки множества небольших файлов формата JSON Lines. Там же говорится и о поддержке байтовых диапазонов, что позволяет разбивать на части большие файлы JSON Lines.
Итоги
RAPIDS cuDF даёт нам мощные, гибкие и быстрые инструменты для работы с JSON-данными в Python.
Обработка JSON-данных с использованием видеоускорителей, кроме того, возможна при использовании RAPIDS Accelerator For Apache Spark (начиная с версии 24.12). Этому вопросу посвящён материал Accelerating JSON Processing on Apache Spark with GPUs.
Дополнительные сведения по нашей теме вы можете найти здесь:
GitHub-репозиторий /rapidsai/cudf
Docker-контейнеры RAPIDS (доступно для релизов и ночных билдов)
Учебный курс Accelerate Data Science Workflows with Zero Code Changes, проводимый Deep Learning Institute
Публикация Mastering the cudf.pandas Profiler for GPU Acceleration
О, а приходите к нам работать? 🤗 💰
Мы в wunderfund.io занимаемся высокочастотной алготорговлей с 2014 года. Высокочастотная торговля — это непрерывное соревнование лучших программистов и математиков всего мира. Присоединившись к нам, вы станете частью этой увлекательной схватки.
Мы предлагаем интересные и сложные задачи по анализу данных и low latency разработке для увлеченных исследователей и программистов. Гибкий график и никакой бюрократии, решения быстро принимаются и воплощаются в жизнь.
Сейчас мы ищем плюсовиков, питонистов, дата-инженеров и мл-рисерчеров.