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 года, а технические прогнозы на этот год всё выходят и выходят. И это — несмотря на то, что очень сложно предсказать будущее в такой динамично раз...