Ecco la versione SQLAlchemy del tuo script MySQL che viene eseguito in quattro secondi, rispetto ai tre di MySQLdb:
from sqlalchemy import Integer, Column, create_engine, MetaData, Table
import datetime
metadata = MetaData()
foo = Table(
'foo', metadata,
Column('id', Integer, primary_key=True),
Column('a', Integer(), nullable=False),
Column('b', Integer(), nullable=False),
Column('c', Integer(), nullable=False),
)
class Foo(object):
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
engine = create_engine('mysql+mysqldb://scott:[email protected]/test', echo=True)
start = datetime.datetime.now()
with engine.connect() as conn:
foos = [
Foo(row['a'], row['b'], row['c'])
for row in
conn.execute(foo.select().limit(1000000)).fetchall()
]
print "total time: ", datetime.datetime.now() - start
tempo di esecuzione:
total time: 0:00:04.706010
Ecco uno script che utilizza l'ORM per caricare completamente le righe degli oggetti; evitando la creazione di un elenco fisso con tutti gli oggetti 1M contemporaneamente utilizzando yield per, questo viene eseguito in 13 secondi con SQLAlchemy master (18 secondi con rel 0.9):
import time
from sqlalchemy import Integer, Column, create_engine, Table
from sqlalchemy.orm import Session
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Foo(Base):
__table__ = Table(
'foo', Base.metadata,
Column('id', Integer, primary_key=True),
Column('a', Integer(), nullable=False),
Column('b', Integer(), nullable=False),
Column('c', Integer(), nullable=False),
)
engine = create_engine('mysql+mysqldb://scott:[email protected]/test', echo=True)
sess = Session(engine)
now = time.time()
# avoid using all() so that we don't have the overhead of building
# a large list of full objects in memory
for obj in sess.query(Foo).yield_per(100).limit(1000000):
pass
print("Total time: %d" % (time.time() - now))
Possiamo quindi dividere la differenza tra questi due approcci e caricare solo singole colonne con l'ORM:
for obj in sess.query(Foo.id, Foo.a, Foo.b, Foo.c).yield_per(100).limit(1000000):
pass
Quanto sopra viene eseguito nuovamente in 4 secondi .
Il confronto di SQLAlchemy Core è il confronto più appropriato con un cursore MySQLdb grezzo. Se utilizzi l'ORM ma esegui query per singole colonne, nelle versioni più recenti sono necessari circa quattro secondi.
A livello di ORM, i problemi di velocità sono dovuti al fatto che la creazione di oggetti in Python è lenta e SQLAlchemy ORM applica una grande quantità di contabilità a questi oggetti mentre li recupera, il che è necessario per adempiere al suo contratto di utilizzo, inclusa l'unità di lavoro, mappa identità, caricamento ansioso, raccolte, ecc.
Per velocizzare notevolmente la query, recupera le singole colonne anziché gli oggetti interi. Vedere le tecniche suhttp://docs .sqlalchemy.org/en/latest/faq/performance.html#result-fetching-slowness-orm che descrivono questo.
Per il tuo confronto con PeeWee, PW è un sistema molto più semplice con molte meno funzionalità, incluso il fatto che non fa nulla con le mappe di identità. Anche con PeeWee, il più semplice possibile di un ORM, ci vogliono ancora 15 secondi , che è la prova che cPython è davvero molto lento rispetto al recupero grezzo di MySQLdb che è in C diritto.
Per confronto con Java, Java VM è molto più veloce di cPython . L'ibernazione è ridicola complicato, ma Java VM è estremamente veloce grazie al JIT e anche tutta quella complessità finisce per funzionare più velocemente. Se vuoi confrontare Python con Java, usa Pypy.