Skip to content
This repository was archived by the owner on Dec 10, 2018. It is now read-only.

Commit

Permalink
Dynamically compile spec'd __init__ functions
Browse files Browse the repository at this point in the history
init_func_generator() previously used a single argument-less `init` base
function coupled with a locals() hack to build spec-aware __init__()
functions. This worked well, but profiling indicated that we were spending a
good amount of time calling locals() and dict.copy()'ing its result.

We now dynamically generate unique, per-spec __init__() functions using
compile(..., 'exec'). This results in much faster runtime performance at the
expense of a little compile-time complexity.

The benchmark indicates this change results in ~10% faster decoding (for both
the native and cybin implementations).

Also, because this code no longer requires version-specific code paths, it can
be moved out of _compat.py and into thrift.py.
  • Loading branch information
jparise committed Jun 23, 2016
1 parent 5aa0b90 commit 5661d15
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 68 deletions.
67 changes: 0 additions & 67 deletions thriftpy/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

import platform
import sys
import types

PY3 = sys.version_info[0] == 3
PYPY = "__pypy__" in sys.modules
Expand Down Expand Up @@ -58,69 +57,3 @@ def __new__(cls, name, this_bases, d):
return type.__new__(cls, name, (), d)
return meta(name, bases, d)
return metaclass('temporary_class', None, {})


def init_func_generator(spec):
"""Generate `__init__` function based on TPayload.default_spec
For example::
spec = [('name', 'Alice'), ('number', None)]
will generate::
def __init__(self, name='Alice', number=None):
kwargs = locals()
kwargs.pop('self')
self.__dict__.update(kwargs)
TODO: The `locals()` part may need refine.
"""
if not spec:
def __init__(self):
pass
return __init__

varnames, defaults = zip(*spec)
varnames = ('self', ) + varnames

def init(self):
self.__dict__ = locals().copy()
del self.__dict__['self']

code = init.__code__
if PY3:
new_code = types.CodeType(len(varnames),
0,
len(varnames),
code.co_stacksize,
code.co_flags,
code.co_code,
code.co_consts,
code.co_names,
varnames,
code.co_filename,
"__init__",
code.co_firstlineno,
code.co_lnotab,
code.co_freevars,
code.co_cellvars)
else:
new_code = types.CodeType(len(varnames),
len(varnames),
code.co_stacksize,
code.co_flags,
code.co_code,
code.co_consts,
code.co_names,
varnames,
code.co_filename,
"__init__",
code.co_firstlineno,
code.co_lnotab,
code.co_freevars,
code.co_cellvars)

return types.FunctionType(new_code,
{"__builtins__": __builtins__},
argdefs=defaults)
33 changes: 32 additions & 1 deletion thriftpy/thrift.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
from __future__ import absolute_import

import functools
import types

from ._compat import init_func_generator, with_metaclass
from ._compat import with_metaclass


def args2kwargs(thrift_spec, *args):
Expand All @@ -38,6 +39,36 @@ def _type(s):
return "MAP<%s, %s>" % (_type(spec[0]), _type(spec[1]))


def init_func_generator(spec):
"""Generate `__init__` function based on TPayload.default_spec
For example::
spec = [('name', 'Alice'), ('number', None)]
will generate a types.FunctionType object representing::
def __init__(self, name='Alice', number=None):
self.name = name
self.number = number
"""
if not spec:
def __init__(self):
pass
return __init__

varnames, defaults = zip(*spec)

args = ', '.join(map('{0[0]}={0[1]!r}'.format, spec))
init = "def __init__(self, {0}):\n".format(args)
init += "\n".join(map(' self.{0} = {0}'.format, varnames))

code = compile(init, '<init_func_generator>', 'exec')
func = next(c for c in code.co_consts if isinstance(c, types.CodeType))

return types.FunctionType(func, {}, argdefs=defaults)


class TType(object):
STOP = 0
VOID = 1
Expand Down

0 comments on commit 5661d15

Please sign in to comment.