py: Optimise types for common case where type has a single parent type.

The common cases for inheritance are 0 or 1 parent types, for both built-in
types (eg built-in exceptions) as well as user defined types.  So it makes
sense to optimise the case of 1 parent type by storing just the type and
not a tuple of 1 value (that value being the single parent type).

This patch makes such an optimisation.  Even though there is a bit more
code to handle the two cases (either a single type or a tuple with 2 or
more values) it helps reduce overall code size because it eliminates the
need to create a static tuple to hold single parents (eg for the built-in
exceptions).  It also helps reduce RAM usage for user defined types that
only derive from a single parent.

Changes in code size (in bytes) due to this patch:

    bare-arm:       -16
    minimal (x86): -176
    unix (x86-64): -320
    unix nanbox:   -384
    stmhal:         -64
    cc3200:         -32
    esp8266:       -108
This commit is contained in:
Damien George 2017-04-06 12:09:01 +10:00
parent fc710169b7
commit 816413e4b2
5 changed files with 101 additions and 95 deletions

View File

@ -525,8 +525,11 @@ struct _mp_obj_type_t {
// One of disjoint protocols (interfaces), like mp_stream_p_t, etc.
const void *protocol;
// A tuple containing all the base types of this type.
struct _mp_obj_tuple_t *bases_tuple;
// A pointer to the parents of this type:
// - 0 parents: pointer is NULL (object is implicitly the single parent)
// - 1 parent: a pointer to the type of that parent
// - 2 or more parents: pointer to a tuple object containing the parent types
const void *parent;
// A dict mapping qstrs to objects local methods/constants/etc.
struct _mp_obj_dict_t *locals_dict;

View File

@ -577,8 +577,6 @@ const mp_obj_type_t mp_type_dict = {
};
#if MICROPY_PY_COLLECTIONS_ORDEREDDICT
STATIC const mp_rom_obj_tuple_t ordereddict_base_tuple = {{&mp_type_tuple}, 1, {MP_ROM_PTR(&mp_type_dict)}};
const mp_obj_type_t mp_type_ordereddict = {
{ &mp_type_type },
.name = MP_QSTR_OrderedDict,
@ -588,7 +586,7 @@ const mp_obj_type_t mp_type_ordereddict = {
.binary_op = dict_binary_op,
.subscr = dict_subscr,
.getiter = dict_getiter,
.bases_tuple = (mp_obj_tuple_t*)(mp_rom_obj_tuple_t*)&ordereddict_base_tuple,
.parent = &mp_type_dict,
.locals_dict = (mp_obj_dict_t*)&dict_locals_dict,
};
#endif

View File

@ -197,9 +197,6 @@ const mp_obj_type_t mp_type_BaseException = {
.locals_dict = (mp_obj_dict_t*)&exc_locals_dict,
};
#define MP_DEFINE_EXCEPTION_BASE(base_name) \
STATIC const mp_rom_obj_tuple_t mp_type_ ## base_name ## _base_tuple = {{&mp_type_tuple}, 1, {MP_ROM_PTR(&mp_type_ ## base_name)}};\
#define MP_DEFINE_EXCEPTION(exc_name, base_name) \
const mp_obj_type_t mp_type_ ## exc_name = { \
{ &mp_type_type }, \
@ -207,23 +204,20 @@ const mp_obj_type_t mp_type_ ## exc_name = { \
.print = mp_obj_exception_print, \
.make_new = mp_obj_exception_make_new, \
.attr = exception_attr, \
.bases_tuple = (mp_obj_tuple_t*)(mp_rom_obj_tuple_t*)&mp_type_ ## base_name ## _base_tuple, \
.parent = &mp_type_ ## base_name, \
};
// List of all exceptions, arranged as in the table at:
// http://docs.python.org/3/library/exceptions.html
MP_DEFINE_EXCEPTION_BASE(BaseException)
MP_DEFINE_EXCEPTION(SystemExit, BaseException)
MP_DEFINE_EXCEPTION(KeyboardInterrupt, BaseException)
MP_DEFINE_EXCEPTION(GeneratorExit, BaseException)
MP_DEFINE_EXCEPTION(Exception, BaseException)
MP_DEFINE_EXCEPTION_BASE(Exception)
#if MICROPY_PY_ASYNC_AWAIT
MP_DEFINE_EXCEPTION(StopAsyncIteration, Exception)
#endif
MP_DEFINE_EXCEPTION(StopIteration, Exception)
MP_DEFINE_EXCEPTION(ArithmeticError, Exception)
MP_DEFINE_EXCEPTION_BASE(ArithmeticError)
//MP_DEFINE_EXCEPTION(FloatingPointError, ArithmeticError)
MP_DEFINE_EXCEPTION(OverflowError, ArithmeticError)
MP_DEFINE_EXCEPTION(ZeroDivisionError, ArithmeticError)
@ -235,18 +229,15 @@ MP_DEFINE_EXCEPTION(Exception, BaseException)
MP_DEFINE_EXCEPTION(ImportError, Exception)
//MP_DEFINE_EXCEPTION(IOError, Exception) use OSError instead
MP_DEFINE_EXCEPTION(LookupError, Exception)
MP_DEFINE_EXCEPTION_BASE(LookupError)
MP_DEFINE_EXCEPTION(IndexError, LookupError)
MP_DEFINE_EXCEPTION(KeyError, LookupError)
MP_DEFINE_EXCEPTION(MemoryError, Exception)
MP_DEFINE_EXCEPTION(NameError, Exception)
/*
MP_DEFINE_EXCEPTION_BASE(NameError)
MP_DEFINE_EXCEPTION(UnboundLocalError, NameError)
*/
MP_DEFINE_EXCEPTION(OSError, Exception)
#if MICROPY_PY_BUILTINS_TIMEOUTERROR
MP_DEFINE_EXCEPTION_BASE(OSError)
MP_DEFINE_EXCEPTION(TimeoutError, OSError)
#endif
/*
@ -267,30 +258,24 @@ MP_DEFINE_EXCEPTION(Exception, BaseException)
MP_DEFINE_EXCEPTION(ReferenceError, Exception)
*/
MP_DEFINE_EXCEPTION(RuntimeError, Exception)
MP_DEFINE_EXCEPTION_BASE(RuntimeError)
MP_DEFINE_EXCEPTION(NotImplementedError, RuntimeError)
MP_DEFINE_EXCEPTION(SyntaxError, Exception)
MP_DEFINE_EXCEPTION_BASE(SyntaxError)
MP_DEFINE_EXCEPTION(IndentationError, SyntaxError)
/*
MP_DEFINE_EXCEPTION_BASE(IndentationError)
MP_DEFINE_EXCEPTION(TabError, IndentationError)
*/
//MP_DEFINE_EXCEPTION(SystemError, Exception)
MP_DEFINE_EXCEPTION(TypeError, Exception)
#if MICROPY_EMIT_NATIVE
MP_DEFINE_EXCEPTION_BASE(TypeError)
MP_DEFINE_EXCEPTION(ViperTypeError, TypeError)
#endif
MP_DEFINE_EXCEPTION(ValueError, Exception)
#if MICROPY_PY_BUILTINS_STR_UNICODE
MP_DEFINE_EXCEPTION_BASE(ValueError)
MP_DEFINE_EXCEPTION(UnicodeError, ValueError)
//TODO: Implement more UnicodeError subclasses which take arguments
#endif
/*
MP_DEFINE_EXCEPTION(Warning, Exception)
MP_DEFINE_EXCEPTION_BASE(Warning)
MP_DEFINE_EXCEPTION(DeprecationWarning, Warning)
MP_DEFINE_EXCEPTION(PendingDeprecationWarning, Warning)
MP_DEFINE_EXCEPTION(RuntimeWarning, Warning)

View File

@ -134,8 +134,6 @@ STATIC mp_obj_t namedtuple_make_new(const mp_obj_type_t *type_in, size_t n_args,
return MP_OBJ_FROM_PTR(tuple);
}
STATIC const mp_rom_obj_tuple_t namedtuple_base_tuple = {{&mp_type_tuple}, 1, {MP_ROM_PTR(&mp_type_tuple)}};
STATIC mp_obj_t mp_obj_new_namedtuple_type(qstr name, size_t n_fields, mp_obj_t *fields) {
mp_obj_namedtuple_type_t *o = m_new_obj_var(mp_obj_namedtuple_type_t, qstr, n_fields);
memset(&o->base, 0, sizeof(o->base));
@ -148,7 +146,7 @@ STATIC mp_obj_t mp_obj_new_namedtuple_type(qstr name, size_t n_fields, mp_obj_t
o->base.attr = namedtuple_attr;
o->base.subscr = mp_obj_tuple_subscr;
o->base.getiter = mp_obj_tuple_getiter;
o->base.bases_tuple = (mp_obj_tuple_t*)(mp_rom_obj_tuple_t*)&namedtuple_base_tuple;
o->base.parent = &mp_type_tuple;
o->n_fields = n_fields;
for (size_t i = 0; i < n_fields; i++) {
o->fields[i] = mp_obj_str_get_qstr(fields[i]);

View File

@ -57,26 +57,34 @@ STATIC mp_obj_t mp_obj_new_instance(const mp_obj_type_t *class, size_t subobjs)
}
STATIC int instance_count_native_bases(const mp_obj_type_t *type, const mp_obj_type_t **last_native_base) {
size_t len = type->bases_tuple->len;
mp_obj_t *items = type->bases_tuple->items;
int count = 0;
for (size_t i = 0; i < len; i++) {
assert(MP_OBJ_IS_TYPE(items[i], &mp_type_type));
const mp_obj_type_t *bt = (const mp_obj_type_t *)MP_OBJ_TO_PTR(items[i]);
if (bt == &mp_type_object) {
// Not a "real" type
continue;
}
if (mp_obj_is_native_type(bt)) {
*last_native_base = bt;
count++;
for (;;) {
if (type == &mp_type_object) {
// Not a "real" type, end search here.
return count;
} else if (mp_obj_is_native_type(type)) {
// Native types don't have parents (at least not from our perspective) so end.
*last_native_base = type;
return count + 1;
} else if (type->parent == NULL) {
// No parents so end search here.
return count;
} else if (((mp_obj_base_t*)type->parent)->type == &mp_type_tuple) {
// Multiple parents, search through them all recursively.
const mp_obj_tuple_t *parent_tuple = type->parent;
const mp_obj_t *item = parent_tuple->items;
const mp_obj_t *top = item + parent_tuple->len;
for (; item < top; ++item) {
assert(MP_OBJ_IS_TYPE(*item, &mp_type_type));
const mp_obj_type_t *bt = (const mp_obj_type_t *)MP_OBJ_TO_PTR(*item);
count += instance_count_native_bases(bt, last_native_base);
}
return count;
} else {
count += instance_count_native_bases(bt, last_native_base);
// A single parent, use iteration to continue the search.
type = type->parent;
}
}
return count;
}
// TODO
@ -160,32 +168,31 @@ STATIC void mp_obj_class_lookup(struct class_lookup_data *lookup, const mp_obj_
// attribute not found, keep searching base classes
// for a const struct, this entry might be NULL
if (type->bases_tuple == NULL) {
if (type->parent == NULL) {
return;
}
size_t len = type->bases_tuple->len;
mp_obj_t *items = type->bases_tuple->items;
if (len == 0) {
return;
}
for (size_t i = 0; i < len - 1; i++) {
assert(MP_OBJ_IS_TYPE(items[i], &mp_type_type));
mp_obj_type_t *bt = (mp_obj_type_t*)MP_OBJ_TO_PTR(items[i]);
if (bt == &mp_type_object) {
// Not a "real" type
continue;
} else if (((mp_obj_base_t*)type->parent)->type == &mp_type_tuple) {
const mp_obj_tuple_t *parent_tuple = type->parent;
const mp_obj_t *item = parent_tuple->items;
const mp_obj_t *top = item + parent_tuple->len - 1;
for (; item < top; ++item) {
assert(MP_OBJ_IS_TYPE(*item, &mp_type_type));
mp_obj_type_t *bt = (mp_obj_type_t*)MP_OBJ_TO_PTR(*item);
if (bt == &mp_type_object) {
// Not a "real" type
continue;
}
mp_obj_class_lookup(lookup, bt);
if (lookup->dest[0] != MP_OBJ_NULL) {
return;
}
}
mp_obj_class_lookup(lookup, bt);
if (lookup->dest[0] != MP_OBJ_NULL) {
return;
}
}
// search last base (simple tail recursion elimination)
assert(MP_OBJ_IS_TYPE(items[len - 1], &mp_type_type));
type = (mp_obj_type_t*)MP_OBJ_TO_PTR(items[len - 1]);
// search last base (simple tail recursion elimination)
assert(MP_OBJ_IS_TYPE(*item, &mp_type_type));
type = (mp_obj_type_t*)MP_OBJ_TO_PTR(*item);
} else {
type = type->parent;
}
if (type == &mp_type_object) {
// Not a "real" type
return;
@ -946,14 +953,21 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict)
o->getiter = instance_getiter;
//o->iternext = ; not implemented
o->buffer_p.get_buffer = instance_get_buffer;
// Inherit protocol from a base class. This allows to define an
// abstract base class which would translate C-level protocol to
// Python method calls, and any subclass inheriting from it will
// support this feature.
if (len > 0) {
// Inherit protocol from a base class. This allows to define an
// abstract base class which would translate C-level protocol to
// Python method calls, and any subclass inheriting from it will
// support this feature.
o->protocol = ((mp_obj_type_t*)MP_OBJ_TO_PTR(items[0]))->protocol;
if (len >= 2) {
o->parent = MP_OBJ_TO_PTR(bases_tuple);
} else {
o->parent = MP_OBJ_TO_PTR(items[0]);
}
}
o->bases_tuple = MP_OBJ_TO_PTR(bases_tuple);
o->locals_dict = MP_OBJ_TO_PTR(locals_dict);
const mp_obj_type_t *native_base;
@ -1015,13 +1029,6 @@ STATIC void super_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
mp_obj_type_t *type = MP_OBJ_TO_PTR(self->type);
// for a const struct, this entry might be NULL
if (type->bases_tuple == NULL) {
return;
}
size_t len = type->bases_tuple->len;
mp_obj_t *items = type->bases_tuple->items;
struct class_lookup_data lookup = {
.obj = MP_OBJ_TO_PTR(self->obj),
.attr = attr,
@ -1029,13 +1036,27 @@ STATIC void super_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
.dest = dest,
.is_type = false,
};
for (size_t i = 0; i < len; i++) {
assert(MP_OBJ_IS_TYPE(items[i], &mp_type_type));
mp_obj_class_lookup(&lookup, (mp_obj_type_t*)MP_OBJ_TO_PTR(items[i]));
if (type->parent == NULL) {
// no parents, do nothing
} else if (((mp_obj_base_t*)type->parent)->type == &mp_type_tuple) {
const mp_obj_tuple_t *parent_tuple = type->parent;
size_t len = parent_tuple->len;
const mp_obj_t *items = parent_tuple->items;
for (size_t i = 0; i < len; i++) {
assert(MP_OBJ_IS_TYPE(items[i], &mp_type_type));
mp_obj_class_lookup(&lookup, (mp_obj_type_t*)MP_OBJ_TO_PTR(items[i]));
if (dest[0] != MP_OBJ_NULL) {
return;
}
}
} else {
mp_obj_class_lookup(&lookup, type->parent);
if (dest[0] != MP_OBJ_NULL) {
return;
}
}
mp_obj_class_lookup(&lookup, &mp_type_object);
}
@ -1073,27 +1094,28 @@ bool mp_obj_is_subclass_fast(mp_const_obj_t object, mp_const_obj_t classinfo) {
const mp_obj_type_t *self = MP_OBJ_TO_PTR(object);
// for a const struct, this entry might be NULL
if (self->bases_tuple == NULL) {
if (self->parent == NULL) {
// type has no parents
return false;
}
} else if (((mp_obj_base_t*)self->parent)->type == &mp_type_tuple) {
// get the base objects (they should be type objects)
const mp_obj_tuple_t *parent_tuple = self->parent;
const mp_obj_t *item = parent_tuple->items;
const mp_obj_t *top = item + parent_tuple->len - 1;
// get the base objects (they should be type objects)
size_t len = self->bases_tuple->len;
mp_obj_t *items = self->bases_tuple->items;
if (len == 0) {
return false;
}
// iterate through the base objects
for (size_t i = 0; i < len - 1; i++) {
if (mp_obj_is_subclass_fast(items[i], classinfo)) {
return true;
// iterate through the base objects
for (; item < top; ++item) {
if (mp_obj_is_subclass_fast(*item, classinfo)) {
return true;
}
}
}
// search last base (simple tail recursion elimination)
object = items[len - 1];
// search last base (simple tail recursion elimination)
object = *item;
} else {
// type has 1 parent
object = MP_OBJ_FROM_PTR(self->parent);
}
}
}