msgspec: быстрый и экономичный парсинг JSON на Python

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

В библиотеке msgspec много функций, например кодирование, поддержка MessagePack (альтернативный формат, который быстрее JSON) и другие. Если вы регулярно парсите файлы JSON, и у вас проблемы с производительностью или памятью, или просто нужны встроенные схемы, то попробуйте msgspec.


Ниже рассказываем о библиотеке подробнее. Итак, чтобы обработать большой файл JSON на Python без сбоев и аварийного завершения, нужно:


  1. Убедиться, что используется не слишком много памяти.
  2. Спарсить файл как можно быстрее.
  3. В идеале также заранее убедиться, что данные валидны и имеют правильную структуру.

Конечно, можно объединить решения с несколькими библиотеками. А можно — всего с одной. Схемы, быстрый парсинг и хитрые приемы для уменьшения потребления памяти — все это новая библиотека msgspec.


json и orjson


Начнем с двух других библиотек: встроенного модуля json на Python и быстрой библиотеки orjson. Вернемся к примеру из моей статьи о потоковом парсинге JSON и спарсим файл размером ~25 Мб, в котором кодируется список объектов JSON (например, словарей). Это события GitHub и пользователи, выполняющие определенные действия с репозиториями:


[{"id":"2489651045","type":"CreateEvent","actor":{"id":665991,"login":"petroav","gravatar_id":"","url":"https://api.github.com/users/petroav","avatar_url":"https://avatars.githubusercontent.com/u/665991?"},"repo":{"id":28688495,"name":"petroav/6.828","url":"https://api.github.com/repos/petroav/6.828"},"payload":{"ref":"master","ref_type":"branch","master_branch":"master","description":"Solution to homework and assignments from MIT's 6.828 (Operating Systems Engineering). Done in my spare time.","pusher_type":"user"},"public":true,"created_at":"2015-01-01T15:00:00Z"},
...
]

Наша цель — выяснить, с какими репозиториями взаимодействовал пользователь.


Вот как это делается со встроенным модулем json стандартной библиотеки Python:


import json

with open("large.json", "r") as f:
    data = json.load(f)

user_to_repos = {}
for record in data:
    user = record["actor"]["login"]
    repo = record["repo"]["name"]
    if user not in user_to_repos:
        user_to_repos[user] = set()
    user_to_repos[user].add(repo)
print(len(user_to_repos), "records")

А вот так с orjson (отличается двумя строками):


import orjson

with open("large.json", "rb") as f:
    data = orjson.loads(f.read())

user_to_repos = {}
for record in data:
    # ... same as stdlib code ...

Вот сколько памяти и времени занимают эти два варианта:


$ /usr/bin/time -f "RAM: %M KB, Elapsed: %E" python stdlib.py 
5250 records
RAM: 136464 KB, Elapsed: 0:00.42
$ /usr/bin/time -f "RAM: %M KB, Elapsed: %E" python with_orjson.py 
5250 records
RAM: 113676 KB, Elapsed: 0:00.28

Потребление памяти одинаковое, но orjson быстрее — 280 мс против 420 мс.


Теперь рассмотрим msgspec.


msgspec: декодирование и кодирование на основе схемы для JSON


Вот соответствующий код с msgspec, здесь подход к парсингу несколько отличается:


from msgspec.json import decode
from msgspec import Struct

class Repo(Struct):
    name: str

class Actor(Struct):
    login: str

class Interaction(Struct):
    actor: Actor
    repo: Repo

with open("large.json", "rb") as f:
    data = decode(f.read(), type=list[Interaction])

user_to_repos = {}
for record in data:
    user = record.actor.login
    repo = record.repo.name
    if user not in user_to_repos:
        user_to_repos[user] = set()
    user_to_repos[user].add(repo)
print(len(user_to_repos), "records")

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


Очень полезно: схемы для всех полей не нужны. И, хотя в записях JSON много полей (смотрите в примере выше), мы указываем в msgspec только нужные нам.

Вот результат парсинга с msgspec:


$ /usr/bin/time -f "RAM: %M KB, Elapsed: %E" python with_msgspec.py 
5250 records
RAM: 38612 KB, Elapsed: 0:00.09

Намного быстрее и гораздо меньше памяти.


В итоге у нас три решения и еще одно потоковое — ijson:




Пакет Время ОЗУ Постоянная память Схема
Stdlib json 420 мс 136 Мб
orjson 280 мс 114 Мб
ijson 300 мс 14 Мб
msgspec 90 мс 39 Мб



При потоковом решении для парсинга всегда используется постоянный объём памяти. В остальных решениях потребление памяти зависит от размера входных данных. У msgspec потребление памяти значительно меньше, и это решение намного быстрее.


Плюсы и минусы парсинга со схемой


В msgspec, указав схему, можно создавать объекты Python только для нужных нам полей. То есть потребление оперативной памяти меньше, а декодирование быстрее. Не нужно тратить время или память на создание тысяч бесполезных объектов Python.


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


С другой стороны:


  • Потребление памяти при декодировании по-прежнему зависит от входного файла. А в потоковых парсерах JSON, таких как ijson, можно использовать постоянную память во время парсинга, каким бы большим ни был входной файл.
  • Указание схемы подразумевает написание большего объёма кода и меньшую гибкость при работе с неполными данными.


  • Профессия Data Scientist
  • Профессия Fullstack-разработчик на Python (16 месяцев)

Краткий каталог курсов

Data Science и Machine Learning


  • Профессия Data Scientist
  • Профессия Data Analyst
  • Курс «Математика для Data Science»
  • Курс «Математика и Machine Learning для Data Science»
  • Курс по Data Engineering
  • Курс «Machine Learning и Deep Learning»
  • Курс по Machine Learning

Python, веб-разработка


  • Профессия Fullstack-разработчик на Python
  • Курс «Python для веб-разработки»
  • Профессия Frontend-разработчик
  • Профессия Веб-разработчик

Мобильная разработка


  • Профессия iOS-разработчик
  • Профессия Android-разработчик

Java и C#


  • Профессия Java-разработчик
  • Профессия QA-инженер на JAVA
  • Профессия C#-разработчик
  • Профессия Разработчик игр на Unity

От основ — в глубину


  • Курс «Алгоритмы и структуры данных»
  • Профессия C++ разработчик
  • Профессия «Белый хакер»

А также


  • Курс по DevOps
  • Все курсы
Источник: https://habr.com/ru/company/skillfactory/blog/725972/


Интересные статьи

Интересные статьи

К сожалению, в python-мире до сих пор повсеместно применяется неизолированный запуск приложения и его инфраструктуры на личных устройствах. Боюсь, даже опытные специалисты неохотно используют контейне...
Когда речь заходит о тяжелой промышленности и технологиях в ней, в большинстве случаев мы ожидаем услышать Java, а может быть и Java EE, или наоборот что-то очень низкоуровневое. Именно такие предполо...
Специально к старту нового потока курса «Python для веб-разработки» представляем подборку из 57 репозиториев, которые будут полезны как начинающему, так и опытному разработчику: это репоз...
Уже прошло почти восемь месяцев 2020 года, а технические прогнозы на этот год всё выходят и выходят. И это — несмотря на то, что очень сложно предсказать будущее в такой динамично раз...