Commit 6aaccd0f authored by Jakob Kummerow's avatar Jakob Kummerow Committed by Commit Bot

[elements] Fix pathological slowness when deleting many elements

When most elements of an object are deleted, we want to normalize its
elements backing store to a dictionary in order to save space. Finding
the right time to do so should not incur a linear cost on each delete
operation. This patch changes the heuristic to an amortized-constant
approach based on a global counter and the current backing store
capacity.

BUG=chromium:542978

Change-Id: Ifdf29ab2211fdde1df9078f63be4118627d6a67e
Reviewed-on: https://chromium-review.googlesource.com/506191Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#45330}
parent 9fd97a5f
...@@ -1886,8 +1886,6 @@ class FastElementsAccessor : public ElementsAccessorBase<Subclass, KindTraits> { ...@@ -1886,8 +1886,6 @@ class FastElementsAccessor : public ElementsAccessorBase<Subclass, KindTraits> {
// TODO(verwaest): Move this out of elements.cc. // TODO(verwaest): Move this out of elements.cc.
// If an old space backing store is larger than a certain size and // If an old space backing store is larger than a certain size and
// has too few used values, normalize it. // has too few used values, normalize it.
// To avoid doing the check on every delete we require at least
// one adjacent hole to the value being deleted.
const int kMinLengthForSparsenessCheck = 64; const int kMinLengthForSparsenessCheck = 64;
if (backing_store->length() < kMinLengthForSparsenessCheck) return; if (backing_store->length() < kMinLengthForSparsenessCheck) return;
if (backing_store->GetHeap()->InNewSpace(*backing_store)) return; if (backing_store->GetHeap()->InNewSpace(*backing_store)) return;
...@@ -1897,34 +1895,48 @@ class FastElementsAccessor : public ElementsAccessorBase<Subclass, KindTraits> { ...@@ -1897,34 +1895,48 @@ class FastElementsAccessor : public ElementsAccessorBase<Subclass, KindTraits> {
} else { } else {
length = static_cast<uint32_t>(store->length()); length = static_cast<uint32_t>(store->length());
} }
if ((entry > 0 && backing_store->is_the_hole(isolate, entry - 1)) ||
(entry + 1 < length && // To avoid doing the check on every delete, use a counter-based heuristic.
backing_store->is_the_hole(isolate, entry + 1))) { const int kLengthFraction = 16;
if (!obj->IsJSArray()) { // The above constant must be large enough to ensure that we check for
uint32_t i; // normalization frequently enough. At a minimum, it should be large
for (i = entry + 1; i < length; i++) { // enough to reliably hit the "window" of remaining elements count where
if (!backing_store->is_the_hole(isolate, i)) break; // normalization would be beneficial.
} STATIC_ASSERT(kLengthFraction >=
if (i == length) { SeededNumberDictionary::kEntrySize *
DeleteAtEnd(obj, backing_store, entry); SeededNumberDictionary::kPreferFastElementsSizeFactor);
return; size_t current_counter = isolate->elements_deletion_counter();
} if (current_counter < length / kLengthFraction) {
isolate->set_elements_deletion_counter(current_counter + 1);
return;
}
// Reset the counter whenever the full check is performed.
isolate->set_elements_deletion_counter(0);
if (!obj->IsJSArray()) {
uint32_t i;
for (i = entry + 1; i < length; i++) {
if (!backing_store->is_the_hole(isolate, i)) break;
} }
int num_used = 0; if (i == length) {
for (int i = 0; i < backing_store->length(); ++i) { DeleteAtEnd(obj, backing_store, entry);
if (!backing_store->is_the_hole(isolate, i)) { return;
++num_used; }
// Bail out if a number dictionary wouldn't be able to save at least }
// 75% space. int num_used = 0;
if (4 * SeededNumberDictionary::ComputeCapacity(num_used) * for (int i = 0; i < backing_store->length(); ++i) {
SeededNumberDictionary::kEntrySize > if (!backing_store->is_the_hole(isolate, i)) {
backing_store->length()) { ++num_used;
return; // Bail out if a number dictionary wouldn't be able to save much space.
} if (SeededNumberDictionary::kPreferFastElementsSizeFactor *
SeededNumberDictionary::ComputeCapacity(num_used) *
SeededNumberDictionary::kEntrySize >
static_cast<uint32_t>(backing_store->length())) {
return;
} }
} }
JSObject::NormalizeElements(obj);
} }
JSObject::NormalizeElements(obj);
} }
static void ReconfigureImpl(Handle<JSObject> object, static void ReconfigureImpl(Handle<JSObject> object,
......
...@@ -1291,6 +1291,11 @@ class Isolate { ...@@ -1291,6 +1291,11 @@ class Isolate {
// reset to nullptr. // reset to nullptr.
void UnregisterFromReleaseAtTeardown(ManagedObjectFinalizer** finalizer_ptr); void UnregisterFromReleaseAtTeardown(ManagedObjectFinalizer** finalizer_ptr);
size_t elements_deletion_counter() { return elements_deletion_counter_; }
void set_elements_deletion_counter(size_t value) {
elements_deletion_counter_ = value;
}
protected: protected:
explicit Isolate(bool enable_serializer); explicit Isolate(bool enable_serializer);
bool IsArrayOrObjectPrototype(Object* object); bool IsArrayOrObjectPrototype(Object* object);
...@@ -1577,6 +1582,8 @@ class Isolate { ...@@ -1577,6 +1582,8 @@ class Isolate {
size_t total_regexp_code_generated_; size_t total_regexp_code_generated_;
size_t elements_deletion_counter_ = 0;
friend class ExecutionAccess; friend class ExecutionAccess;
friend class HandleScopeImplementer; friend class HandleScopeImplementer;
friend class HeapTester; friend class HeapTester;
......
...@@ -15403,13 +15403,14 @@ static bool ShouldConvertToSlowElements(JSObject* object, uint32_t capacity, ...@@ -15403,13 +15403,14 @@ static bool ShouldConvertToSlowElements(JSObject* object, uint32_t capacity,
object->GetHeap()->InNewSpace(object))) { object->GetHeap()->InNewSpace(object))) {
return false; return false;
} }
// If the fast-case backing storage takes up roughly three times as // If the fast-case backing storage takes up much more memory than a
// much space (in machine words) as a dictionary backing storage // dictionary backing storage would, the object should have slow elements.
// would, the object should have slow elements.
int used_elements = object->GetFastElementsUsage(); int used_elements = object->GetFastElementsUsage();
int dictionary_size = SeededNumberDictionary::ComputeCapacity(used_elements) * uint32_t size_threshold =
SeededNumberDictionary::kEntrySize; SeededNumberDictionary::kPreferFastElementsSizeFactor *
return 3 * static_cast<uint32_t>(dictionary_size) <= *new_capacity; SeededNumberDictionary::ComputeCapacity(used_elements) *
SeededNumberDictionary::kEntrySize;
return size_threshold <= *new_capacity;
} }
......
...@@ -334,6 +334,10 @@ class SeededNumberDictionary ...@@ -334,6 +334,10 @@ class SeededNumberDictionary
static const int kRequiresSlowElementsMask = 1; static const int kRequiresSlowElementsMask = 1;
static const int kRequiresSlowElementsTagSize = 1; static const int kRequiresSlowElementsTagSize = 1;
static const uint32_t kRequiresSlowElementsLimit = (1 << 29) - 1; static const uint32_t kRequiresSlowElementsLimit = (1 << 29) - 1;
// JSObjects prefer dictionary elements if the dictionary saves this much
// memory compared to a fast elements backing store.
static const uint32_t kPreferFastElementsSizeFactor = 3;
}; };
class UnseededNumberDictionary class UnseededNumberDictionary
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment