Commit 9b395475 authored by Jan de Mooij's avatar Jan de Mooij
Browse files

Bug 1364346 part 3 - Optimize Array.prototype.unshift by taking advantage of...

Bug 1364346 part 3 - Optimize Array.prototype.unshift by taking advantage of shifted elements. r=anba

--HG--
extra : rebase_source : 793e3dbf21745b1f13661f4856c582a86987a493
parent 71b086f5
Loading
Loading
Loading
Loading
+47 −0
Original line number Diff line number Diff line
function test1() {
    var a = [];
    for (var i = 0; i < 100; i++)
        a.unshift("foo" + i);
    for (var i = 99; i >= 0; i--) {
        assertEq(a.shift(), "foo" + i);
        a.unshift("foo" + (i - 1));
    }
    assertEq(a.length, 100);
}
test1();

function sum(arr) {
    var res = 0;
    for (var i = 0; i < arr.length; i++)
        res += arr[i];
    return res;
}
function test2() {
    var a = [];
    for (var i = 0; i < 200; i++)
        a.push(i);
    for (var i = 0; i < 100; i++)
        a.shift();
    for (var i = 0; i < 200; i++)
        a.unshift(i);
    assertEq(a.length, 300);
    assertEq(sum(a), 34850);
}
test2();

function test3() {
    var a = [];
    for (var i = 0; i < 200; i++)
        a.push(i);
    var toAdd = [];
    var step = 1;
    for (var i = 0; i < 2500; i += step) {
        for (var j = 0; j < step; j++)
            toAdd.unshift(i + j);
        a.unshift(...toAdd);
        step = Math.max((i / 16)|0, 1);
    }
    assertEq(a.length, 41463);
    assertEq(sum(a), 26657756);
}
test3();
+10 −8
Original line number Diff line number Diff line
@@ -2504,6 +2504,7 @@ js::array_unshift(JSContext* cx, unsigned argc, Value* vp)
            NativeObject* nobj = &obj->as<NativeObject>();
            if (nobj->is<ArrayObject>() && !nobj->as<ArrayObject>().lengthIsWritable())
                break;
            if (!nobj->tryUnshiftDenseElements(args.length())) {
                DenseElementResult result = nobj->ensureDenseElements(cx, uint32_t(length), args.length());
                if (result != DenseElementResult::Success) {
                    if (result == DenseElementResult::Failure)
@@ -2513,6 +2514,7 @@ js::array_unshift(JSContext* cx, unsigned argc, Value* vp)
                }
                if (length > 0)
                    nobj->moveDenseElements(args.length(), 0, uint32_t(length));
            }
            for (uint32_t i = 0; i < args.length(); i++)
                nobj->setDenseElementWithType(cx, i, args[i]);
            optimized = true;
+8 −1
Original line number Diff line number Diff line
@@ -181,6 +181,14 @@ NativeObject::tryShiftDenseElements(uint32_t count)
        return false;
    }

    shiftDenseElementsUnchecked(count);
    return true;
}

inline void
NativeObject::shiftDenseElementsUnchecked(uint32_t count)
{
    ObjectElements* header = getElementsHeader();
    MOZ_ASSERT(count > 0);
    MOZ_ASSERT(count < header->initializedLength);

@@ -195,7 +203,6 @@ NativeObject::tryShiftDenseElements(uint32_t count)
    elements_ += count;
    ObjectElements* newHeader = getElementsHeader();
    memmove(newHeader, header, sizeof(ObjectElements));
    return true;
}

inline void
+81 −0
Original line number Diff line number Diff line
@@ -749,6 +749,87 @@ NativeObject::maybeMoveShiftedElements()
        moveShiftedElements();
}

