반응형
바닐라 json 패키지는 느리다. orjson이나 ujson을 사용하자.
json
- JavaScript Object Notation(자바 스크립트 객체 표기법)의 축약어로 경량의 데이터 교환 형식이다.
- JSON은 사람과 기계 모두 이해하기 쉬우며 용량이 작으며 텍스트 기반이기 때문에 다양한 프로그래밍 언어에서 데이터를 읽고 사용할 수 있다.
- 단순히 데이터를 표시하는 표현 방법이다. 원래는 JavaScript 언어로부터 파생되었지만, 현p재는 많은 프로그래밍 언어에서 사용되며, 특히 웹 애플리케이션에서 데이터를 전송할 때 많이 사용된다.
{
"company": "Tech Solutions",
"founded": 2010,
"employees": [
{
"firstName": "John",
"lastName": "Doe",
"age": 30,
"address": {
"street": "123 Main St",
"city": "Somewhere",
"zip": "12345"
},
"roles": ["developer", "team lead"]
},
{
"firstName": "Jane",
"lastName": "Smith",
"age": 28,
"address": {
"street": "456 Elm St",
"city": "Anywhere",
"zip": "67890"
},
"roles": ["developer"]
}
],
"locations": ["New York", "San Francisco"],
"isListed": true
}
Python의 내장 json 패키지는 Json문자열을 python dict와 list로 seamless하게 바꿔주기 때문에 아주 편리하다.
orjson, ujson
이 json도 상당한 병목이 될 수 있습니다. Python의 내장 json 패키지는 pure python으로 구현되어 있고, 크고 깊은 json 데이터를 다룰 때 예상보다 오랜 시간이 소요될 수 있습니다.
- orjson, ujson : 핵심 로직이 Rust나 C로 구현되어 있고, 큰 사이즈의 데이터를 다루는데 적합하도록 최적화되어 있어 내장 패키지보다 처리 속도가 훨씬 빠르다.
orjson
- Rust 언어로 구현되었으며, Python의 기본 json 패키지보다 빠른 성능을 제공한다.
- 다른 라이브러리에 비해 dataclass 인스턴스를 40-50배 빠르게 직렬화한다.
- 직렬화 : 데이터 구조나 객체 상태를 저장하거나 전송 가능한 형식으로 변환하는 과정
- 표준 라이브러리에 비해 10배에서 20배 빠르게 예쁘게 출력한다.
- str, int, list, dict의 하위 클래스를 기본적으로 직렬화하며, 다른 것들을 어떻게 직렬화할지 지정하기 위해 default가 필요하다.
- 파일과 같은 객체에서 읽거나 쓰기 위한 dump() 또는 load() 함수를 제공하지 않는다.
- dump() : Python 객체를 JSON 문자열로 직렬화(serialize)하고, 파일에 저장한다.
- load() : 파일 객체로부터 JSON 데이터를 읽어와서 Python 객체로 역직렬화
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# Person 객체를 JSON으로 직렬화하기 위한 함수
def person_encoder(obj):
if isinstance(obj, Person):
return {"name": obj.name, "age": obj.age}
raise TypeError("Type not serializable")
# Person 객체 생성
person = Person("John", 30)
# orjson으로 직렬화
json_str = orjson.dumps(person, default=person_encoder)
# json의 경우
data = {"name": "John", "age": 30}
with open('data.json', 'w') as file:
json.dump(data, file)
with open('data.json', 'r') as file:
data = json.load(file)
# orjson의 경우
data = {"name": "John", "age": 30}
with open('data.json', 'wb') as file:
file.write(orjson.dumps(data))
with open('data.json', 'rb') as file:
data = orjson.loads(file.read())
- str 대신 bytes로 직렬화하기 때문에 완벽한 대체품이 아니다.
import json data = {"name": "John", "age": 30} serialized_data = json.dumps(data) print(type(serialized_data)) # <class 'str'> serialized_data = orjson.dumps(data) print(type(serialized_data)) # <class 'bytes'>
ujson
- CPython 해당 모듈의 일부를 구현하며, JSON 인코딩 및 디코딩을 위한 모듈이다.
ujson.dump(obj, stream)
- obj를 JSON 문자열로 직렬화(변환)하고, 주어진 스트림(stream)에 그 결과를 쓰는 함수
ujson.dumps(obj)
- obj를 JSON 문자열로 표현하고 그 결과 문자열을 반환하는 함수
ujson.load(stream):
- 주어진 스트림(stream)을 JSON 문자열로 간주하고, 그 내용을 파이썬 객체로 역직렬화(변환)하는 함수
ujson.loads(str)
- JSON 문자열 str을 파싱하여 파이썬 객체로 반환하는 함수
json, orjson, ujson
서로 다른 json 라이브러리에서 1000개의 property를 가지는 json string을 100번 파싱하는 예시
import json
import random
import timeit
import ujson
import orjson
sample_dict = {}
for i in range(1000):
sample_dict[f"feature{i}"] = random.random()
json_string = json.dumps(sample_dict)
def test_json():
return json.loads(json_string)
def test_ujson():
return ujson.loads(json_string)
def test_orjson():
return orjson.loads(json_string)
num_runs = 100
json_time = timeit.timeit(test_json, number=num_runs)
ujson_time = timeit.timeit(test_ujson, number=num_runs)
orjson_time = timeit.timeit(test_orjson, number=num_runs)
print(f'json: {json_time * 1000:.1f} ms')
print(f'ujson: {ujson_time * 1000:.1f} ms')
print(f'orjson: {orjson_time * 1000:.1f} ms')
json: 36.5 ms
ujson: 14.4 ms
orjson: 8.6 ms
json: 24.5 ms
ujson: 13.5 ms
orjson: 9.3 ms
내장 json 패키지와 비교했을 때, ujson의 경우 2배 이상, orjson의 경우 4배 이상 속도가 차이나는 것을 알 수 있습니다. 만약 json 파싱을 heavy하게 해야 하는 상황이라면, 라이브러리를 바꾸는 것만으로도 유의미한 성능 향상을 이룰 수 있을 것입니다.
하이퍼커넥트의 ML 모델 서빙 서버 중에는, 다량의 feature 데이터를 json 형태로 입력받는 것들이 있었습니다. 서버에서 입력받는 json 데이터는 대부분 다량의 피쳐가 포함된, 큰 사이즈가 많았습니다. 해당 서버들에서 바닐라 json 패키지에서 orjson으로 바꾼 후, p99 latency가 5~10% 정도 개선되는 경험을 했습니다.
반응형