bug 603318 - make dense array slow during array growth, not during the GC. r=bhackett

This commit is contained in:
Igor Bukanov
2010-10-14 16:12:19 +02:00
parent de3ddcf18f
commit 84db6eec8e
9 changed files with 414 additions and 148 deletions

View File

@@ -62,9 +62,9 @@
*
* Arrays are converted to use js_SlowArrayClass when any of these conditions
* are met:
* - the load factor (COUNT / capacity) is less than 0.25, and there are
* more than MIN_SPARSE_INDEX slots total
* - a property is set that is not indexed (and not "length"); or
* - there are more than MIN_SPARSE_INDEX slots total
* - the load factor (COUNT / capacity) is less than 0.25
* - a property is set that is not indexed (and not "length")
* - a property is defined that has non-default property attributes.
*
* Dense arrays do not track property creation order, so unlike other native
@@ -115,30 +115,6 @@ using namespace js::gc;
#define MAXINDEX 4294967295u
#define MAXSTR "4294967295"
/*
* Use the limit on number of object slots for sanity and consistency (see the
* assertion in JSObject::makeDenseArraySlow).
*/
static inline bool
INDEX_TOO_BIG(jsuint index)
{
return index >= JSObject::NSLOTS_LIMIT;
}
static inline bool
INDEX_TOO_SPARSE(JSObject *array, jsuint index)
{
/* Small arrays with less than 256 elements are dense, no matter what. */
if (index < 256)
return false;
/*
* Otherwise if the index becomes too large or is more than 256 past
* the current capacity, we have to slowify.
*/
return INDEX_TOO_BIG(index) || (index > array->getDenseArrayCapacity() + 256);
}
static inline bool
ENSURE_SLOW_ARRAY(JSContext *cx, JSObject *obj)
{
@@ -310,6 +286,34 @@ BigIndexToId(JSContext *cx, JSObject *obj, jsuint index, JSBool createAtom,
return JS_TRUE;
}
bool
JSObject::willBeSparseDenseArray(uintN requiredCapacity, uintN newElementsHint)
{
JS_ASSERT(isDenseArray());
JS_ASSERT(requiredCapacity > MIN_SPARSE_INDEX);
uintN cap = numSlots();
JS_ASSERT(requiredCapacity >= cap);
if (requiredCapacity >= JSObject::NSLOTS_LIMIT)
return true;
uintN minimalDenseCount = requiredCapacity / 4;
if (newElementsHint >= minimalDenseCount)
return false;
minimalDenseCount -= newElementsHint;
if (minimalDenseCount > cap)
return true;
Value *elems = getDenseArrayElements();
for (uintN i = 0; i < cap; i++) {
if (!elems[i].isMagic(JS_ARRAY_HOLE) && !--minimalDenseCount)
return false;
}
return true;
}
static bool
ReallyBigIndexToId(JSContext* cx, jsdouble index, jsid* idp)
{
@@ -439,19 +443,23 @@ SetArrayElement(JSContext *cx, JSObject *obj, jsdouble index, const Value &v)
if (obj->isDenseArray()) {
/* Predicted/prefetched code should favor the remains-dense case. */
if (index <= jsuint(-1)) {
JSObject::EnsureDenseResult result = JSObject::ED_SPARSE;
do {
if (index > jsuint(-1))
break;
jsuint idx = jsuint(index);
if (!INDEX_TOO_SPARSE(obj, idx)) {
JS_ASSERT(idx + 1 > idx);
if (!obj->ensureDenseArrayElements(cx, idx + 1))
return JS_FALSE;
if (idx >= obj->getArrayLength())
obj->setArrayLength(idx + 1);
obj->setDenseArrayElement(idx, v);
return JS_TRUE;
}
}
result = obj->ensureDenseArrayElements(cx, idx, 1);
if (result != JSObject::ED_OK)
break;
if (idx >= obj->getArrayLength())
obj->setArrayLength(idx + 1);
obj->setDenseArrayElement(idx, v);
return true;
} while (false);
if (result == JSObject::ED_FAILED)
return false;
JS_ASSERT(result == JSObject::ED_SPARSE);
if (!obj->makeDenseArraySlow(cx))
return JS_FALSE;
}
@@ -474,13 +482,7 @@ js_EnsureDenseArrayCapacity(JSContext *cx, JSObject *obj, jsint i)
Class *origObjClasp = obj->clasp;
#endif
jsuint u = jsuint(i);
jsuint capacity = obj->getDenseArrayCapacity();
if (u < capacity)
return true;
if (INDEX_TOO_SPARSE(obj, u))
return false;
JSBool ret = obj->ensureDenseArrayElements(cx, u + 1);
JSBool ret = (obj->ensureDenseArrayElements(cx, u, 1) == JSObject::ED_OK);
/* Partially check the CallInfo's storeAccSet is correct. */
JS_ASSERT(obj->clasp == origObjClasp);
@@ -801,20 +803,29 @@ array_setProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp, JSBool stric
if (!obj->isDenseArray())
return js_SetProperty(cx, obj, id, vp, strict);
if (!js_IdIsIndex(id, &i) || js_PrototypeHasIndexedProperties(cx, obj) ||
INDEX_TOO_SPARSE(obj, i)) {
if (!obj->makeDenseArraySlow(cx))
return false;
return js_SetProperty(cx, obj, id, vp, strict);
}
do {
if (!js_IdIsIndex(id, &i))
break;
if (js_PrototypeHasIndexedProperties(cx, obj))
break;
if (!obj->ensureDenseArrayElements(cx, i + 1))
JSObject::EnsureDenseResult result = obj->ensureDenseArrayElements(cx, i, 1);
if (result != JSObject::ED_OK) {
if (result == JSObject::ED_FAILED)
return false;
JS_ASSERT(result == JSObject::ED_SPARSE);
break;
}
if (i >= obj->getArrayLength())
obj->setArrayLength(i + 1);
obj->setDenseArrayElement(i, *vp);
return true;
} while (false);
if (!obj->makeDenseArraySlow(cx))
return false;
if (i >= obj->getArrayLength())
obj->setArrayLength(i + 1);
obj->setDenseArrayElement(i, *vp);
return true;
return js_SetProperty(cx, obj, id, vp, strict);
}
static JSBool
@@ -861,7 +872,7 @@ array_defineProperty(JSContext *cx, JSObject *obj, jsid id, const Value *value,
return JS_TRUE;
isIndex = js_IdIsIndex(id, &i);
if (!isIndex || attrs != JSPROP_ENUMERATE || !obj->isDenseArray() || INDEX_TOO_SPARSE(obj, i)) {
if (!isIndex || attrs != JSPROP_ENUMERATE) {
if (!ENSURE_SLOW_ARRAY(cx, obj))
return JS_FALSE;
return js_DefineProperty(cx, obj, id, value, getter, setter, attrs);
@@ -915,20 +926,9 @@ array_trace(JSTracer *trc, JSObject *obj)
{
JS_ASSERT(obj->isDenseArray());
size_t holes = 0;
uint32 capacity = obj->getDenseArrayCapacity();
for (uint32 i = 0; i < capacity; i++) {
Value v = obj->getDenseArrayElement(i);
if (v.isMagic(JS_ARRAY_HOLE))
++holes;
else
MarkValue(trc, obj->getDenseArrayElement(i), "dense_array_elems");
}
if (IS_GC_MARKING_TRACER(trc) && holes > MIN_SPARSE_INDEX && holes > capacity / 4 * 3) {
/* This might fail, in which case we don't slowify it. */
static_cast<GCMarker *>(trc)->arraysToSlowify.append(obj);
}
for (uint32 i = 0; i < capacity; i++)
MarkValue(trc, obj->getDenseArrayElement(i), "dense_array_elems");
}
static JSBool
@@ -1379,22 +1379,28 @@ InitArrayElements(JSContext *cx, JSObject *obj, jsuint start, jsuint count, Valu
* Optimize for dense arrays so long as adding the given set of elements
* wouldn't otherwise make the array slow.
*/
if (obj->isDenseArray() && !js_PrototypeHasIndexedProperties(cx, obj) &&
start <= MAXINDEX - count && !INDEX_TOO_BIG(start + count)) {
do {
if (!obj->isDenseArray())
break;
if (js_PrototypeHasIndexedProperties(cx, obj))
break;
JSObject::EnsureDenseResult result = obj->ensureDenseArrayElements(cx, start, count);
if (result != JSObject::ED_OK) {
if (result == JSObject::ED_FAILED)
return false;
JS_ASSERT(result == JSObject::ED_SPARSE);
break;
}
jsuint newlen = start + count;
JS_ASSERT(jsdouble(start) + count == jsdouble(newlen));
if (!obj->ensureDenseArrayElements(cx, newlen))
return JS_FALSE;
if (newlen > obj->getArrayLength())
obj->setArrayLength(newlen);
JS_ASSERT(count < uint32(-1) / sizeof(Value));
memcpy(obj->getDenseArrayElements() + start, vector, sizeof(jsval) * count);
JS_ASSERT_IF(count != 0, !obj->getDenseArrayElement(newlen - 1).isMagic(JS_ARRAY_HOLE));
return JS_TRUE;
}
return true;
} while (false);
Value* end = vector + count;
while (vector != end && start < MAXINDEX) {
@@ -1436,7 +1442,9 @@ InitArrayObject(JSContext *cx, JSObject *obj, jsuint length, const Value *vector
obj->setArrayLength(length);
if (!vector || !length)
return true;
if (!obj->ensureDenseArrayElements(cx, length))
/* Avoid ensureDenseArrayElements to skip sparse array checks there. */
if (!obj->ensureSlots(cx, length))
return false;
memcpy(obj->getDenseArrayElements(), vector, length * sizeof(Value));
return true;
@@ -1470,7 +1478,12 @@ array_reverse(JSContext *cx, uintN argc, Value *vp)
return JS_FALSE;
vp->setObject(*obj);
if (obj->isDenseArray() && !js_PrototypeHasIndexedProperties(cx, obj)) {
do {
if (!obj->isDenseArray())
break;
if (js_PrototypeHasIndexedProperties(cx, obj))
break;
/* An empty array or an array with no elements is already reversed. */
if (len == 0 || obj->getDenseArrayCapacity() == 0)
return JS_TRUE;
@@ -1484,8 +1497,13 @@ array_reverse(JSContext *cx, uintN argc, Value *vp)
* holes in the array at its start) and ensure that the capacity is
* sufficient to hold all the elements in the array if it were full.
*/
if (!obj->ensureDenseArrayElements(cx, len))
return JS_FALSE;
JSObject::EnsureDenseResult result = obj->ensureDenseArrayElements(cx, len, 0);
if (result != JSObject::ED_OK) {
if (result == JSObject::ED_FAILED)
return false;
JS_ASSERT(result == JSObject::ED_SPARSE);
break;
}
uint32 lo = 0, hi = len - 1;
for (; lo < hi; lo++, hi--) {
@@ -1500,7 +1518,7 @@ array_reverse(JSContext *cx, uintN argc, Value *vp)
* holes).
*/
return JS_TRUE;
}
} while (false);
AutoValueRooter tvr(cx);
for (jsuint i = 0, half = len / 2; i < half; i++) {
@@ -2005,21 +2023,27 @@ static JSBool
array_push1_dense(JSContext* cx, JSObject* obj, const Value &v, Value *rval)
{
uint32 length = obj->getArrayLength();
if (INDEX_TOO_SPARSE(obj, length)) {
if (!obj->makeDenseArraySlow(cx))
return JS_FALSE;
Value tmp = v;
return array_push_slowly(cx, obj, 1, &tmp, rval);
}
do {
JSObject::EnsureDenseResult result = obj->ensureDenseArrayElements(cx, length, 1);
if (result != JSObject::ED_OK) {
if (result == JSObject::ED_FAILED)
return false;
JS_ASSERT(result == JSObject::ED_SPARSE);
break;
}
if (!obj->ensureDenseArrayElements(cx, length + 1))
return JS_FALSE;
obj->setArrayLength(length + 1);
obj->setArrayLength(length + 1);
JS_ASSERT(obj->getDenseArrayElement(length).isMagic(JS_ARRAY_HOLE));
obj->setDenseArrayElement(length, v);
rval->setNumber(obj->getArrayLength());
return JS_TRUE;
JS_ASSERT(obj->getDenseArrayElement(length).isMagic(JS_ARRAY_HOLE));
obj->setDenseArrayElement(length, v);
rval->setNumber(obj->getArrayLength());
return true;
} while (false);
if (!obj->makeDenseArraySlow(cx))
return false;
Value tmp = v;
return array_push_slowly(cx, obj, 1, &tmp, rval);
}
JS_ALWAYS_INLINE JSBool
@@ -2036,8 +2060,13 @@ ArrayCompPushImpl(JSContext *cx, JSObject *obj, const Value &v)
return JS_FALSE;
}
if (!obj->ensureDenseArrayElements(cx, length + 1))
return JS_FALSE;
/*
* Array comprehension cannot add holes to the array and never leaks
* the array before it is fully initialized. So we can use ensureSlots
* instead of ensureDenseArrayElements.
*/
if (!obj->ensureSlots(cx, length + 1))
return false;
}
obj->setArrayLength(length + 1);
obj->setDenseArrayElement(length, v);
@@ -2195,16 +2224,27 @@ array_unshift(JSContext *cx, uintN argc, Value *vp)
/* Slide up the array to make room for argc at the bottom. */
argv = JS_ARGV(cx, vp);
if (length > 0) {
if (obj->isDenseArray() && !js_PrototypeHasIndexedProperties(cx, obj) &&
!INDEX_TOO_SPARSE(obj, unsigned(newlen + argc))) {
JS_ASSERT(newlen + argc == length + argc);
if (!obj->ensureDenseArrayElements(cx, length + argc))
return JS_FALSE;
bool optimized = false;
do {
if (!obj->isDenseArray())
break;
if (js_PrototypeHasIndexedProperties(cx, obj))
break;
JSObject::EnsureDenseResult result = obj->ensureDenseArrayElements(cx, length, argc);
if (result != JSObject::ED_OK) {
if (result == JSObject::ED_FAILED)
return false;
JS_ASSERT(result == JSObject::ED_SPARSE);
break;
}
Value *elems = obj->getDenseArrayElements();
memmove(elems + argc, elems, length * sizeof(jsval));
for (uint32 i = 0; i < argc; i++)
obj->setDenseArrayElement(i, MagicValue(JS_ARRAY_HOLE));
} else {
optimized = true;
} while (false);
if (!optimized) {
last = length;
jsdouble upperIndex = last + argc;
AutoValueRooter tvr(cx);
@@ -2324,12 +2364,23 @@ array_splice(JSContext *cx, uintN argc, Value *vp)
if (argc > count) {
delta = (jsuint)argc - count;
last = length;
if (obj->isDenseArray() && !js_PrototypeHasIndexedProperties(cx, obj) &&
length <= obj->getDenseArrayCapacity() &&
(length == 0 || !obj->getDenseArrayElement(length - 1).isMagic(JS_ARRAY_HOLE))) {
if (!obj->ensureDenseArrayElements(cx, length + delta))
return JS_FALSE;
bool optimized = false;
do {
if (!obj->isDenseArray())
break;
if (js_PrototypeHasIndexedProperties(cx, obj))
break;
if (length > obj->getDenseArrayCapacity())
break;
if (length != 0 && obj->getDenseArrayElement(length - 1).isMagic(JS_ARRAY_HOLE))
break;
JSObject::EnsureDenseResult result = obj->ensureDenseArrayElements(cx, length, delta);
if (result != JSObject::ED_OK) {
if (result == JSObject::ED_FAILED)
return false;
JS_ASSERT(result == JSObject::ED_SPARSE);
break;
}
Value *arraybeg = obj->getDenseArrayElements();
Value *srcbeg = arraybeg + last - 1;
Value *srcend = arraybeg + end - 1;
@@ -2338,7 +2389,10 @@ array_splice(JSContext *cx, uintN argc, Value *vp)
*dst = *src;
obj->setArrayLength(obj->getArrayLength() + delta);
} else {
optimized = true;
} while (false);
if (!optimized) {
/* (uint) end could be 0, so we can't use a vanilla >= test. */
while (last-- > end) {
if (!JS_CHECK_OPERATION_LIMIT(cx) ||
@@ -2979,7 +3033,9 @@ js_NewPreallocatedArray(JSContext* cx, JSObject* proto, int32 len)
JSObject *obj = js_NewEmptyArray(cx, proto, len);
if (!obj)
return NULL;
if (!obj->ensureDenseArrayElements(cx, len))
/* Avoid ensureDenseArrayElements to skip sparse array checks there. */
if (!obj->ensureSlots(cx, len))
return NULL;
return obj;
}