bool
NativeObject::tryUnshiftDenseElements(uint32_t count)
{
    MOZ_ASSERT(count > 0);

    ObjectElements* header = getElementsHeader();
    uint32_t numShifted = header->numShiftedElements();

    if (count > numShifted) {
        // We need more elements than are easily available. Try to make space
        // for more elements than we need (and shift the remaining ones) so
        // that unshifting more elements later will be fast.

        // Don't bother reserving elements if the number of elements is small.
        // Note that there's no technical reason for using this particular
        // limit.
        if (header->initializedLength <= 10 ||
            header->isCopyOnWrite() ||
            header->isFrozen() ||
            header->hasNonwritableArrayLength() ||
            MOZ_UNLIKELY(count > ObjectElements::MaxShiftedElements))
        {
            return false;
        }

        MOZ_ASSERT(header->capacity >= header->initializedLength);
        uint32_t unusedCapacity = header->capacity - header->initializedLength;

        // Determine toShift, the number of extra elements we want to make
        // available.
        uint32_t toShift = count - numShifted;
        MOZ_ASSERT(toShift <= ObjectElements::MaxShiftedElements,
                   "count <= MaxShiftedElements so toShift <= MaxShiftedElements");

        // Give up if we need to allocate more elements.
        if (toShift > unusedCapacity)
            return false;

        // Move more elements than we need, so that other unshift calls will be
        // fast. We just have to make sure we don't exceed unusedCapacity.
        toShift = Min(toShift + unusedCapacity / 2, unusedCapacity);

        // Ensure |numShifted + toShift| does not exceed MaxShiftedElements.
        if (numShifted + toShift > ObjectElements::MaxShiftedElements)
            toShift = ObjectElements::MaxShiftedElements - numShifted;

        MOZ_ASSERT(count <= numShifted + toShift);
        MOZ_ASSERT(numShifted + toShift <= ObjectElements::MaxShiftedElements);
        MOZ_ASSERT(toShift <= unusedCapacity);

        // Now move/unshift the elements.
        uint32_t initLen = header->initializedLength;
        setDenseInitializedLength(initLen + toShift);
        for (uint32_t i = 0; i < toShift; i++)
            initDenseElement(initLen + i, UndefinedValue());
        moveDenseElements(toShift, 0, initLen);

        // Shift the elements we just prepended.
        shiftDenseElementsUnchecked(toShift);

        // We can now fall-through to the fast path below.
        header = getElementsHeader();
        MOZ_ASSERT(header->numShiftedElements() == numShifted + toShift);

        numShifted = header->numShiftedElements();
        MOZ_ASSERT(count <= numShifted);
    }

    elements_ -= count;
    ObjectElements* newHeader = getElementsHeader();
    memmove(newHeader, header, sizeof(ObjectElements));

    newHeader->unshiftShiftedElements(count);

    // Initialize to |undefined| to ensure pre-barriers don't see garbage.
    for (uint32_t i = 0; i < count; i++)
        initDenseElement(i, UndefinedValue());

    return true;
}

// Given a requested capacity (in elements) and (potentially) the length of an
// array for which elements are being allocated, compute an actual allocation
// amount (in elements).  (Allocation amounts include space for an
+15 −0
Original line number Diff line number Diff line
@@ -283,6 +283,16 @@ class ObjectElements
        capacity -= count;
        initializedLength -= count;
    }
    void unshiftShiftedElements(uint32_t count) {
        MOZ_ASSERT(count > 0);
        MOZ_ASSERT(!(flags & (NONWRITABLE_ARRAY_LENGTH | FROZEN | COPY_ON_WRITE)));
        uint32_t numShifted = numShiftedElements();
        MOZ_ASSERT(count <= numShifted);
        numShifted -= count;
        flags = (numShifted << NumShiftedElementsShift) | (flags & FlagsMask);
        capacity += count;
        initializedLength += count;
    }
    void clearShiftedElements() {
        flags &= FlagsMask;
        MOZ_ASSERT(numShiftedElements() == 0);
@@ -955,6 +965,8 @@ class NativeObject : public ShapedObject
            getSlotAddressUnchecked(i)->HeapSlot::~HeapSlot();
    }

    inline void shiftDenseElementsUnchecked(uint32_t count);

  public:
    static bool rollbackProperties(JSContext* cx, HandleNativeObject obj,
                                   uint32_t slotSpan);
@@ -1074,6 +1086,9 @@ class NativeObject : public ShapedObject
    // Try to shift |count| dense elements, see the "Shifted elements" comment.
    inline bool tryShiftDenseElements(uint32_t count);

    // Try to make space for |count| dense elements at the start of the array.
    bool tryUnshiftDenseElements(uint32_t count);

    // Move the elements header and all shifted elements to the start of the
    // allocated elements space, so that numShiftedElements is 0 afterwards.
    void moveShiftedElements();