Skip to content

Commit

Permalink
Ruby: Use class inheritance to save memory
Browse files Browse the repository at this point in the history
The only difference between all the `Message` classes is just
their `descriptor` instance variable.

So rather than create an entirely new class from scratch every time
we can simply inherit from an abstract class.

This shink each `Message` class from `1456` bytes to `944` bytes, and
the singleton class of each from `960` to `792`, for a total of
`680` bytes saved per message class, so a ~28% reduction.
  • Loading branch information
byroot committed Aug 10, 2022
1 parent b36782a commit 7309e88
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 29 deletions.
61 changes: 32 additions & 29 deletions ruby/ext/google/protobuf_c/message.c
Expand Up @@ -37,6 +37,7 @@
#include "repeated_field.h"

static VALUE cParseError = Qnil;
static VALUE cAbstractMessage = Qnil;
static ID descriptor_instancevar_interned;

static VALUE initialize_rb_class_with_no_args(VALUE klass) {
Expand Down Expand Up @@ -1201,36 +1202,8 @@ VALUE build_class_from_descriptor(VALUE descriptor) {
klass = rb_define_class_id(
// Docs say this parameter is ignored. User will assign return value to
// their own toplevel constant class name.
rb_intern("Message"), rb_cObject);
rb_intern("Message"), cAbstractMessage);
rb_ivar_set(klass, descriptor_instancevar_interned, descriptor);
rb_define_alloc_func(klass, Message_alloc);
rb_require("google/protobuf/message_exts");
rb_include_module(klass, rb_eval_string("::Google::Protobuf::MessageExts"));
rb_extend_object(
klass, rb_eval_string("::Google::Protobuf::MessageExts::ClassMethods"));

rb_define_method(klass, "method_missing", Message_method_missing, -1);
rb_define_method(klass, "respond_to_missing?", Message_respond_to_missing,
-1);
rb_define_method(klass, "initialize", Message_initialize, -1);
rb_define_method(klass, "dup", Message_dup, 0);
// Also define #clone so that we don't inherit Object#clone.
rb_define_method(klass, "clone", Message_dup, 0);
rb_define_method(klass, "==", Message_eq, 1);
rb_define_method(klass, "eql?", Message_eq, 1);
rb_define_method(klass, "freeze", Message_freeze, 0);
rb_define_method(klass, "hash", Message_hash, 0);
rb_define_method(klass, "to_h", Message_to_h, 0);
rb_define_method(klass, "inspect", Message_inspect, 0);
rb_define_method(klass, "to_s", Message_inspect, 0);
rb_define_method(klass, "[]", Message_index, 1);
rb_define_method(klass, "[]=", Message_index_set, 2);
rb_define_singleton_method(klass, "decode", Message_decode, -1);
rb_define_singleton_method(klass, "encode", Message_encode, -1);
rb_define_singleton_method(klass, "decode_json", Message_decode_json, -1);
rb_define_singleton_method(klass, "encode_json", Message_encode_json, -1);
rb_define_singleton_method(klass, "descriptor", Message_descriptor, 0);

return klass;
}

Expand Down Expand Up @@ -1392,8 +1365,38 @@ const upb_Message* Message_GetUpbMessage(VALUE value, const upb_MessageDef* m,
return self->msg;
}

static void Message_define_class(VALUE klass) {
rb_define_alloc_func(klass, Message_alloc);

rb_require("google/protobuf/message_exts");
rb_define_method(klass, "method_missing", Message_method_missing, -1);
rb_define_method(klass, "respond_to_missing?", Message_respond_to_missing,
-1);
rb_define_method(klass, "initialize", Message_initialize, -1);
rb_define_method(klass, "dup", Message_dup, 0);
// Also define #clone so that we don't inherit Object#clone.
rb_define_method(klass, "clone", Message_dup, 0);
rb_define_method(klass, "==", Message_eq, 1);
rb_define_method(klass, "eql?", Message_eq, 1);
rb_define_method(klass, "freeze", Message_freeze, 0);
rb_define_method(klass, "hash", Message_hash, 0);
rb_define_method(klass, "to_h", Message_to_h, 0);
rb_define_method(klass, "inspect", Message_inspect, 0);
rb_define_method(klass, "to_s", Message_inspect, 0);
rb_define_method(klass, "[]", Message_index, 1);
rb_define_method(klass, "[]=", Message_index_set, 2);
rb_define_singleton_method(klass, "decode", Message_decode, -1);
rb_define_singleton_method(klass, "encode", Message_encode, -1);
rb_define_singleton_method(klass, "decode_json", Message_decode_json, -1);
rb_define_singleton_method(klass, "encode_json", Message_encode_json, -1);
rb_define_singleton_method(klass, "descriptor", Message_descriptor, 0);
}

void Message_register(VALUE protobuf) {
cParseError = rb_const_get(protobuf, rb_intern("ParseError"));
cAbstractMessage = rb_define_class_under(protobuf, "AbstractMessage", rb_cObject);
Message_define_class(cAbstractMessage);
rb_gc_register_address(&cAbstractMessage);

// Ruby-interned string: "descriptor". We use this identifier to store an
// instance variable on message classes we create in order to link them back
Expand Down
5 changes: 5 additions & 0 deletions ruby/lib/google/protobuf/message_exts.rb
Expand Up @@ -49,5 +49,10 @@ def to_proto(options = {})
end

end
class AbstractMessage
include MessageExts
extend MessageExts::ClassMethods
end
private_constant :AbstractMessage
end
end

0 comments on commit 7309e88

Please sign in to comment.