Feature #7457

GC.stat to return "allocated object count" and "freed object count"

Added by Koichi Sasada over 1 year ago. Updated over 1 year ago.

[ruby-core:50264]
Status:Closed
Priority:Normal
Assignee:Narihiro Nakamura
Category:core
Target version:2.0.0

Description

How about to return "allocated object count" and "freed object count"?

The following patch enable to show "total allocated object number"
and "total freed (deallocated) object number".

pp GC.stat #=>
{:count=>0,
:heapused=>12,
:heap
length=>12,
:heapincrement=>0,
:heap
livenum=>7494,
:heap
freenum=>0,
:heap
finalnum=>0,
:heap
allocatednum=>7585, # <= new one!
:heap
freed_num=>88} # <= new one!

Maybe performance has mostly no impact with this patch.

Exact live object number can be calculated by "heapallocatednum - heapfreednum".

These values will be overflow. So they are only hint of performance tuning.

Index: gc.c

--- gc.c (revision 37946)
+++ gc.c (working copy)
@@ -225,7 +225,8 @@ typedef struct rbobjspace {
struct heaps
freebitmap *freebitmap;
RVALUE *range[2];
struct heapsheader *freed;
- size
t livenum;
+ size
t allocatednum;
+ size
t freednum;
size
t freenum;
size
t freemin;
size
t finalnum;
@@ -352,8 +353,6 @@ static inline void gc
profmarktimerst
static inline void gc
profsweeptimerstart(rbobjspacet *);
static inline void gc
profsweeptimerstop(rbobjspacet *);
static inline void gc
profsetmallocinfo(rbobjspacet *);
-static inline void gc
profinclivenum(rbobjspacet *);
-static inline void gc
profdeclivenum(rbobjspace_t *);

/*
@@ -531,7 +530,6 @@ assignheapslot(rbobjspacet *objspace
objspace->heap.sorted[hi]->bits = (uintptrt *)objspace->heap.freebitmap;
objspace->heap.freebitmap = objspace->heap.freebitmap->next;
memset(heaps->bits, 0, HEAPBITMAPLIMIT * sizeof(uintptrt));
- objspace->heap.free
num += objs;
pend = p + objs;
if (lomem == 0 || lomem > p) lomem = p;
if (himem < pend) himem = pend;
@@ -660,7 +658,7 @@ newobj(VALUE klass, VALUE flags)
RANY(obj)->file = rbsourcefile();
RANY(obj)->line = rb
sourceline();
#endif
- gcprofinclivenum(objspace);
+ objspace->heap.allocated_num++;

 return obj;

}
@@ -1422,7 +1420,8 @@ finalizelist(rbobjspacet *objspace, R
if (!FL
TEST(p, FLSINGLETON)) { /* not freeing page */
add
slotlocalfreelist(objspace, p);
if (!islazysweeping(objspace)) {
- gcprofdeclivenum(objspace);
+ objspace->heap.freednum++;
+ objspace->heap.free
num++;
}
}
else {
@@ -1873,10 +1872,16 @@ gcclearslotbits(struct heapsslot *sl
memset(slot->bits, 0, HEAPBITMAPLIMIT * sizeof(uintptr_t));
}

+static sizet
+objspace
livenum(rbobjspacet *objspace)
+{
+ return objspace->heap.allocated
num - objspace->heap.freednum;
+}
+
static void
slot
sweep(rbobjspacet *objspace, struct heapsslot *sweepslot)
{
- sizet freenum = 0, finalnum = 0;
+ size
t emptynum = 0, freednum = 0, finalnum = 0;
RVALUE *p, *pend;
RVALUE *final = deferred
finallist;
int deferred;
@@ -1903,17 +1908,17 @@ slot
sweep(rbobjspacet *objspace, stru
p->as.free.flags = 0;
p->as.free.next = sweepslot->freelist;
sweep
slot->freelist = p;
- freenum++;
+ freed
num++;
}
}
else {
- freenum++;
+ empty
num++;
}
}
p++;
}
gcclearslotbits(sweepslot);
- if (finalnum + freenum == sweepslot->header->limit &&
+ if (final
num + freednum + emptynum == sweepslot->header->limit &&
objspace->heap.free
num > objspace->heap.doheapfree) {
RVALUE *pp;

@@ -1925,13 +1930,14 @@ slotsweep(rbobjspacet *objspace, stru
unlink
heapslot(objspace, sweepslot);
}
else {
- if (freenum > 0) {
+ if (freed
num + emptynum > 0) {
link
freeheapslot(objspace, sweepslot);
}
else {
sweep
slot->freenext = NULL;
}
- objspace->heap.free
num += freenum;
+ objspace->heap.freed
num += freednum;
+ objspace->heap.free
num += freednum + emptynum;
}
objspace->heap.finalnum += finalnum;

@@ -1990,7 +1996,8 @@ aftergcsweep(rbobjspacet *objspace)

 inc = ATOMIC_SIZE_EXCHANGE(malloc_increase, 0);
 if (inc > malloc_limit) {
  • malloclimit += (sizet)((inc - malloclimit) * (double)objspace->heap.livenum / (heapsused * HEAPOBJ_LIMIT));
  • malloc_limit +=
  • (sizet)((inc - malloclimit) * (double)objspacelivenum(objspace) / (heapsused * HEAPOBJLIMIT)); if (malloclimit < initialmalloclimit) malloclimit = initialmalloc_limit; }

@@ -2063,7 +2070,7 @@ gcpreparefreeobjects(rbobjspacet *o
gc
marks(objspace);

 before_gc_sweep(objspace);
  • if (objspace->heap.freemin > (heapsused * HEAPOBJLIMIT - objspace->heap.live_num)) {
  • if (objspace->heap.freemin > (heapsused * HEAPOBJLIMIT - objspacelivenum(objspace))) { setheapsincrement(objspace); }

@@ -2544,7 +2551,6 @@ gcmarkptr(rbobjspacet *objspace, VAL
register uintptrt *bits = GETHEAPBITMAP(ptr);
if (MARKED
INBITMAP(bits, ptr)) return 0;
MARK
INBITMAP(bits, ptr);
- objspace->heap.live
num++;
return 1;
}

@@ -2905,11 +2911,8 @@ gcmarks(rbobjspacet *objspace)
objspace->mark
func_data = 0;

 gc_prof_mark_timer_start(objspace);

  • objspace->heap.live_num = 0; objspace->count++;

 SET_STACK_END;

 th->vm->self ? rb_gc_mark(th->vm->self) : rb_vm_mark(th->vm);

@@ -2956,7 +2959,8 @@ rbgcforcerecycle(VALUE p)
add
slotlocalfreelist(objspace, (RVALUE *)p);
}
else {
- gcprofdeclivenum(objspace);
+ objspace->heap.freednum++;
+ objspace->heap.free
num++;
slot = addslotlocalfreelist(objspace, (RVALUE *)p);
if (slot->free
next == NULL) {
linkfreeheapslot(objspace, slot);
@@ -3172,9 +3176,11 @@ gc
stat(int argc, VALUE *argv, VALUE sel
rbhashaset(hash, ID2SYM(rbintern("heapused")), SIZET2NUM(objspace->heap.used));
rbhashaset(hash, ID2SYM(rbintern("heaplength")), SIZET2NUM(objspace->heap.length));
rbhashaset(hash, ID2SYM(rbintern("heapincrement")), SIZET2NUM(objspace->heap.increment));
- rbhashaset(hash, ID2SYM(rbintern("heaplivenum")), SIZET2NUM(objspace->heap.livenum));
+ rbhashaset(hash, ID2SYM(rbintern("heaplivenum")), SIZET2NUM(objspacelivenum(objspace)));
rb
hashaset(hash, ID2SYM(rbintern("heapfreenum")), SIZET2NUM(objspace->heap.freenum));
rb
hashaset(hash, ID2SYM(rbintern("heapfinalnum")), SIZET2NUM(objspace->heap.finalnum));
+ rb
hashaset(hash, ID2SYM(rbintern("heapallocatednum")), SIZET2NUM(objspace->heap.allocatednum));
+ rb
hashaset(hash, ID2SYM(rbintern("heapfreednum")), SIZET2NUM(objspace->heap.freed_num));
return hash;
}

@@ -3952,7 +3958,7 @@ gcprofsetmallocinfo(rbobjspacet *o
static inline void
gcprofsetheapinfo(rbobjspacet *objspace, gcprofilerecord *record)
{
- sizet live = objspace->heap.livenum;
+ sizet live = objspacelivenum(objspace);
size
t total = heapsused * HEAPOBJ_LIMIT;

 record->heap_total_objects = total;

@@ -3960,16 +3966,6 @@ gcprofsetheapinfo(rbobjspacet *obj
record->heaptotalsize = total * sizeof(RVALUE);
}

-static inline void
-gcprofinclivenum(rbobjspacet *objspace)
-{

-}

-static inline void
-gcprofdeclivenum(rbobjspacet *objspace)
-{

-}

#else

static inline void
@@ -4057,18 +4053,6 @@ gcprofsetheapinfo(rbobjspacet *obj
record->havefinalize = deferredfinallist ? Qtrue : Qfalse;
record->heap
usesize = live * sizeof(RVALUE);
record->heap
total_size = total * sizeof(RVALUE);

-}

-static inline void
-gcprofinclivenum(rbobjspacet *objspace)
-{
- objspace->heap.live_num++;

-}

-static inline void
-gcprofdeclivenum(rbobjspacet *objspace)
-{
- objspace->heap.live_num--;
}

#endif /* !GCPROFILEMORE_DETAIL */

History

#1 Updated by Jeremy Kemper over 1 year ago

Yes!! A million times yes. Tracking total allocations makes it possible to profile Ruby code by object creation instead of time. This is very useful, often more so than profiling process time, because reducing excessive object creation will massively speed up GC and reduce the max # of heaps the VM needs, so lower memory for the process, all due to less object churn.

The ruby-prof gem supports object allocation profiling for 1.8.x (REE patches). The REE patches provide rbosallocatedobjects and ObjectSpace.allocatedobjects. Ideally, we would have a simple reader method like this to avoid creating lots of GC.stat Hash objects.

Even better, the benchmark.rb stdlib could then support benchmarking by objects created and objects created per second in addition to process time!

#2 Updated by Narihiro Nakamura over 1 year ago

Could you commit it?

Thanks!

#3 Updated by Koichi Sasada over 1 year ago

(2012/11/29 5:06), bitsweat (Jeremy Kemper) wrote:

The ruby-prof gem supports object allocation profiling for 1.8.x (REE patches). The REE patches provide rbosallocatedobjects and ObjectSpace.allocatedobjects. Ideally, we would have a simple reader method like this to avoid creating lots of GC.stat Hash objects.

You can avoid to create Hash object using 1st parameter.

h = {}
r = 10.times.map{
GC.stat(h); h[:heaptotalallocated_num]
}
p r #=> [2422, 2423, 2423, 2423, 2423, 2423, 2423, 2423, 2423, 2423]

--
// SASADA Koichi at atdot dot net

#4 Updated by Koichi Sasada over 1 year ago

  • Status changed from Open to Closed

Finally, I introduce two keys.

  * total_allocated_object: total allocated object number.
  * total_freed_object: total freed object number.

I remove "heap" prefix because this information is not about current "heap".

Give us comments.

Thanks,
Koichi

Also available in: Atom PDF