From: Mike Bayer Date: Fri, 2 Nov 2018 20:40:59 +0000 (-0400) Subject: Deannotate ORM columns in ColumnEntity X-Git-Tag: rel_1_3_0b1~25^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=88bfa1b89c5b3b2290fa266c53322f003833af40;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Deannotate ORM columns in ColumnEntity Fixed a minor performance issue which could in some cases add unnecessary overhead to result fetching, involving the use of ORM columns and entities that include those same columns at the same time within a query. The issue has to do with hash / eq overhead when referring to the column in different ways. Fixes: #4347 Change-Id: I191d4d1b1623898060a9accdfd186de16f89a6b7 --- diff --git a/doc/build/changelog/unreleased_12/4347.rst b/doc/build/changelog/unreleased_12/4347.rst new file mode 100644 index 0000000000..c24f87b0d8 --- /dev/null +++ b/doc/build/changelog/unreleased_12/4347.rst @@ -0,0 +1,9 @@ +.. change:: + :tags: bug, orm + :tickets: 4347 + + Fixed a minor performance issue which could in some cases add unnecessary + overhead to result fetching, involving the use of ORM columns and entities + that include those same columns at the same time within a query. The issue + has to do with hash / eq overhead when referring to the column in different + ways. diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index bfddb5cfec..cace2e54af 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -4243,6 +4243,11 @@ class _ColumnEntity(_QueryEntity): else: column = query._adapt_clause(self.column, False, True) + if column._annotations: + # annotated columns perform more slowly in compiler and + # result due to the __eq__() method, so use deannotated + column = column._deannotate() + if context.adapter: column = context.adapter.columns[column] @@ -4251,6 +4256,12 @@ class _ColumnEntity(_QueryEntity): def setup_context(self, query, context): column = query._adapt_clause(self.column, False, True) + + if column._annotations: + # annotated columns perform more slowly in compiler and + # result due to the __eq__() method, so use deannotated + column = column._deannotate() + context.froms += tuple(self.froms) context.primary_columns.append(column) diff --git a/test/aaa_profiling/test_orm.py b/test/aaa_profiling/test_orm.py index ae0dd657d4..18434b9a4e 100644 --- a/test/aaa_profiling/test_orm.py +++ b/test/aaa_profiling/test_orm.py @@ -1,13 +1,14 @@ from sqlalchemy import Integer, String, ForeignKey from sqlalchemy.orm import mapper, relationship, \ sessionmaker, Session, defer, joinedload, defaultload, selectinload, \ - Load, configure_mappers + Load, configure_mappers, Bundle from sqlalchemy import testing from sqlalchemy.testing import profiling from sqlalchemy.testing import fixtures from sqlalchemy.testing.schema import Table, Column from sqlalchemy import inspect + class MergeTest(fixtures.MappedTest): @classmethod @@ -954,3 +955,149 @@ class BranchedOptionTest(fixtures.MappedTest): q.options(*opts) go() + +class AnnotatedOverheadTest(fixtures.MappedTest): + @classmethod + def define_tables(cls, metadata): + Table( + 'a', + metadata, + Column('id', Integer, primary_key=True), + Column('data', String(50)) + ) + + @classmethod + def setup_classes(cls): + class A(cls.Basic): + pass + + @classmethod + def setup_mappers(cls): + A = cls.classes.A + a = cls.tables.a + + mapper(A, a) + + @classmethod + def insert_data(cls): + A = cls.classes.A + s = Session() + s.add_all([A(data='asdf') for i in range(5)]) + s.commit() + + def test_no_bundle(self): + A = self.classes.A + s = Session() + + q = s.query(A).select_from(A) + + @profiling.function_call_count() + def go(): + for i in range(100): + q.all() + go() + + def test_no_entity_wo_annotations(self): + A = self.classes.A + a = self.tables.a + s = Session() + + q = s.query(a.c.data).select_from(A) + + @profiling.function_call_count() + def go(): + for i in range(100): + q.all() + go() + + def test_no_entity_w_annotations(self): + A = self.classes.A + s = Session() + q = s.query(A.data).select_from(A) + + @profiling.function_call_count() + def go(): + for i in range(100): + q.all() + go() + + def test_entity_w_annotations(self): + A = self.classes.A + s = Session() + q = s.query( + A, A.data + ).select_from(A) + + @profiling.function_call_count() + def go(): + for i in range(100): + q.all() + go() + + def test_entity_wo_annotations(self): + A = self.classes.A + a = self.tables.a + s = Session() + q = s.query( + A, a.c.data + ).select_from(A) + + @profiling.function_call_count() + def go(): + for i in range(100): + q.all() + go() + + def test_no_bundle_wo_annotations(self): + A = self.classes.A + a = self.tables.a + s = Session() + q = s.query( + a.c.data, A + ).select_from(A) + + @profiling.function_call_count() + def go(): + for i in range(100): + q.all() + go() + + def test_no_bundle_w_annotations(self): + A = self.classes.A + s = Session() + q = s.query( + A.data, A + ).select_from(A) + + @profiling.function_call_count() + def go(): + for i in range(100): + q.all() + go() + + def test_bundle_wo_annotation(self): + A = self.classes.A + a = self.tables.a + s = Session() + q = s.query( + Bundle("ASdf", a.c.data), A + ).select_from(A) + + @profiling.function_call_count() + def go(): + for i in range(100): + q.all() + go() + + def test_bundle_w_annotation(self): + A = self.classes.A + s = Session() + q = s.query( + Bundle("ASdf", A.data), A + ).select_from(A) + + @profiling.function_call_count() + def go(): + for i in range(100): + q.all() + go() diff --git a/test/profiles.txt b/test/profiles.txt index ae7b5746ab..ef13151bcf 100644 --- a/test/profiles.txt +++ b/test/profiles.txt @@ -1,15 +1,15 @@ # /home/classic/dev/sqlalchemy/test/profiles.txt # This file is written out on a per-environment basis. -# For each test in aaa_profiling, the corresponding function and +# For each test in aaa_profiling, the corresponding function and # environment is located within this file. If it doesn't exist, # the test is skipped. -# If a callcount does exist, it is compared to what we received. +# If a callcount does exist, it is compared to what we received. # assertions are raised if the counts do not match. -# -# To add a new callcount test, apply the function_call_count -# decorator and re-run the tests using the --write-profiles +# +# To add a new callcount test, apply the function_call_count +# decorator and re-run the tests using the --write-profiles # option - this file will be rewritten including the new count. -# +# # TEST: test.aaa_profiling.test_compiler.CompileTest.test_insert @@ -196,6 +196,69 @@ test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 3.7_postgre test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 3.7_sqlite_pysqlite_dbapiunicode_cextensions 158 test.aaa_profiling.test_compiler.CompileTest.test_update_whereclause 3.7_sqlite_pysqlite_dbapiunicode_nocextensions 158 +# TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation + +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation 2.7_sqlite_pysqlite_dbapiunicode_cextensions 48456,48400 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 51456,51400 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation 3.7_sqlite_pysqlite_dbapiunicode_cextensions 50867,50800 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_w_annotation 3.7_sqlite_pysqlite_dbapiunicode_nocextensions 54067,54000 + +# TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation + +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation 2.7_sqlite_pysqlite_dbapiunicode_cextensions 48480,48480 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 51480,51480 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation 3.7_sqlite_pysqlite_dbapiunicode_cextensions 50880,50880 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_bundle_wo_annotation 3.7_sqlite_pysqlite_dbapiunicode_nocextensions 54080,54080 + +# TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations + +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 46205,46200 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 49205,49200 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations 3.7_sqlite_pysqlite_dbapiunicode_cextensions 48008,48000 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_w_annotations 3.7_sqlite_pysqlite_dbapiunicode_nocextensions 51208,51200 + +# TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations + +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 46280,46280 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 49280,49280 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations 3.7_sqlite_pysqlite_dbapiunicode_cextensions 48080,48080 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_entity_wo_annotations 3.7_sqlite_pysqlite_dbapiunicode_nocextensions 51280,51280 + +# TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle + +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle 2.7_sqlite_pysqlite_dbapiunicode_cextensions 38500,38500 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 41000,41000 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle 3.7_sqlite_pysqlite_dbapiunicode_cextensions 41200,41200 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle 3.7_sqlite_pysqlite_dbapiunicode_nocextensions 43900,43900 + +# TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations + +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 46205,46200 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 49205,49200 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations 3.7_sqlite_pysqlite_dbapiunicode_cextensions 48008,48000 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_w_annotations 3.7_sqlite_pysqlite_dbapiunicode_nocextensions 51208,51200 + +# TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations + +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 46280,46280 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 49280,49280 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations 3.7_sqlite_pysqlite_dbapiunicode_cextensions 48080,48080 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_bundle_wo_annotations 3.7_sqlite_pysqlite_dbapiunicode_nocextensions 51280,51280 + +# TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations + +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 25437,25437 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 26937,26937 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations 3.7_sqlite_pysqlite_dbapiunicode_cextensions 27535,27535 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_w_annotations 3.7_sqlite_pysqlite_dbapiunicode_nocextensions 29235,29235 + +# TEST: test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations + +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_cextensions 25517,25517 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 27017,27017 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations 3.7_sqlite_pysqlite_dbapiunicode_cextensions 27615,27615 +test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations 3.7_sqlite_pysqlite_dbapiunicode_nocextensions 29315,29315 + # TEST: test.aaa_profiling.test_orm.AttributeOverheadTest.test_attribute_set test.aaa_profiling.test_orm.AttributeOverheadTest.test_attribute_set 2.7_mssql_pyodbc_dbapiunicode_cextensions 4256 @@ -475,7 +538,6 @@ test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.7_postgresql_ test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.7_sqlite_pysqlite_dbapiunicode_cextensions 455708 test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_build_query 3.7_sqlite_pysqlite_dbapiunicode_nocextensions 455708 - # TEST: test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results test.aaa_profiling.test_orm.JoinedEagerLoadTest.test_fetch_results 2.7_mssql_pyodbc_dbapiunicode_cextensions 466544