再びORMのパフォーマンス、または新しい有望なプロジェクト-Pony ORM

Habrahabrに関する最初の記事で、既存のORMの主な問題(オブジェクトリレーショナルマッピング、オブジェクトリレーショナルマッピング)の1つ、つまりパフォーマンスについて書いた。 python、Django、およびSQLAlchemyで最も有名でよく知られている2つのORM実装を検討してテストした結果、 強力なユニバーサル ORM を使用するとパフォーマンスが著しく低下するという結論に達しました MySQLなどの高速DBMSエンジンを使用する場合、データアクセスのパフォーマンスは3〜5倍以上低下します



最近、ポニーと呼ばれる新しいORMエンジンの開発者の1人が私に連絡し、このエンジンに関する私の考えを共有するように頼みました。 これらの考慮事項は、Habrahabrコミュニティにとって興味深いかもしれないと思いました。





簡単な要約





前回の記事で説明したものと同様のパフォーマンステストを再度実行し、それらの結果をpony ORMが示す結果と比較しました。 キャッシュされたパラメーター化された要求の条件下でパフォーマンスを測定するには、オブジェクトを受信するためのテストを変更して、すべての新しい要求が新しいキーを持つオブジェクトを受信するようにしました。



結果:ポニーORMは、オブジェクトキャッシュがなくても、djangoとSQLAlchemyの最高の結果を1.5〜3倍上回っています。



ポニーが良くなった理由





私はすぐに認めなければなりません:ポニーORMに通常の手段で同等の条件でdjangoとSQLAlchemyを装備することはできませんでした。 これは、djangoで構築された特定のクエリのみをキャッシュでき、SQLAlchemyで準備されたパラメータ化されたクエリ(簡単な努力ではない)で、ポニーORM が可能なすべてをキャッシュするためです 。 テキストポニーORMを斜めに表示:キャッシュ



-SQLクエリ固有のDBMSの完成したテキスト

-テキストからコンパイルされたリクエストの構造

-放送関係

-接続

-作成されたオブジェクト

-オブジェクトの読み取りと変更

-遅延読み取りの要求

-オブジェクトの作成、更新、削除のリクエスト

-オブジェクトを検索するクエリ

-ロックリクエスト

-関係とその変更をナビゲートするためのクエリ

-多分私が逃した何か



このようなキャッシングにより、これを使用するコードを可能な限り高速で実行できます。これは、以前の記事の1つでここで説明したような、パフォーマンスの向上に関する難しいトリックを心配することなく実行できます。



もちろん、 キャッシングは時々不便をもたらします。 たとえば、オブジェクトをキャッシュしても、メモリ内のオブジェクトの状態とテーブル内の画像を簡単に比較することはできません。これは、異なるプロセスで競争的に処理されるデータを正しく処理するために必要な場合があります。 ポニーのリリースの1つでは、コードの特定の種類のキャッシュをオプションで無効にできるパラメーターを確認したいと思います



願い





他のORMと完全に比較できるように、ポニーORMには何が欠けていますか?



-データ移行-ORMを使用する大規模プロジェクトに絶対に必要な手順

-MS SQLなどの一般的なDBMSへのアダプター

-コード内のさまざまなDBMSからの完全な抽象化

-オブジェクトの完全なメタデータへのアクセス

-フィールドタイプのカスタマイズ

-完全なドキュメント



ポニーORMで具体化できる現代のORMでは、このプロジェクトがまだ停滞状態になっていないのに、私は何が欠けていますか?



-混合フィルターの使用(フィルター内のオブジェクトのフィールドとメソッドへの同時アクセス)

-計算可能なフィールドとそれらのインデックス

-複合フィールド(テーブルのいくつかのフィールドに格納)

-ネストされたオブジェクトのフィールド(通常のPythonオブジェクトを表すフィールド)

-異なるデータベースのオブジェクトをリンクする



そしてもちろん、データベースへの効果的なアクセスの基盤としてポニーORMを使用するアプリケーションを作成するための全体的なフレームワークを見てみたいと思います。



更新2013-08-03





Pony ORM作成者から質問への回答を受け取りたい場合は、alexander.kozlovsky @ gmail.comおよびm.alexey@gmail.comに連絡してください。 招待は大歓迎です。



用途





試験結果




>>> 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.
      
      






All Articles