Недавно со мной связался один из разработчиков нового движка ORM под названием pony и попросил поделиться своими соображениями по поводу этого движка. Я подумал, что эти соображения могут быть интересны и сообществу Хабрахабр.
Краткое резюме
Я вновь провел некоторые тесты производительности, сходные с описанными в предыдущей статье, и сравнил их результаты с результатами, показанными pony ORM. Для измерения производительности в условиях кешированного параметризованного запроса, мне пришлось видоизменить тест получения объекта так, чтобы каждый новый запрос получал объект с новым ключом.
Результат: pony ORM превосходит лучшие результаты django и SQLAlchemy в 1.5-3 раза, даже без кеширования объектов.
Почему pony оказался лучше
Сразу признаюсь: мне не удалось штатными средствами поставить в равные условия pony ORM с django и SQLAlchemy. Произошло это потому, что если в django можно кешировать только сконструированные конкретные запросы, а в SQLAlchemy — подготовленные параметризованные запросы (при некоторых нетривиальных усилиях), то pony ORM кеширует все, что только можно. Просмотр текста pony ORM по диагонали показал: кешируются
— готовый текст запроса SQL конкретной СУБД
— структура компилированного из текста запроса
— трансляция отношений
— соединения
— создаваемые объекты
— прочитанные и измененные объекты
— запросы для отложенного чтения
— запросы для создания, обновления и удаления объектов
— запросы для поиска объектов
— запросы блокировки
— запросы для навигации по отношениям и их модификации
— может быть еще что-то, что я пропустил
Такое кеширование позволяет выполнять код, который его использует, настолько быстро, насколько это только вообще возможно, не озабочиваясь при этом хитрыми выкрутасами по поводу повышения производительности наподобие того, который я с отчаяния придумал и описал здесь в одной из своих предыдущих статей.
Конечно, кеширование иногда приносит некоторые неудобства. Например, кеширование объектов не позволяет легко сравнить имеющееся в памяти состояние объекта с его образом в таблице — это иногда нужно для корректной обработки данных, конкурентно обрабатываемых в разных процессах. В одном из релизов pony мне лично хотелось бы увидеть параметры, позволяющие по желанию отключать отдельные виды кеширования для участка кода.
Пожелания
Чего мне пока не хватает в pony ORM, чтобы полноценно сравнить ее с другими ORM?
— миграция данных — совершенно необходимая процедура для больших проектов, использующих ORM
— адаптеры к некоторым популярным СУБД, например MS SQL
— полное абстрагирование от разновидности СУБД в коде
— доступ к полным метаданным объекта
— кастомизация типов полей
— полная документация
Чего мне не хватает в современных ORM, что можно было бы воплотить в pony ORM, пока этот проект еще не разросся до состояния стагнации?
— использование смешанных фильтров (обращение к полям и методам объекта одновременно в фильтре)
— вычислимые поля и индексы по ним
— композитные поля (хранимые в нескольких полях таблицы)
— поле вложенного объекта (поле, представляющее собой обычный объект python)
— связывание объектов из разных БД
Ну и конечно, хотелось бы видеть целостный framework для создания приложений, использующий pony ORM как основу для эффективного доступа к БД.
Приложения
Результаты проведенных тестов
>>> import test_native >>> test_native.test_native() get row by key: native req/seq: 3050.80815908 req time (ms): 0.327782 get value by key: native req/seq: 4956.05711955 req time (ms): 0.2017733
>>> import test_django >>> test_django.test_django() get object by key: django req/seq: 587.58369836 req time (ms): 1.7018852 get value by key: django req/seq: 779.4622303 req time (ms): 1.2829358
>>> import test_alchemy >>> test_alchemy.test_alchemy() get object by key: alchemy req/seq: 317.002465265 req time (ms): 3.1545496 get value by key: alchemy req/seq: 1827.75593609 req time (ms): 0.547119
>>> import test_pony >>> test_pony.test_pony() get object by key: pony req/seq: 1571.18299553 req time (ms): 0.6364631 get value by key: pony req/seq: 2916.85249448 req time (ms): 0.3428353
Код тестов
test_native.py
import datetime def test_native(): from django.db import connection, transaction cursor = connection.cursor() t1 = datetime.datetime.now() for i in range(10000): cursor.execute("select username,first_name,last_name,email,password,is_staff,is_active,is_superuser,last_login,date_joined from auth_user where id=%s limit 1" % (i+1)) f = cursor.fetchone() u = f[0] t2 = datetime.datetime.now() print "get row by key: native req/seq:",10000/(t2-t1).total_seconds(),'req time (ms):',(t2-t1).total_seconds()/10. t1 = datetime.datetime.now() for i in range(10000): cursor.execute("select username from auth_user where id=%s limit 1" % (i+1)) f = cursor.fetchone() u = f[0][0] t2 = datetime.datetime.now() print "get value by key: native req/seq:",10000/(t2-t1).total_seconds(),'req time (ms):',(t2-t1).total_seconds()/10.
test_django.py
import datetime from django.contrib.auth.models import User def test_django(): t1 = datetime.datetime.now() q = User.objects.all() for i in range(10000): u = q.get(id=i+1) t2 = datetime.datetime.now() print "get object by key: django req/seq:",10000/(t2-t1).total_seconds(),'req time (ms):',(t2-t1).total_seconds()/10. t1 = datetime.datetime.now() q = User.objects.all().values('username') for i in range(10000): u = q.get(id=i+1)['username'] t2 = datetime.datetime.now() print "get value by key: django req/seq:",10000/(t2-t1).total_seconds(),'req time (ms):',(t2-t1).total_seconds()/10.
test_alchemy.py
import datetime from sqlalchemy import * from sqlalchemy.orm.session import Session as ASession from sqlalchemy.ext.declarative import declarative_base query_cache = {} engine = create_engine('mysql://testorm:testorm@127.0.0.1/testorm', execution_options={'compiled_cache':query_cache}) session = ASession(bind=engine) Base = declarative_base(engine) class AUser(Base): __tablename__ = 'auth_user' id = Column(Integer, primary_key=True) username = Column(String(50)) password = Column(String(128)) last_login = Column(DateTime()) first_name = Column(String(30)) last_name = Column(String(30)) email = Column(String(30)) is_staff = Column(Boolean()) is_active = Column(Boolean()) date_joined = Column(DateTime()) def test_alchemy(): t1 = datetime.datetime.now() for i in range(10000): u = session.query(AUser).filter(AUser.id==i+1)[0] t2 = datetime.datetime.now() print "get object by key: alchemy req/seq:",10000/(t2-t1).total_seconds(),'req time (ms):',(t2-t1).total_seconds()/10. table = AUser.__table__ sel = select(['username'],from_obj=table,limit=1,whereclause=table.c.id==bindparam('ident')) t1 = datetime.datetime.now() for i in range(10000): u = sel.execute(ident=i+1).first()['username'] t2 = datetime.datetime.now() print "get value by key: alchemy req/seq:",10000/(t2-t1).total_seconds(),'req time (ms):',(t2-t1).total_seconds()/10.
test_pony.py
import datetime from datetime import date, time from pony import * from pony.orm import * db = Database('mysql', db='testorm', user='testorm', passwd='testorm') class PUser(db.Entity): _table_ = 'auth_user' id = PrimaryKey(int, auto=True) username = Required(str) password = Optional(str) last_login = Required(date) first_name = Optional(str) last_name = Optional(str) email = Optional(str) is_staff = Optional(bool) is_active = Optional(bool) date_joined = Optional(date) db.generate_mapping(create_tables=False) def test_pony(): t1 = datetime.datetime.now() with db_session: for i in range(10000): u = select(u for u in PUser if u.id==i+1)[:1][0] t2 = datetime.datetime.now() print "get object by key: pony req/seq:",10000/(t2-t1).total_seconds(),'req time (ms):',(t2-t1).total_seconds()/10. t1 = datetime.datetime.now() with db_session: for i in range(10000): u = select(u.username for u in PUser if u.id==i+1)[:1][0] t2 = datetime.datetime.now() print "get value by key: pony req/seq:",10000/(t2-t1).total_seconds(),'req time (ms):',(t2-t1).total_seconds()/10.
ссылка на оригинал статьи http://habrahabr.ru/post/188842/
Добавить комментарий