import sys
import timeit
import uuid
from copy import copy
from math import sqrt
from time import time
try:
from frozndict import frozendict as RustFrozenDict
except ImportError:
RustFrozenDict = None
try:
from frozendict import frozendict as CFrozenDict
except ImportError:
CFrozenDict = None
try:
import immutables
ImmutableMap = immutables.Map except ImportError:
ImmutableMap = None
def mindev(data, xbar=None):
if not data:
raise ValueError("No data")
if xbar is None:
xbar = min(data)
sigma2 = sum((x - xbar) ** 2 for x in data)
n = max(len(data) - 1, 1)
return sqrt(sigma2 / n)
def autorange(stmt, setup="pass", globals=None, ratio=1000, bench_time=2, number=None):
if setup is None:
setup = "pass"
t = timeit.Timer(stmt=stmt, setup=setup, globals=globals)
break_immediately = False
if number is None:
a = t.autorange()
number_temp = a[0]
number = max(int(number_temp / ratio), 1)
repeat = max(int(number_temp / number), 1)
else:
repeat = 1
break_immediately = True
data_min = list(t.repeat(number=number, repeat=repeat))
bench_start = time()
while True:
data_min.extend(t.repeat(number=number, repeat=repeat))
if break_immediately or time() - bench_start > bench_time:
break
data_min.sort()
xbar = data_min[0]
i = 0
while i < len(data_min):
i = len(data_min)
sigma = mindev(data_min, xbar=xbar)
for i2 in range(2, len(data_min)):
if data_min[i2] - xbar > 3 * sigma:
break
k = max(i, 5)
del data_min[k:]
return (min(data_min) / number, mindev(data_min, xbar=xbar) / number)
def get_uuid():
return str(uuid.uuid4())
PRINT_TPL = (
"Name: {name: <26} Size: {size: >4}; Keys: {keys: >3}; "
"Type: {type: >12}; Time: {time:.2e}; Sigma: {sigma:.0e}"
)
BENCH_HASH_NAME = "hash"
BENCH_COPY_NAME = "copy"
BENCH_SET_NAME = "set"
BENCH_DELETE_NAME = "delete"
BENCH_FROMKEYS_NAME = "fromkeys"
BENCH_CONSTR_KWARGS_NAME = "constructor(kwargs)"
BENCHMARKS = (
{
"name": "constructor(d)",
"code": "klass(d)",
"setup": "klass = type(o)",
},
{
"name": BENCH_CONSTR_KWARGS_NAME,
"code": "klass(**d)",
"setup": "klass = type(o)",
},
{
"name": "constructor(seq2)",
"code": "klass(v)",
"setup": "klass = type(o); v = tuple(d.items())",
},
{
"name": "constructor(o)",
"code": "klass(o)",
"setup": "klass = type(o)",
},
{
"name": BENCH_COPY_NAME,
"code": None,
"setup": "pass",
},
{
"name": "o == o",
"code": "o == o",
"setup": "pass",
},
{
"name": "for x in o",
"code": "for _ in o: pass",
"setup": "pass",
},
{
"name": "for x in o.values()",
"code": "for _ in values: pass",
"setup": "values = o.values()",
},
{
"name": "for x in o.items()",
"code": "for _ in items: pass",
"setup": "items = o.items()",
},
{
"name": "pickle.dumps",
"code": "dumps(o, protocol=-1)",
"setup": "from pickle import dumps",
},
{
"name": "pickle.loads",
"code": "loads(dump)",
"setup": "from pickle import loads, dumps; dump = dumps(o, protocol=-1)",
},
{
"name": BENCH_FROMKEYS_NAME,
"code": "fromkeys(keys)",
"setup": "fromkeys = type(o).fromkeys; keys = list(o.keys())",
},
{
"name": BENCH_SET_NAME,
"code": None,
"setup": "val = get_uuid()",
},
{
"name": BENCH_DELETE_NAME,
"code": None,
"setup": "pass",
},
{
"name": BENCH_HASH_NAME,
"code": "hash(o)",
"setup": "pass",
},
)
def main(number):
dictionary_sizes = (5, 1000)
sep_n = 80
sep_major = "#"
sep_minor = "/"
str_key = "12323f29-c31f-478c-9b15-e7acc5354df9"
int_key = max(dictionary_sizes[0] - 2, 0)
dict_collection = []
for n in dictionary_sizes:
d1 = {}
d2 = {}
for i in range(n - 1):
d1[get_uuid()] = get_uuid()
d2[i] = i
d1[str_key] = get_uuid()
d2[999] = 999
entries = [(d1, str_key, "str"), (d2, int_key, "int")]
dict_collection.append((n, entries))
for benchmark in BENCHMARKS:
print(sep_major * sep_n)
for _n, entries in dict_collection:
for plain_dict, one_key, key_label in entries:
if benchmark["name"] == BENCH_CONSTR_KWARGS_NAME and key_label == "int":
continue
candidates = []
if dict is not None:
candidates.append((plain_dict, "dict"))
if RustFrozenDict is not None:
candidates.append((RustFrozenDict(plain_dict), "FrozenDict"))
if CFrozenDict is not None:
candidates.append((CFrozenDict(plain_dict), "frozendict"))
if ImmutableMap is not None:
candidates.append((ImmutableMap(plain_dict), "Map"))
print(sep_minor * sep_n)
for o, _ in candidates:
if benchmark["name"] == BENCH_HASH_NAME and isinstance(o, dict):
continue
if benchmark["name"] == BENCH_SET_NAME:
if isinstance(o, dict):
benchmark["code"] = "o.copy()[one_key] = val"
else:
benchmark["code"] = (
"o.set(one_key, val)"
if type(o).__name__ == "Map"
else "type(o)({**o, one_key: val})"
)
if benchmark["name"] == BENCH_DELETE_NAME:
if isinstance(o, dict):
benchmark["code"] = "del o.copy()[one_key]"
else:
benchmark["code"] = (
"o.delete(one_key)"
if type(o).__name__ == "Map"
else "{k: v for k, v in o.items() if k != one_key}"
)
if benchmark["name"] == BENCH_COPY_NAME:
if type(o).__name__ == "Map":
benchmark["code"] = "copy(o)"
else:
benchmark["code"] = "o.copy()"
if (
benchmark["name"] == BENCH_FROMKEYS_NAME
and type(o).__name__ == "Map"
):
continue
if benchmark["name"] in (
"pickle.dumps",
"pickle.loads",
) and isinstance(o, dict):
continue
bench_res = autorange(
stmt=benchmark["code"],
setup=benchmark["setup"],
globals={
"o": copy(o) if isinstance(o, dict) else o,
"get_uuid": get_uuid,
"d": plain_dict.copy(),
"one_key": one_key,
"copy": copy,
},
number=number,
)
print(
PRINT_TPL.format(
name=f"`{benchmark['name']}`; ",
keys=key_label,
size=len(o),
type=type(o).__name__,
time=bench_res[0],
sigma=bench_res[1],
)
)
print(sep_major * sep_n)
if __name__ == "__main__":
NUM = None
ARGV = sys.argv
LEN_ARGV = len(ARGV)
MAX_POSITIONAL_ARGS = 1
MAX_LEN_ARGV = MAX_POSITIONAL_ARGS + 1
if LEN_ARGV > MAX_LEN_ARGV:
raise ValueError(
f"{__name__} must not accept more than "
f"{MAX_POSITIONAL_ARGS} positional command-line parameters"
)
if LEN_ARGV == MAX_LEN_ARGV:
NUM = int(ARGV[MAX_POSITIONAL_ARGS])
main(NUM)