mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-16 05:37:16 -04:00
e6cd36fc51
These are small bits where cache_anon_map in particular
is part of the cache key generation scheme which is a key
target for cython.
changing such a tiny element of the cache key gen is
doing basically nothing yet, as the cython impl is
mostly the exact same speed as the python one. I guess for
cython to be effective we'd need to redo the whole cache key
generation and possibly not use the same kinds of structures,
which might not be very easy to do.
Additionally, some cython runtime import errors are being
observed on jenkins, add an upfront check to the test suite
to indicate if the expected build succeeded when REQUIRE_SQLALCHEMY_CEXT
is set.
Running case CacheAnonMap
Running python .... Done
Running cython .... Done
| python | cython | cy / py |
test_get_anon_non_present| 0.301266758 | 0.231203834 | 0.767438915 |
test_get_anon_present| 0.300919362 | 0.227336695 | 0.755473803 |
test_has_key_non_present| 0.152725077 | 0.133191719 | 0.872101171 |
test_has_key_present| 0.152689778 | 0.133673095 | 0.875455428 |
Running case PrefixAnonMap
Running python .. Done
Running cython .. Done
| python | cython | cy / py |
test_apply_non_present| 0.358715744 | 0.335245703 | 0.934572034 |
test_apply_present | 0.354434996 | 0.338579782 | 0.955266229 |
Change-Id: I0d3f1dd285c044afc234479141d831b2ee0455be
1154 lines
28 KiB
Python
1154 lines
28 KiB
Python
from collections import defaultdict
|
|
from decimal import Decimal
|
|
import re
|
|
from secrets import token_urlsafe
|
|
from textwrap import wrap
|
|
from timeit import timeit
|
|
from types import MappingProxyType
|
|
|
|
|
|
def test_case(fn):
|
|
fn.__test_case__ = True
|
|
return fn
|
|
|
|
|
|
class Case:
|
|
"""Base test case. Mark test cases with ``test_case``"""
|
|
|
|
IMPLEMENTATIONS = {}
|
|
"Keys are the impl name, values are callable to load it"
|
|
NUMBER = 1_000_000
|
|
|
|
_CASES = []
|
|
|
|
def __init__(self, impl):
|
|
self.impl = impl
|
|
self.init_objects()
|
|
|
|
def __init_subclass__(cls):
|
|
if not cls.__name__.startswith("_"):
|
|
Case._CASES.append(cls)
|
|
|
|
def init_objects(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def _load(cls, fn):
|
|
try:
|
|
return fn()
|
|
except Exception as e:
|
|
print(f"Error loading {fn}: {e}")
|
|
|
|
@classmethod
|
|
def import_object(cls):
|
|
impl = []
|
|
for name, fn in cls.IMPLEMENTATIONS.items():
|
|
obj = cls._load(fn)
|
|
if obj:
|
|
impl.append((name, obj))
|
|
return impl
|
|
|
|
@classmethod
|
|
def _divide_results(cls, results, num, div, name):
|
|
"utility method to create ratios of two implementation"
|
|
if div in results and num in results:
|
|
results[name] = {
|
|
m: results[num][m] / results[div][m] for m in results[div]
|
|
}
|
|
|
|
@classmethod
|
|
def update_results(cls, results):
|
|
pass
|
|
|
|
@classmethod
|
|
def run_case(cls, factor, filter_):
|
|
objects = cls.import_object()
|
|
number = max(1, int(cls.NUMBER * factor))
|
|
|
|
stack = [c for c in cls.mro() if c not in {object, Case}]
|
|
methods = []
|
|
while stack:
|
|
curr = stack.pop(0)
|
|
# dict keeps the definition order, dir is instead sorted
|
|
methods += [
|
|
m
|
|
for m, fn in curr.__dict__.items()
|
|
if hasattr(fn, "__test_case__")
|
|
]
|
|
|
|
if filter_:
|
|
methods = [m for m in methods if re.search(filter_, m)]
|
|
|
|
results = defaultdict(dict)
|
|
for name, impl in objects:
|
|
print(f"Running {name} ", end="", flush=True)
|
|
impl_case = cls(impl)
|
|
fails = []
|
|
for m in methods:
|
|
call = getattr(impl_case, m)
|
|
try:
|
|
value = timeit(call, number=number)
|
|
print(".", end="", flush=True)
|
|
except Exception as e:
|
|
fails.append(f"{name}::{m} error: {e}")
|
|
print("x", end="", flush=True)
|
|
value = float("nan")
|
|
|
|
results[name][m] = value
|
|
print(" Done")
|
|
for f in fails:
|
|
print("\t", f)
|
|
|
|
cls.update_results(results)
|
|
return results
|
|
|
|
|
|
class ImmutableDict(Case):
|
|
@staticmethod
|
|
def python():
|
|
from sqlalchemy.util._py_collections import immutabledict
|
|
|
|
return immutabledict
|
|
|
|
@staticmethod
|
|
def c():
|
|
from sqlalchemy.cimmutabledict import immutabledict
|
|
|
|
return immutabledict
|
|
|
|
@staticmethod
|
|
def cython():
|
|
from sqlalchemy.cyextension.immutabledict import immutabledict
|
|
|
|
return immutabledict
|
|
|
|
IMPLEMENTATIONS = {
|
|
"python": python.__func__,
|
|
"c": c.__func__,
|
|
"cython": cython.__func__,
|
|
}
|
|
|
|
def init_objects(self):
|
|
self.small = {"a": 5, "b": 4}
|
|
self.large = {f"k{i}": f"v{i}" for i in range(50)}
|
|
self.d1 = self.impl({"x": 5, "y": 4})
|
|
self.d2 = self.impl({f"key{i}": f"value{i}" for i in range(50)})
|
|
|
|
@classmethod
|
|
def update_results(cls, results):
|
|
cls._divide_results(results, "c", "python", "c / py")
|
|
cls._divide_results(results, "cython", "python", "cy / py")
|
|
cls._divide_results(results, "cython", "c", "cy / c")
|
|
|
|
@test_case
|
|
def init_empty(self):
|
|
self.impl()
|
|
|
|
@test_case
|
|
def init(self):
|
|
self.impl(self.small)
|
|
|
|
@test_case
|
|
def init_large(self):
|
|
self.impl(self.large)
|
|
|
|
@test_case
|
|
def len(self):
|
|
len(self.d1) + len(self.d2)
|
|
|
|
@test_case
|
|
def getitem(self):
|
|
self.d1["x"]
|
|
self.d2["key42"]
|
|
|
|
@test_case
|
|
def union(self):
|
|
self.d1.union(self.small)
|
|
|
|
@test_case
|
|
def union_large(self):
|
|
self.d2.union(self.large)
|
|
|
|
@test_case
|
|
def merge_with(self):
|
|
self.d1.merge_with(self.small)
|
|
|
|
@test_case
|
|
def merge_with_large(self):
|
|
self.d2.merge_with(self.large)
|
|
|
|
@test_case
|
|
def get(self):
|
|
self.d1.get("x")
|
|
self.d2.get("key42")
|
|
|
|
@test_case
|
|
def get_miss(self):
|
|
self.d1.get("xxx")
|
|
self.d2.get("xxx")
|
|
|
|
@test_case
|
|
def keys(self):
|
|
self.d1.keys()
|
|
self.d2.keys()
|
|
|
|
@test_case
|
|
def items(self):
|
|
self.d1.items()
|
|
self.d2.items()
|
|
|
|
@test_case
|
|
def values(self):
|
|
self.d1.values()
|
|
self.d2.values()
|
|
|
|
@test_case
|
|
def iter(self):
|
|
list(self.d1)
|
|
list(self.d2)
|
|
|
|
@test_case
|
|
def in_case(self):
|
|
"x" in self.d1
|
|
"key42" in self.d1
|
|
|
|
@test_case
|
|
def in_miss(self):
|
|
"xx" in self.d1
|
|
"xx" in self.d1
|
|
|
|
@test_case
|
|
def eq(self):
|
|
self.d1 == self.d1
|
|
self.d2 == self.d2
|
|
|
|
@test_case
|
|
def eq_dict(self):
|
|
self.d1 == dict(self.d1)
|
|
self.d2 == dict(self.d2)
|
|
|
|
@test_case
|
|
def eq_other(self):
|
|
self.d1 == self.d2
|
|
self.d1 == "foo"
|
|
|
|
@test_case
|
|
def ne(self):
|
|
self.d1 != self.d1
|
|
self.d2 != self.d2
|
|
|
|
@test_case
|
|
def ne_dict(self):
|
|
self.d1 != dict(self.d1)
|
|
self.d2 != dict(self.d2)
|
|
|
|
@test_case
|
|
def ne_other(self):
|
|
self.d1 != self.d2
|
|
self.d1 != "foo"
|
|
|
|
|
|
class Processor(Case):
|
|
@staticmethod
|
|
def python():
|
|
from sqlalchemy.engine import processors
|
|
|
|
return processors
|
|
|
|
@staticmethod
|
|
def c():
|
|
from sqlalchemy import cprocessors as mod
|
|
|
|
mod.to_decimal_processor_factory = (
|
|
lambda t, s: mod.DecimalResultProcessor(t, "%%.%df" % s).process
|
|
)
|
|
|
|
return mod
|
|
|
|
@staticmethod
|
|
def cython():
|
|
from sqlalchemy.cyextension import processors as mod
|
|
|
|
mod.to_decimal_processor_factory = (
|
|
lambda t, s: mod.DecimalResultProcessor(t, "%%.%df" % s).process
|
|
)
|
|
|
|
return mod
|
|
|
|
IMPLEMENTATIONS = {
|
|
"python": python.__func__,
|
|
"c": c.__func__,
|
|
"cython": cython.__func__,
|
|
}
|
|
NUMBER = 500_000
|
|
|
|
def init_objects(self):
|
|
self.to_dec = self.impl.to_decimal_processor_factory(Decimal, 10)
|
|
|
|
self.bytes = token_urlsafe(2048).encode()
|
|
self.text = token_urlsafe(2048)
|
|
|
|
@classmethod
|
|
def update_results(cls, results):
|
|
cls._divide_results(results, "c", "python", "c / py")
|
|
cls._divide_results(results, "cython", "python", "cy / py")
|
|
cls._divide_results(results, "cython", "c", "cy / c")
|
|
|
|
@test_case
|
|
def int_to_boolean(self):
|
|
self.impl.int_to_boolean(None)
|
|
self.impl.int_to_boolean(10)
|
|
self.impl.int_to_boolean(1)
|
|
self.impl.int_to_boolean(-10)
|
|
self.impl.int_to_boolean(0)
|
|
|
|
@test_case
|
|
def to_str(self):
|
|
self.impl.to_str(None)
|
|
self.impl.to_str(123)
|
|
self.impl.to_str(True)
|
|
self.impl.to_str(self)
|
|
|
|
@test_case
|
|
def to_float(self):
|
|
self.impl.to_float(None)
|
|
self.impl.to_float(123)
|
|
self.impl.to_float(True)
|
|
self.impl.to_float(42)
|
|
self.impl.to_float(0)
|
|
self.impl.to_float(42.0)
|
|
|
|
@test_case
|
|
def str_to_datetime(self):
|
|
self.impl.str_to_datetime(None)
|
|
self.impl.str_to_datetime("2020-01-01 20:10:34")
|
|
self.impl.str_to_datetime("2030-11-21 01:04:34.123456")
|
|
|
|
@test_case
|
|
def str_to_time(self):
|
|
self.impl.str_to_time(None)
|
|
self.impl.str_to_time("20:10:34")
|
|
self.impl.str_to_time("01:04:34.123456")
|
|
|
|
@test_case
|
|
def str_to_date(self):
|
|
self.impl.str_to_date(None)
|
|
self.impl.str_to_date("2020-01-01")
|
|
|
|
@test_case
|
|
def to_decimal(self):
|
|
self.to_dec(None) is None
|
|
self.to_dec(123.44)
|
|
self.to_dec(99)
|
|
self.to_dec(99)
|
|
|
|
|
|
class DistillParam(Case):
|
|
NUMBER = 2_000_000
|
|
|
|
@staticmethod
|
|
def python():
|
|
from sqlalchemy.engine import _py_util
|
|
|
|
return _py_util
|
|
|
|
@staticmethod
|
|
def cython():
|
|
from sqlalchemy.cyextension import util as mod
|
|
|
|
return mod
|
|
|
|
IMPLEMENTATIONS = {
|
|
"python": python.__func__,
|
|
"cython": cython.__func__,
|
|
}
|
|
|
|
def init_objects(self):
|
|
self.tup_tup = tuple(tuple(range(10)) for _ in range(100))
|
|
self.list_tup = list(self.tup_tup)
|
|
self.dict = {f"c{i}": i for i in range(100)}
|
|
self.mapping = MappingProxyType(self.dict)
|
|
self.tup_dic = (self.dict, self.dict)
|
|
self.list_dic = [self.dict, self.dict]
|
|
|
|
@classmethod
|
|
def update_results(cls, results):
|
|
cls._divide_results(results, "c", "python", "c / py")
|
|
cls._divide_results(results, "cython", "python", "cy / py")
|
|
cls._divide_results(results, "cython", "c", "cy / c")
|
|
|
|
@test_case
|
|
def none_20(self):
|
|
self.impl._distill_params_20(None)
|
|
|
|
@test_case
|
|
def empty_sequence_20(self):
|
|
self.impl._distill_params_20(())
|
|
self.impl._distill_params_20([])
|
|
|
|
@test_case
|
|
def list_20(self):
|
|
self.impl._distill_params_20(self.list_tup)
|
|
|
|
@test_case
|
|
def tuple_20(self):
|
|
self.impl._distill_params_20(self.tup_tup)
|
|
|
|
@test_case
|
|
def list_dict_20(self):
|
|
self.impl._distill_params_20(self.list_tup)
|
|
|
|
@test_case
|
|
def tuple_dict_20(self):
|
|
self.impl._distill_params_20(self.dict)
|
|
|
|
@test_case
|
|
def mapping_20(self):
|
|
self.impl._distill_params_20(self.mapping)
|
|
|
|
@test_case
|
|
def raw_none(self):
|
|
self.impl._distill_raw_params(None)
|
|
|
|
@test_case
|
|
def raw_empty_sequence(self):
|
|
self.impl._distill_raw_params(())
|
|
self.impl._distill_raw_params([])
|
|
|
|
@test_case
|
|
def raw_list(self):
|
|
self.impl._distill_raw_params(self.list_tup)
|
|
|
|
@test_case
|
|
def raw_tuple(self):
|
|
self.impl._distill_raw_params(self.tup_tup)
|
|
|
|
@test_case
|
|
def raw_list_dict(self):
|
|
self.impl._distill_raw_params(self.list_tup)
|
|
|
|
@test_case
|
|
def raw_tuple_dict(self):
|
|
self.impl._distill_raw_params(self.dict)
|
|
|
|
@test_case
|
|
def raw_mapping(self):
|
|
self.impl._distill_raw_params(self.mapping)
|
|
|
|
|
|
class IdentitySet(Case):
|
|
@staticmethod
|
|
def set_fn():
|
|
return set
|
|
|
|
@staticmethod
|
|
def python():
|
|
from sqlalchemy.util._py_collections import IdentitySet
|
|
|
|
return IdentitySet
|
|
|
|
@staticmethod
|
|
def cython():
|
|
from sqlalchemy.cyextension import collections
|
|
|
|
return collections.IdentitySet
|
|
|
|
IMPLEMENTATIONS = {
|
|
"set": set_fn.__func__,
|
|
"python": python.__func__,
|
|
"cython": cython.__func__,
|
|
}
|
|
NUMBER = 10
|
|
|
|
def init_objects(self):
|
|
self.val1 = list(range(10))
|
|
self.val2 = list(wrap(token_urlsafe(4 * 2048), 4))
|
|
|
|
self.imp_1 = self.impl(self.val1)
|
|
self.imp_2 = self.impl(self.val2)
|
|
|
|
@classmethod
|
|
def update_results(cls, results):
|
|
cls._divide_results(results, "python", "set", "py / set")
|
|
cls._divide_results(results, "cython", "python", "cy / py")
|
|
cls._divide_results(results, "cython", "set", "cy / set")
|
|
|
|
@test_case
|
|
def init_empty(self):
|
|
i = self.impl
|
|
for _ in range(10000):
|
|
i()
|
|
|
|
@test_case
|
|
def init(self):
|
|
i, v = self.impl, self.val2
|
|
for _ in range(500):
|
|
i(v)
|
|
|
|
@test_case
|
|
def init_from_impl(self):
|
|
for _ in range(500):
|
|
self.impl(self.imp_2)
|
|
|
|
@test_case
|
|
def add(self):
|
|
ii = self.impl()
|
|
for _ in range(10):
|
|
for i in range(1000):
|
|
ii.add(str(i))
|
|
|
|
@test_case
|
|
def contains(self):
|
|
ii = self.impl(self.val2)
|
|
for _ in range(500):
|
|
for x in self.val1 + self.val2:
|
|
x in ii
|
|
|
|
@test_case
|
|
def remove(self):
|
|
v = [str(i) for i in range(7500)]
|
|
ii = self.impl(v)
|
|
for x in v[:5000]:
|
|
ii.remove(x)
|
|
|
|
@test_case
|
|
def discard(self):
|
|
v = [str(i) for i in range(7500)]
|
|
ii = self.impl(v)
|
|
for x in v[:5000]:
|
|
ii.discard(x)
|
|
|
|
@test_case
|
|
def pop(self):
|
|
for x in range(1000):
|
|
ii = self.impl(self.val1)
|
|
for x in self.val1:
|
|
ii.pop()
|
|
|
|
@test_case
|
|
def clear(self):
|
|
i, v = self.impl, self.val1
|
|
for _ in range(5000):
|
|
ii = i(v)
|
|
ii.clear()
|
|
|
|
@test_case
|
|
def eq(self):
|
|
for x in range(1000):
|
|
self.imp_1 == self.imp_1
|
|
self.imp_1 == self.imp_2
|
|
self.imp_1 == self.val2
|
|
|
|
@test_case
|
|
def ne(self):
|
|
for x in range(1000):
|
|
self.imp_1 != self.imp_1
|
|
self.imp_1 != self.imp_2
|
|
self.imp_1 != self.val2
|
|
|
|
@test_case
|
|
def issubset(self):
|
|
for _ in range(250):
|
|
self.imp_1.issubset(self.imp_1)
|
|
self.imp_1.issubset(self.imp_2)
|
|
self.imp_1.issubset(self.val1)
|
|
self.imp_1.issubset(self.val2)
|
|
|
|
@test_case
|
|
def le(self):
|
|
for x in range(1000):
|
|
self.imp_1 <= self.imp_1
|
|
self.imp_1 <= self.imp_2
|
|
self.imp_2 <= self.imp_1
|
|
self.imp_2 <= self.imp_2
|
|
|
|
@test_case
|
|
def lt(self):
|
|
for x in range(2500):
|
|
self.imp_1 < self.imp_1
|
|
self.imp_1 < self.imp_2
|
|
self.imp_2 < self.imp_1
|
|
self.imp_2 < self.imp_2
|
|
|
|
@test_case
|
|
def issuperset(self):
|
|
for _ in range(250):
|
|
self.imp_1.issuperset(self.imp_1)
|
|
self.imp_1.issuperset(self.imp_2)
|
|
self.imp_1.issubset(self.val1)
|
|
self.imp_1.issubset(self.val2)
|
|
|
|
@test_case
|
|
def ge(self):
|
|
for x in range(1000):
|
|
self.imp_1 >= self.imp_1
|
|
self.imp_1 >= self.imp_2
|
|
self.imp_2 >= self.imp_1
|
|
self.imp_2 >= self.imp_2
|
|
|
|
@test_case
|
|
def gt(self):
|
|
for x in range(2500):
|
|
self.imp_1 > self.imp_1
|
|
self.imp_2 > self.imp_2
|
|
self.imp_2 > self.imp_1
|
|
self.imp_2 > self.imp_2
|
|
|
|
@test_case
|
|
def union(self):
|
|
for _ in range(250):
|
|
self.imp_1.union(self.imp_2)
|
|
|
|
@test_case
|
|
def or_test(self):
|
|
for _ in range(250):
|
|
self.imp_1 | self.imp_2
|
|
|
|
@test_case
|
|
def update(self):
|
|
ii = self.impl(self.val1)
|
|
for _ in range(250):
|
|
ii.update(self.imp_2)
|
|
|
|
@test_case
|
|
def ior(self):
|
|
ii = self.impl(self.val1)
|
|
for _ in range(250):
|
|
ii |= self.imp_2
|
|
|
|
@test_case
|
|
def difference(self):
|
|
for _ in range(250):
|
|
self.imp_1.difference(self.imp_2)
|
|
self.imp_1.difference(self.val2)
|
|
|
|
@test_case
|
|
def sub(self):
|
|
for _ in range(500):
|
|
self.imp_1 - self.imp_2
|
|
|
|
@test_case
|
|
def difference_update(self):
|
|
ii = self.impl(self.val1)
|
|
for _ in range(250):
|
|
ii.difference_update(self.imp_2)
|
|
ii.difference_update(self.val2)
|
|
|
|
@test_case
|
|
def isub(self):
|
|
ii = self.impl(self.val1)
|
|
for _ in range(500):
|
|
ii -= self.imp_2
|
|
|
|
@test_case
|
|
def intersection(self):
|
|
for _ in range(250):
|
|
self.imp_1.intersection(self.imp_2)
|
|
self.imp_1.intersection(self.val2)
|
|
|
|
@test_case
|
|
def and_test(self):
|
|
for _ in range(500):
|
|
self.imp_1 & self.imp_2
|
|
|
|
@test_case
|
|
def intersection_up(self):
|
|
ii = self.impl(self.val1)
|
|
for _ in range(250):
|
|
ii.intersection_update(self.imp_2)
|
|
ii.intersection_update(self.val2)
|
|
|
|
@test_case
|
|
def iand(self):
|
|
ii = self.impl(self.val1)
|
|
for _ in range(500):
|
|
ii &= self.imp_2
|
|
|
|
@test_case
|
|
def symmetric_diff(self):
|
|
for _ in range(125):
|
|
self.imp_1.symmetric_difference(self.imp_2)
|
|
self.imp_1.symmetric_difference(self.val2)
|
|
|
|
@test_case
|
|
def xor(self):
|
|
for _ in range(250):
|
|
self.imp_1 ^ self.imp_2
|
|
|
|
@test_case
|
|
def symmetric_diff_up(self):
|
|
ii = self.impl(self.val1)
|
|
for _ in range(125):
|
|
ii.symmetric_difference_update(self.imp_2)
|
|
ii.symmetric_difference_update(self.val2)
|
|
|
|
@test_case
|
|
def ixor(self):
|
|
ii = self.impl(self.val1)
|
|
for _ in range(250):
|
|
ii ^= self.imp_2
|
|
|
|
@test_case
|
|
def copy(self):
|
|
for _ in range(250):
|
|
self.imp_1.copy()
|
|
self.imp_2.copy()
|
|
|
|
@test_case
|
|
def len(self):
|
|
for x in range(5000):
|
|
len(self.imp_1)
|
|
len(self.imp_2)
|
|
|
|
@test_case
|
|
def iter(self):
|
|
for _ in range(2000):
|
|
list(self.imp_1)
|
|
list(self.imp_2)
|
|
|
|
@test_case
|
|
def repr(self):
|
|
for _ in range(250):
|
|
str(self.imp_1)
|
|
str(self.imp_2)
|
|
|
|
|
|
class OrderedSet(IdentitySet):
|
|
@staticmethod
|
|
def set_fn():
|
|
return set
|
|
|
|
@staticmethod
|
|
def python():
|
|
from sqlalchemy.util._py_collections import OrderedSet
|
|
|
|
return OrderedSet
|
|
|
|
@staticmethod
|
|
def cython():
|
|
from sqlalchemy.cyextension import collections
|
|
|
|
return collections.OrderedSet
|
|
|
|
@staticmethod
|
|
def ordered_lib():
|
|
from orderedset import OrderedSet
|
|
|
|
return OrderedSet
|
|
|
|
IMPLEMENTATIONS = {
|
|
"set": set_fn.__func__,
|
|
"python": python.__func__,
|
|
"cython": cython.__func__,
|
|
"ordsetlib": ordered_lib.__func__,
|
|
}
|
|
|
|
@classmethod
|
|
def update_results(cls, results):
|
|
super().update_results(results)
|
|
cls._divide_results(results, "ordsetlib", "set", "ordlib/set")
|
|
cls._divide_results(results, "cython", "ordsetlib", "cy / ordlib")
|
|
|
|
@test_case
|
|
def add_op(self):
|
|
ii = self.impl(self.val1)
|
|
v2 = self.impl(self.val2)
|
|
for _ in range(1000):
|
|
ii + v2
|
|
|
|
@test_case
|
|
def getitem(self):
|
|
ii = self.impl(self.val1)
|
|
for _ in range(1000):
|
|
for i in range(len(self.val1)):
|
|
ii[i]
|
|
|
|
@test_case
|
|
def insert(self):
|
|
ii = self.impl(self.val1)
|
|
for _ in range(5):
|
|
for i in range(1000):
|
|
ii.insert(-i % 2, 1)
|
|
|
|
|
|
class TupleGetter(Case):
|
|
@staticmethod
|
|
def python():
|
|
from sqlalchemy.engine._py_row import tuplegetter
|
|
|
|
return tuplegetter
|
|
|
|
@staticmethod
|
|
def c():
|
|
from sqlalchemy import cresultproxy
|
|
|
|
return cresultproxy.tuplegetter
|
|
|
|
@staticmethod
|
|
def cython():
|
|
from sqlalchemy.cyextension import resultproxy
|
|
|
|
return resultproxy.tuplegetter
|
|
|
|
IMPLEMENTATIONS = {
|
|
"python": python.__func__,
|
|
"c": c.__func__,
|
|
"cython": cython.__func__,
|
|
}
|
|
|
|
def init_objects(self):
|
|
self.impl_tg = self.impl
|
|
|
|
self.tuple = tuple(range(1000))
|
|
self.tg_inst = self.impl_tg(42)
|
|
self.tg_inst_m = self.impl_tg(42, 420, 99, 9, 1)
|
|
|
|
class MockRow:
|
|
def __init__(self, data):
|
|
self.data = data
|
|
|
|
def _get_by_int_impl(self, index):
|
|
# called by python
|
|
return self.data[index]
|
|
|
|
def _get_by_key_impl_mapping(self, index):
|
|
# called by c
|
|
return self.data[index]
|
|
|
|
self.row = MockRow(self.tuple)
|
|
|
|
@classmethod
|
|
def update_results(cls, results):
|
|
cls._divide_results(results, "c", "python", "c / py")
|
|
cls._divide_results(results, "cython", "python", "cy / py")
|
|
cls._divide_results(results, "cython", "c", "cy / c")
|
|
|
|
@test_case
|
|
def tuplegetter_one(self):
|
|
self.tg_inst(self.tuple)
|
|
|
|
@test_case
|
|
def tuplegetter_many(self):
|
|
self.tg_inst_m(self.tuple)
|
|
|
|
@test_case
|
|
def tuplegetter_new_one(self):
|
|
self.impl_tg(42)(self.tuple)
|
|
|
|
@test_case
|
|
def tuplegetter_new_many(self):
|
|
self.impl_tg(42, 420, 99, 9, 1)(self.tuple)
|
|
|
|
|
|
class BaseRow(Case):
|
|
@staticmethod
|
|
def python():
|
|
from sqlalchemy.engine._py_row import BaseRow
|
|
|
|
return BaseRow
|
|
|
|
@staticmethod
|
|
def c():
|
|
from sqlalchemy.cresultproxy import BaseRow
|
|
|
|
return BaseRow
|
|
|
|
@staticmethod
|
|
def cython():
|
|
from sqlalchemy.cyextension import resultproxy
|
|
|
|
return resultproxy.BaseRow
|
|
|
|
IMPLEMENTATIONS = {
|
|
"python": python.__func__,
|
|
# "c": c.__func__,
|
|
"cython": cython.__func__,
|
|
}
|
|
|
|
def init_objects(self):
|
|
from sqlalchemy.engine.result import SimpleResultMetaData
|
|
from string import ascii_letters
|
|
|
|
self.parent = SimpleResultMetaData(("a", "b", "c"))
|
|
self.row_args = (
|
|
self.parent,
|
|
self.parent._processors,
|
|
self.parent._keymap,
|
|
0,
|
|
(1, 2, 3),
|
|
)
|
|
self.parent_long = SimpleResultMetaData(tuple(ascii_letters))
|
|
self.row_long_args = (
|
|
self.parent_long,
|
|
self.parent_long._processors,
|
|
self.parent_long._keymap,
|
|
0,
|
|
tuple(range(len(ascii_letters))),
|
|
)
|
|
self.row = self.impl(*self.row_args)
|
|
self.row_long = self.impl(*self.row_long_args)
|
|
assert isinstance(self.row, self.impl), type(self.row)
|
|
|
|
class Row(self.impl):
|
|
pass
|
|
|
|
self.Row = Row
|
|
self.row_sub = Row(*self.row_args)
|
|
|
|
self.row_state = self.row.__getstate__()
|
|
self.row_long_state = self.row_long.__getstate__()
|
|
|
|
@classmethod
|
|
def update_results(cls, results):
|
|
cls._divide_results(results, "c", "python", "c / py")
|
|
cls._divide_results(results, "cython", "python", "cy / py")
|
|
cls._divide_results(results, "cython", "c", "cy / c")
|
|
|
|
@test_case
|
|
def base_row_new(self):
|
|
self.impl(*self.row_args)
|
|
self.impl(*self.row_long_args)
|
|
|
|
@test_case
|
|
def row_new(self):
|
|
self.Row(*self.row_args)
|
|
self.Row(*self.row_long_args)
|
|
|
|
@test_case
|
|
def row_dumps(self):
|
|
self.row.__getstate__()
|
|
self.row_long.__getstate__()
|
|
|
|
@test_case
|
|
def row_loads(self):
|
|
self.impl.__new__(self.impl).__setstate__(self.row_state)
|
|
self.impl.__new__(self.impl).__setstate__(self.row_long_state)
|
|
|
|
@test_case
|
|
def row_filter(self):
|
|
self.row._filter_on_values(None)
|
|
self.row_long._filter_on_values(None)
|
|
|
|
@test_case
|
|
def row_values_impl(self):
|
|
self.row._values_impl()
|
|
self.row_long._values_impl()
|
|
|
|
@test_case
|
|
def row_iter(self):
|
|
list(self.row)
|
|
list(self.row_long)
|
|
|
|
@test_case
|
|
def row_len(self):
|
|
len(self.row)
|
|
len(self.row_long)
|
|
|
|
@test_case
|
|
def row_hash(self):
|
|
hash(self.row)
|
|
hash(self.row_long)
|
|
|
|
@test_case
|
|
def getitem(self):
|
|
self.row[0]
|
|
self.row[1]
|
|
self.row[-1]
|
|
self.row_long[0]
|
|
self.row_long[1]
|
|
self.row_long[-1]
|
|
|
|
@test_case
|
|
def getitem_slice(self):
|
|
self.row[0:1]
|
|
self.row[1:-1]
|
|
self.row_long[0:1]
|
|
self.row_long[1:-1]
|
|
|
|
@test_case
|
|
def get_by_int(self):
|
|
self.row._get_by_int_impl(0)
|
|
self.row._get_by_int_impl(1)
|
|
self.row_long._get_by_int_impl(0)
|
|
self.row_long._get_by_int_impl(1)
|
|
|
|
@test_case
|
|
def get_by_key(self):
|
|
self.row._get_by_key_impl(0)
|
|
self.row._get_by_key_impl(1)
|
|
self.row_long._get_by_key_impl(0)
|
|
self.row_long._get_by_key_impl(1)
|
|
|
|
@test_case
|
|
def get_by_key_slice(self):
|
|
self.row._get_by_key_impl(slice(0, 1))
|
|
self.row._get_by_key_impl(slice(1, -1))
|
|
self.row_long._get_by_key_impl(slice(0, 1))
|
|
self.row_long._get_by_key_impl(slice(1, -1))
|
|
|
|
@test_case
|
|
def getattr(self):
|
|
self.row.a
|
|
self.row.b
|
|
self.row_long.x
|
|
self.row_long.y
|
|
|
|
|
|
class CacheAnonMap(Case):
|
|
@staticmethod
|
|
def python():
|
|
from sqlalchemy.sql._py_util import cache_anon_map
|
|
|
|
return cache_anon_map
|
|
|
|
@staticmethod
|
|
def cython():
|
|
from sqlalchemy.cyextension.util import cache_anon_map
|
|
|
|
return cache_anon_map
|
|
|
|
IMPLEMENTATIONS = {"python": python.__func__, "cython": cython.__func__}
|
|
|
|
NUMBER = 1000000
|
|
|
|
def init_objects(self):
|
|
from sqlalchemy import column, bindparam
|
|
|
|
self.object_1 = column("x")
|
|
self.object_2 = bindparam("y")
|
|
|
|
self.impl_w_non_present = self.impl()
|
|
self.impl_w_present = iwp = self.impl()
|
|
iwp.get_anon(self.object_1)
|
|
iwp.get_anon(self.object_2)
|
|
|
|
@classmethod
|
|
def update_results(cls, results):
|
|
cls._divide_results(results, "cython", "python", "cy / py")
|
|
|
|
@test_case
|
|
def test_get_anon_non_present(self):
|
|
self.impl_w_non_present.get_anon(self.object_1)
|
|
|
|
@test_case
|
|
def test_get_anon_present(self):
|
|
self.impl_w_present.get_anon(self.object_1)
|
|
|
|
@test_case
|
|
def test_has_key_non_present(self):
|
|
id(self.object_1) in self.impl_w_non_present
|
|
|
|
@test_case
|
|
def test_has_key_present(self):
|
|
id(self.object_1) in self.impl_w_present
|
|
|
|
|
|
class PrefixAnonMap(Case):
|
|
@staticmethod
|
|
def python():
|
|
from sqlalchemy.sql._py_util import prefix_anon_map
|
|
|
|
return prefix_anon_map
|
|
|
|
@staticmethod
|
|
def cython():
|
|
from sqlalchemy.cyextension.util import prefix_anon_map
|
|
|
|
return prefix_anon_map
|
|
|
|
IMPLEMENTATIONS = {"python": python.__func__, "cython": cython.__func__}
|
|
|
|
NUMBER = 1000000
|
|
|
|
def init_objects(self):
|
|
from sqlalchemy.sql.elements import _anonymous_label
|
|
|
|
self.name = _anonymous_label.safe_construct(58243, "some_column_name")
|
|
|
|
self.impl_w_non_present = self.impl()
|
|
self.impl_w_present = iwp = self.impl()
|
|
self.name.apply_map(iwp)
|
|
|
|
@classmethod
|
|
def update_results(cls, results):
|
|
cls._divide_results(results, "cython", "python", "cy / py")
|
|
|
|
@test_case
|
|
def test_apply_non_present(self):
|
|
|
|
self.name.apply_map(self.impl_w_non_present)
|
|
|
|
@test_case
|
|
def test_apply_present(self):
|
|
|
|
self.name.apply_map(self.impl_w_present)
|
|
|
|
|
|
def tabulate(results, inverse):
|
|
dim = 11
|
|
header = "{:<20}|" + (" {:<%s} |" % dim) * len(results)
|
|
num_format = "{:<%s.9f}" % dim
|
|
row = "{:<20}|" + " {} |" * len(results)
|
|
names = list(results)
|
|
print(header.format("", *names))
|
|
|
|
for meth in inverse:
|
|
strings = [
|
|
num_format.format(inverse[meth][name])[:dim] for name in names
|
|
]
|
|
print(row.format(meth, *strings))
|
|
|
|
|
|
def main():
|
|
import argparse
|
|
|
|
cases = Case._CASES
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description="Compare implementation between them"
|
|
)
|
|
parser.add_argument(
|
|
"case",
|
|
help="Case to run",
|
|
nargs="+",
|
|
choices=["all"] + [c.__name__ for c in cases],
|
|
)
|
|
parser.add_argument("--filter", help="filter the test for this regexp")
|
|
parser.add_argument(
|
|
"--factor", help="scale number passed to timeit", type=float, default=1
|
|
)
|
|
parser.add_argument("--csv", help="save to csv", action="store_true")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if "all" in args.case:
|
|
to_run = cases
|
|
else:
|
|
to_run = [c for c in cases if c.__name__ in args.case]
|
|
|
|
for case in to_run:
|
|
print("Running case", case.__name__)
|
|
result = case.run_case(args.factor, args.filter)
|
|
|
|
inverse = defaultdict(dict)
|
|
for name in result:
|
|
for meth in result[name]:
|
|
inverse[meth][name] = result[name][meth]
|
|
|
|
tabulate(result, inverse)
|
|
|
|
if args.csv:
|
|
import csv
|
|
|
|
file_name = f"{case.__name__}.csv"
|
|
with open(file_name, "w", newline="") as f:
|
|
w = csv.DictWriter(f, ["", *result])
|
|
w.writeheader()
|
|
for n in inverse:
|
|
w.writerow({"": n, **inverse[n]})
|
|
print("Wrote file", file_name)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|