Skip to content

Commit 4902fc0

Browse files
casperisfinezhangskz
authored andcommitted
Ruby implement memsize functions for native types (#10291)
Fix: #10280 This allows Ruby to report a more correct estimation of the memory used by these objects. It's useful when running memory profilers against applications. cc @zhangskz @haberman Closes #10291 COPYBARA_INTEGRATE_REVIEW=#10291 from casperisfine:ruby-sizes 9150795 PiperOrigin-RevId: 606718632
1 parent f6b108a commit 4902fc0

6 files changed

Lines changed: 57 additions & 5 deletions

File tree

ruby/ext/google/protobuf_c/map.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ static void Map_mark(void* _self) {
3838
rb_gc_mark(self->arena);
3939
}
4040

41+
static size_t Map_memsize(const void* _self) { return sizeof(Map); }
42+
4143
const rb_data_type_t Map_type = {
4244
"Google::Protobuf::Map",
43-
{Map_mark, RUBY_DEFAULT_FREE, NULL},
45+
{Map_mark, RUBY_DEFAULT_FREE, Map_memsize},
4446
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
4547
};
4648

ruby/ext/google/protobuf_c/message.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,11 @@ static void Message_mark(void* _self) {
4444
rb_gc_mark(self->arena);
4545
}
4646

47+
static size_t Message_memsize(const void* _self) { return sizeof(Message); }
48+
4749
static rb_data_type_t Message_type = {
4850
"Google::Protobuf::Message",
49-
{Message_mark, RUBY_DEFAULT_FREE, NULL},
51+
{Message_mark, RUBY_DEFAULT_FREE, Message_memsize},
5052
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
5153
};
5254

ruby/ext/google/protobuf_c/protobuf.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,23 @@ static void Arena_free(void *data) {
164164
xfree(arena);
165165
}
166166

167+
static size_t Arena_memsize(const void *data) {
168+
const Arena *arena = data;
169+
size_t fused_count;
170+
size_t memsize = upb_Arena_SpaceAllocated(arena->arena, &fused_count);
171+
if (fused_count > 1) {
172+
// If other arena were fused we attribute an equal
173+
// share of memory usage to each one.
174+
memsize /= fused_count;
175+
}
176+
return memsize + sizeof(Arena);
177+
}
178+
167179
static VALUE cArena;
168180

169181
const rb_data_type_t Arena_type = {
170182
"Google::Protobuf::Internal::Arena",
171-
{Arena_mark, Arena_free, NULL},
183+
{Arena_mark, Arena_free, Arena_memsize},
172184
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
173185
};
174186

ruby/tests/memory_test.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/ruby
2+
#
3+
# generated_code.rb is in the same directory as this test.
4+
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
5+
6+
require 'test/unit'
7+
require 'objspace'
8+
require 'test_import_pb'
9+
10+
$is_64bit = Google::Protobuf::Internal::SIZEOF_LONG == 8
11+
12+
class MemoryTest < Test::Unit::TestCase
13+
# 40 byte is the default object size. But the real size is dependent on many things
14+
# such as arch etc, so there's no point trying to assert the exact return value here.
15+
# We merely assert that we return something other than the default.
16+
def test_objspace_memsize_of_arena
17+
if $is_64bit
18+
assert_operator 40, :<, ObjectSpace.memsize_of(Google::Protobuf::Internal::Arena.new)
19+
end
20+
end
21+
22+
def test_objspace_memsize_of_message
23+
if $is_64bit
24+
assert_operator 40, :<, ObjectSpace.memsize_of(FooBar::TestImportedMessage.new)
25+
end
26+
end
27+
28+
def test_objspace_memsize_of_map
29+
if $is_64bit
30+
assert_operator 40, :<, ObjectSpace.memsize_of(Google::Protobuf::Map.new(:string, :int32))
31+
end
32+
end
33+
end

upb/mem/arena.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,10 @@ static upb_ArenaRoot _upb_Arena_FindRoot(upb_Arena* a) {
155155
return (upb_ArenaRoot){.root = ai, .tagged_count = poc};
156156
}
157157

158-
size_t upb_Arena_SpaceAllocated(upb_Arena* arena) {
158+
size_t upb_Arena_SpaceAllocated(upb_Arena* arena, size_t* fused_count) {
159159
upb_ArenaInternal* ai = _upb_Arena_FindRoot(arena).root;
160160
size_t memsize = 0;
161+
size_t local_fused_count = 0;
161162

162163
while (ai != NULL) {
163164
upb_MemBlock* block = upb_Atomic_Load(&ai->blocks, memory_order_relaxed);
@@ -166,8 +167,10 @@ size_t upb_Arena_SpaceAllocated(upb_Arena* arena) {
166167
block = upb_Atomic_Load(&block->next, memory_order_relaxed);
167168
}
168169
ai = upb_Atomic_Load(&ai->next, memory_order_relaxed);
170+
local_fused_count++;
169171
}
170172

173+
if (fused_count) *fused_count = local_fused_count;
171174
return memsize;
172175
}
173176

upb/mem/arena.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ UPB_API bool upb_Arena_Fuse(upb_Arena* a, upb_Arena* b);
4646
bool upb_Arena_IncRefFor(upb_Arena* a, const void* owner);
4747
void upb_Arena_DecRefFor(upb_Arena* a, const void* owner);
4848

49-
size_t upb_Arena_SpaceAllocated(upb_Arena* a);
49+
size_t upb_Arena_SpaceAllocated(upb_Arena* a, size_t* fused_count);
5050
uint32_t upb_Arena_DebugRefCount(upb_Arena* a);
5151

5252
UPB_API_INLINE upb_Arena* upb_Arena_New(void) {

0 commit comments

Comments
 (0)