Shrink slots during GC only, split ReallocSlots into Alloc/Grow/ShrinkSlots (504478, r=igor).

This commit is contained in:
Andreas Gal 2009-07-16 18:42:54 -07:00
parent 241532b53a
commit f8892ed21b
6 changed files with 106 additions and 118 deletions

View File

@ -4360,7 +4360,7 @@ JS_CloneFunctionObject(JSContext *cx, JSObject *funobj, JSObject *parent)
uint32 nslots = JSSLOT_FREE(&js_FunctionClass);
JS_ASSERT(nslots == JS_INITIAL_NSLOTS);
nslots += js_FunctionClass.reserveSlots(cx, clone);
if (!js_ReallocSlots(cx, clone, nslots, JS_TRUE))
if (!js_AllocSlots(cx, clone, nslots))
return NULL;
JSUpvarArray *uva = JS_SCRIPT_UPVARS(fun->u.i.script);

View File

@ -1822,8 +1822,7 @@ EmitEnterBlock(JSContext *cx, JSParseNode *pn, JSCodeGenerator *cg)
}
OBJ_SCOPE(blockObj)->freeslot = JSSLOT_FREE(&js_BlockClass);
js_ReallocSlots(cx, blockObj, JSSLOT_FREE(&js_BlockClass), JS_TRUE);
return true;
return js_GrowSlots(cx, blockObj, JSSLOT_FREE(&js_BlockClass));
}
/*

View File

@ -968,7 +968,7 @@ js_PutCallObject(JSContext *cx, JSStackFrame *fp)
JS_LOCK_OBJ(cx, callobj);
n += JS_INITIAL_NSLOTS;
if (n > STOBJ_NSLOTS(callobj))
ok &= js_ReallocSlots(cx, callobj, n, JS_TRUE);
ok &= js_GrowSlots(cx, callobj, n);
scope = OBJ_SCOPE(callobj);
if (ok) {
memcpy(callobj->dslots, fp->argv, fun->nargs * sizeof(jsval));
@ -2516,7 +2516,7 @@ js_AllocFlatClosure(JSContext *cx, JSFunction *fun, JSObject *scopeChain)
uint32 nslots = JSSLOT_FREE(&js_FunctionClass);
JS_ASSERT(nslots == JS_INITIAL_NSLOTS);
nslots += fun_reserveSlots(cx, closure);
if (!js_ReallocSlots(cx, closure, nslots, JS_TRUE))
if (!js_GrowSlots(cx, closure, nslots))
return NULL;
return closure;

View File

@ -2049,7 +2049,7 @@ InitScopeForObject(JSContext* cx, JSObject* obj, JSObject* proto,
/* Let JSScope::create set freeslot so as to reserve slots. */
JS_ASSERT(scope->freeslot >= JSSLOT_PRIVATE);
if (scope->freeslot > JS_INITIAL_NSLOTS &&
!js_ReallocSlots(cx, obj, scope->freeslot, JS_TRUE)) {
!js_AllocSlots(cx, obj, scope->freeslot)) {
JSScope::destroy(cx, scope);
goto bad;
}
@ -2609,7 +2609,7 @@ js_PutBlockObject(JSContext *cx, JSBool normalUnwind)
if (normalUnwind && count > 1) {
--count;
JS_LOCK_OBJ(cx, obj);
if (!js_ReallocSlots(cx, obj, JS_INITIAL_NSLOTS + count, JS_TRUE))
if (!js_AllocSlots(cx, obj, JS_INITIAL_NSLOTS + count))
normalUnwind = JS_FALSE;
else
memcpy(obj->dslots, fp->slots + depth + 1, count * sizeof(jsval));
@ -2984,125 +2984,105 @@ bad:
#define DYNAMIC_WORDS_TO_SLOTS(words) \
(JS_ASSERT((words) > 1), (words) - 1 + JS_INITIAL_NSLOTS)
JSBool
js_ReallocSlots(JSContext *cx, JSObject *obj, uint32 nslots,
JSBool exactAllocation)
{
jsval *old, *slots;
uint32 oslots, nwords, owords, log, i;
bool
js_AllocSlots(JSContext *cx, JSObject *obj, size_t nslots)
{
JS_ASSERT(!obj->dslots);
JS_ASSERT(nslots > JS_INITIAL_NSLOTS);
jsval* slots;
slots = (jsval*) JS_malloc(cx, SLOTS_TO_DYNAMIC_WORDS(nslots) * sizeof(jsval));
if (!slots)
return true;
*slots++ = nslots;
/* clear the newly allocated cells. */
for (jsuint n = JS_INITIAL_NSLOTS; n < nslots; ++n)
slots[n - JS_INITIAL_NSLOTS] = JSVAL_VOID;
obj->dslots = slots;
return true;
}
bool
js_GrowSlots(JSContext *cx, JSObject *obj, size_t nslots)
{
/*
* Minimal number of dynamic slots to allocate.
*/
#define MIN_DYNAMIC_WORDS 4
const size_t MIN_DYNAMIC_WORDS = 4;
/*
* The limit to switch to linear allocation strategy from the power of 2
* growth no to waste too much memory.
*/
#define LINEAR_GROWTH_STEP JS_BIT(16)
const size_t LINEAR_GROWTH_STEP = JS_BIT(16);
old = obj->dslots;
if (nslots <= JS_INITIAL_NSLOTS) {
if (old &&
(exactAllocation ||
SLOTS_TO_DYNAMIC_WORDS((uint32)old[-1]) != MIN_DYNAMIC_WORDS ||
nslots <= (JS_INITIAL_NSLOTS +
JSSLOT_FREE(STOBJ_GET_CLASS(obj))) / 2)) {
/*
* We do not want to free dynamic slots when allocation is a hint,
* we reached minimal allocation and almost all fixed slots are
* used. It avoids allocating dynamic slots again when properties
* are added to the object.
*
* If there were no private or reserved slots, the condition to
* free the slots would be
*
* nslots <= JS_INITIAL_NSLOTS / 2
*
* but to account for never removed slots before JSSLOT_FREE(class)
* we need to subtract it from the slot counts which gives
*
* nslots - JSSLOT_FREE <= (JS_INITIAL_NSLOTS - JSSLOT_FREE) / 2
*
* or
*
* nslots <= (JS_INITIAL_NSLOTS + JSSLOT_FREE) / 2
*/
js_FreeSlots(cx, obj);
}
/* If we are allocating fslots, there is nothing to do. */
if (nslots <= JS_INITIAL_NSLOTS)
return JS_TRUE;
}
oslots = (old) ? (uint32)*--old : JS_INITIAL_NSLOTS;
nwords = SLOTS_TO_DYNAMIC_WORDS(nslots);
size_t nwords = SLOTS_TO_DYNAMIC_WORDS(nslots);
if (nslots > oslots) {
if (!exactAllocation) {
/*
* Round up nslots so the number of bytes in dslots array is power
* of 2 to ensure exponential grouth.
*/
if (nwords <= MIN_DYNAMIC_WORDS) {
nwords = MIN_DYNAMIC_WORDS;
} else if (nwords < LINEAR_GROWTH_STEP) {
JS_CEILING_LOG2(log, nwords);
nwords = JS_BIT(log);
} else {
nwords = JS_ROUNDUP(nwords, LINEAR_GROWTH_STEP);
}
}
slots = (jsval *)JS_realloc(cx, old, nwords * sizeof(jsval));
if (!slots)
return JS_FALSE;
/*
* Round up nslots so the number of bytes in dslots array is power
* of 2 to ensure exponential grouth.
*/
uintN log;
if (nwords <= MIN_DYNAMIC_WORDS) {
nwords = MIN_DYNAMIC_WORDS;
} else if (nwords < LINEAR_GROWTH_STEP) {
JS_CEILING_LOG2(log, nwords);
nwords = JS_BIT(log);
} else {
JS_ASSERT(nslots < oslots);
if (!exactAllocation) {
owords = DYNAMIC_WORDS_TO_SLOTS(oslots);
if (owords <= MIN_DYNAMIC_WORDS)
return JS_TRUE;
if (owords < LINEAR_GROWTH_STEP * 2) {
/*
* Shrink only if 1/4 of slots are left and we need to grow
* the array at least twice to reach the current capacity. It
* prevents frequent capacity growth/shrinking when slots are
* often removed and added.
*/
if (nwords > owords / 4)
return JS_TRUE;
JS_CEILING_LOG2(log, nwords);
nwords = JS_BIT(log);
if (nwords < MIN_DYNAMIC_WORDS)
nwords = MIN_DYNAMIC_WORDS;
} else {
/*
* Shrink only if we free at least 2 linear allocation
* segments, to prevent growth/shrinking resonance.
*/
if (nwords > owords - LINEAR_GROWTH_STEP * 2)
return JS_TRUE;
nwords = JS_ROUNDUP(nwords, LINEAR_GROWTH_STEP);
}
}
/* We avoid JS_realloc not to report a failed shrink attempt. */
slots = (jsval *)realloc(old, nwords * sizeof(jsval));
if (!slots)
slots = old;
nwords = JS_ROUNDUP(nwords, LINEAR_GROWTH_STEP);
}
nslots = DYNAMIC_WORDS_TO_SLOTS(nwords);
*slots++ = (jsval)nslots;
/*
* If nothing was allocated yet, treat it as initial allocation (but with
* the exponential growth algorithm applied).
*/
jsval* slots = obj->dslots;
if (!slots)
return js_AllocSlots(cx, obj, nslots);
size_t oslots = size_t(slots[-1]);
slots = (jsval*) JS_realloc(cx, slots - 1, nwords * sizeof(jsval));
*slots++ = nslots;
obj->dslots = slots;
/* If we're extending an allocation, initialize free slots. */
for (i = oslots; i < nslots; i++)
/* Initialize the additional slots we added. */
JS_ASSERT(nslots > oslots);
for (size_t i = oslots; i < nslots; i++)
slots[i - JS_INITIAL_NSLOTS] = JSVAL_VOID;
return JS_TRUE;
return true;
}
#undef LINEAR_GROWTH_STEP
#undef MIN_DYNAMIC_WORDS
void
js_ShrinkSlots(JSContext *cx, JSObject *obj, size_t nslots)
{
jsval* slots = obj->dslots;
/* Nothing to shrink? */
if (!slots)
return;
JS_ASSERT(size_t(slots[-1]) > JS_INITIAL_NSLOTS);
JS_ASSERT(nslots <= size_t(slots[-1]));
if (nslots <= JS_INITIAL_NSLOTS) {
JS_free(cx, slots - 1);
obj->dslots = NULL;
} else {
size_t nwords = SLOTS_TO_DYNAMIC_WORDS(nslots);
slots = (jsval*) JS_realloc(cx, slots - 1, nwords * sizeof(jsval));
*slots++ = nslots;
obj->dslots = slots;
}
}
extern JSBool
@ -3408,7 +3388,7 @@ js_AllocSlot(JSContext *cx, JSObject *obj, uint32 *slotp)
}
if (scope->freeslot >= STOBJ_NSLOTS(obj) &&
!js_ReallocSlots(cx, obj, scope->freeslot + 1, JS_FALSE)) {
!js_GrowSlots(cx, obj, scope->freeslot + 1)) {
return JS_FALSE;
}
@ -3425,12 +3405,8 @@ js_FreeSlot(JSContext *cx, JSObject *obj, uint32 slot)
JSScope *scope = OBJ_SCOPE(obj);
LOCKED_OBJ_SET_SLOT(obj, slot, JSVAL_VOID);
if (scope->freeslot == slot + 1) {
if (scope->freeslot == slot + 1)
scope->freeslot = slot;
/* When shrinking, js_ReallocSlots always returns true. */
js_ReallocSlots(cx, obj, slot, JS_FALSE);
}
}
@ -5785,6 +5761,13 @@ js_TraceObject(JSTracer *trc, JSObject *obj)
}
if (traceScope) {
if (IS_GC_MARKING_TRACER(trc)) {
/* Check whether we should shrink the object's slots. */
size_t slots = scope->freeslot;
if (STOBJ_NSLOTS(obj) != slots)
js_ShrinkSlots(cx, obj, slots);
}
#ifdef JS_DUMP_SCOPE_METERS
MeterEntryCount(scope->entryCount);
#endif
@ -5917,7 +5900,7 @@ js_SetRequiredSlot(JSContext *cx, JSObject *obj, uint32 slot, jsval v)
if (clasp->reserveSlots)
nslots += clasp->reserveSlots(cx, obj);
JS_ASSERT(slot < nslots);
if (!js_ReallocSlots(cx, obj, nslots, JS_TRUE)) {
if (!js_AllocSlots(cx, obj, nslots)) {
JS_UNLOCK_SCOPE(cx, scope);
return JS_FALSE;
}

View File

@ -575,14 +575,20 @@ js_AllocSlot(JSContext *cx, JSObject *obj, uint32 *slotp);
extern void
js_FreeSlot(JSContext *cx, JSObject *obj, uint32 slot);
extern bool
js_AllocSlots(JSContext *cx, JSObject *obj, size_t nslots);
extern bool
js_GrowSlots(JSContext *cx, JSObject *obj, size_t nslots);
extern void
js_ShrinkSlots(JSContext *cx, JSObject *obj, size_t nslots);
static inline void
js_FreeSlots(JSContext *cx, JSObject *obj)
{
if (obj->dslots) {
JS_ASSERT((uint32)obj->dslots[-1] > JS_INITIAL_NSLOTS);
JS_free(cx, obj->dslots - 1);
obj->dslots = NULL;
}
if (obj->dslots)
js_ShrinkSlots(cx, obj, 0);
}
extern jsid

View File

@ -3071,7 +3071,7 @@ BindLet(JSContext *cx, BindData *data, JSAtom *atom, JSTreeContext *tc)
*/
uintN slot = JSSLOT_FREE(&js_BlockClass) + n;
if (slot >= STOBJ_NSLOTS(blockObj) &&
!js_ReallocSlots(cx, blockObj, slot + 1, JS_FALSE)) {
!js_GrowSlots(cx, blockObj, slot + 1)) {
return JS_FALSE;
}
OBJ_SCOPE(blockObj)->freeslot = slot + 1;