Commit 007eac94 authored by cbruni's avatar cbruni Committed by Commit bot

Improve JSReceiver::GetKeys Speed

The core bottleneck lies in N-square cost of array union. Depending on the size
of the arrays involved it makes sense to rely on a hash-set/table for the lookup.

LOG=N
BUG=v8:2904

Review URL: https://codereview.chromium.org/1316213008

Cr-Commit-Position: refs/heads/master@{#30797}
parent 7af79ae6
...@@ -97,18 +97,6 @@ ELEMENTS_LIST(ELEMENTS_TRAITS) ...@@ -97,18 +97,6 @@ ELEMENTS_LIST(ELEMENTS_TRAITS)
#undef ELEMENTS_TRAITS #undef ELEMENTS_TRAITS
static bool HasIndex(Handle<FixedArray> array, Handle<Object> index_handle) {
DisallowHeapAllocation no_gc;
Object* index = *index_handle;
int len0 = array->length();
for (int i = 0; i < len0; i++) {
Object* element = array->get(i);
if (index->KeyEquals(element)) return true;
}
return false;
}
MUST_USE_RESULT MUST_USE_RESULT
MaybeHandle<Object> ThrowArrayLengthRangeError(Isolate* isolate) { MaybeHandle<Object> ThrowArrayLengthRangeError(Isolate* isolate) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidArrayLength), THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidArrayLength),
...@@ -885,78 +873,26 @@ class ElementsAccessorBase : public ElementsAccessor { ...@@ -885,78 +873,26 @@ class ElementsAccessorBase : public ElementsAccessor {
from, from_start, *to, from_kind, to_start, packed_size, copy_size); from, from_start, *to, from_kind, to_start, packed_size, copy_size);
} }
virtual Handle<FixedArray> AddElementsToFixedArray( virtual void AddElementsToKeyAccumulator(Handle<JSObject> receiver,
Handle<JSObject> receiver, Handle<FixedArray> to, KeyAccumulator* accumulator,
FixedArray::KeyFilter filter) final { FixedArray::KeyFilter filter) final {
Handle<FixedArrayBase> from(receiver->elements()); Handle<FixedArrayBase> from(receiver->elements());
uint32_t add_length =
int len0 = to->length(); ElementsAccessorSubclass::GetCapacityImpl(*receiver, *from);
#ifdef ENABLE_SLOW_DCHECKS if (add_length == 0) return;
if (FLAG_enable_slow_asserts) { accumulator->PrepareForComparisons(add_length);
for (int i = 0; i < len0; i++) { int prev_key_count = accumulator->GetLength();
DCHECK(!to->get(i)->IsTheHole()); for (uint32_t i = 0; i < add_length; i++) {
} if (!ElementsAccessorSubclass::HasEntryImpl(*from, i)) continue;
} Handle<Object> value = ElementsAccessorSubclass::GetImpl(from, i);
#endif DCHECK(!value->IsTheHole());
DCHECK(!value->IsAccessorPair());
// Optimize if 'other' is empty. DCHECK(!value->IsExecutableAccessorInfo());
// We cannot optimize if 'this' is empty, as other may have holes. if (filter == FixedArray::NON_SYMBOL_KEYS && value->IsSymbol()) {
uint32_t len1 = ElementsAccessorSubclass::GetCapacityImpl(*receiver, *from); continue;
if (len1 == 0) return to;
Isolate* isolate = from->GetIsolate();
// Compute how many elements are not in other.
uint32_t extra = 0;
for (uint32_t y = 0; y < len1; y++) {
if (ElementsAccessorSubclass::HasEntryImpl(*from, y)) {
Handle<Object> value = ElementsAccessorSubclass::GetImpl(from, y);
DCHECK(!value->IsTheHole());
DCHECK(!value->IsAccessorPair());
DCHECK(!value->IsExecutableAccessorInfo());
if (filter == FixedArray::NON_SYMBOL_KEYS && value->IsSymbol()) {
continue;
}
if (!HasIndex(to, value)) {
extra++;
}
}
}
if (extra == 0) return to;
// Allocate the result
Handle<FixedArray> result = isolate->factory()->NewFixedArray(len0 + extra);
// Fill in the content
{
DisallowHeapAllocation no_gc;
WriteBarrierMode mode = result->GetWriteBarrierMode(no_gc);
for (int i = 0; i < len0; i++) {
Object* e = to->get(i);
DCHECK(e->IsString() || e->IsNumber());
result->set(i, e, mode);
}
}
// Fill in the extra values.
uint32_t entry = 0;
for (uint32_t y = 0; y < len1; y++) {
if (ElementsAccessorSubclass::HasEntryImpl(*from, y)) {
Handle<Object> value = ElementsAccessorSubclass::GetImpl(from, y);
DCHECK(!value->IsAccessorPair());
DCHECK(!value->IsExecutableAccessorInfo());
if (filter == FixedArray::NON_SYMBOL_KEYS && value->IsSymbol()) {
continue;
}
if (!value->IsTheHole() && !HasIndex(to, value)) {
result->set(len0 + entry, *value);
entry++;
}
} }
accumulator->AddKey(value, prev_key_count);
} }
DCHECK(extra == entry);
return result;
} }
static uint32_t GetCapacityImpl(JSObject* holder, static uint32_t GetCapacityImpl(JSObject* holder,
......
...@@ -100,9 +100,9 @@ class ElementsAccessor { ...@@ -100,9 +100,9 @@ class ElementsAccessor {
virtual void GrowCapacityAndConvert(Handle<JSObject> object, virtual void GrowCapacityAndConvert(Handle<JSObject> object,
uint32_t capacity) = 0; uint32_t capacity) = 0;
virtual Handle<FixedArray> AddElementsToFixedArray( virtual void AddElementsToKeyAccumulator(Handle<JSObject> receiver,
Handle<JSObject> receiver, Handle<FixedArray> to, KeyAccumulator* accumulator,
FixedArray::KeyFilter filter) = 0; FixedArray::KeyFilter filter) = 0;
// Returns a shared ElementsAccessor for the specified ElementsKind. // Returns a shared ElementsAccessor for the specified ElementsKind.
static ElementsAccessor* ForKind(ElementsKind elements_kind) { static ElementsAccessor* ForKind(ElementsKind elements_kind) {
......
This diff is collapsed.
...@@ -2472,17 +2472,6 @@ class FixedArray: public FixedArrayBase { ...@@ -2472,17 +2472,6 @@ class FixedArray: public FixedArrayBase {
enum KeyFilter { ALL_KEYS, NON_SYMBOL_KEYS }; enum KeyFilter { ALL_KEYS, NON_SYMBOL_KEYS };
// Add the elements of a JSArray to this FixedArray.
MUST_USE_RESULT static MaybeHandle<FixedArray> AddKeysFromArrayLike(
Handle<FixedArray> content, Handle<JSObject> array,
KeyFilter filter = ALL_KEYS);
// Computes the union of keys and return the result.
// Used for implementing "for (n in object) { }"
MUST_USE_RESULT static MaybeHandle<FixedArray> UnionOfKeys(
Handle<FixedArray> first,
Handle<FixedArray> second);
// Copy a sub array from the receiver to dest. // Copy a sub array from the receiver to dest.
void CopyTo(int pos, FixedArray* dest, int dest_pos, int len); void CopyTo(int pos, FixedArray* dest, int dest_pos, int len);
...@@ -3661,6 +3650,9 @@ class OrderedHashTable: public FixedArray { ...@@ -3661,6 +3650,9 @@ class OrderedHashTable: public FixedArray {
// exisiting iterators can be updated. // exisiting iterators can be updated.
static Handle<Derived> Clear(Handle<Derived> table); static Handle<Derived> Clear(Handle<Derived> table);
// Returns a true if the OrderedHashTable contains the key
static bool HasKey(Handle<Derived> table, Handle<Object> key);
int NumberOfElements() { int NumberOfElements() {
return Smi::cast(get(kNumberOfElementsIndex))->value(); return Smi::cast(get(kNumberOfElementsIndex))->value();
} }
...@@ -3680,6 +3672,26 @@ class OrderedHashTable: public FixedArray { ...@@ -3680,6 +3672,26 @@ class OrderedHashTable: public FixedArray {
return kHashTableStartIndex + NumberOfBuckets() + (entry * kEntrySize); return kHashTableStartIndex + NumberOfBuckets() + (entry * kEntrySize);
} }
int HashToBucket(int hash) { return hash & (NumberOfBuckets() - 1); }
int HashToEntry(int hash) {
int bucket = HashToBucket(hash);
Object* entry = this->get(kHashTableStartIndex + bucket);
return Smi::cast(entry)->value();
}
int KeyToFirstEntry(Object* key) {
Object* hash = key->GetHash();
// If the object does not have an identity hash, it was never used as a key
if (hash->IsUndefined()) return kNotFound;
return HashToEntry(Smi::cast(hash)->value());
}
int NextChainEntry(int entry) {
Object* next_entry = get(EntryToIndex(entry) + kChainOffset);
return Smi::cast(next_entry)->value();
}
Object* KeyAt(int entry) { return get(EntryToIndex(entry)); } Object* KeyAt(int entry) { return get(EntryToIndex(entry)); }
bool IsObsolete() { bool IsObsolete() {
...@@ -3726,7 +3738,7 @@ class OrderedHashTable: public FixedArray { ...@@ -3726,7 +3738,7 @@ class OrderedHashTable: public FixedArray {
// optimize that case. // optimize that case.
static const int kClearedTableSentinel = -1; static const int kClearedTableSentinel = -1;
private: protected:
static Handle<Derived> Rehash(Handle<Derived> table, int new_capacity); static Handle<Derived> Rehash(Handle<Derived> table, int new_capacity);
void SetNumberOfBuckets(int num) { void SetNumberOfBuckets(int num) {
...@@ -3768,6 +3780,9 @@ class OrderedHashSet: public OrderedHashTable< ...@@ -3768,6 +3780,9 @@ class OrderedHashSet: public OrderedHashTable<
OrderedHashSet, JSSetIterator, 1> { OrderedHashSet, JSSetIterator, 1> {
public: public:
DECLARE_CAST(OrderedHashSet) DECLARE_CAST(OrderedHashSet)
static Handle<OrderedHashSet> Add(Handle<OrderedHashSet> table,
Handle<Object> value);
}; };
...@@ -10488,6 +10503,29 @@ class BooleanBit : public AllStatic { ...@@ -10488,6 +10503,29 @@ class BooleanBit : public AllStatic {
} }
}; };
class KeyAccumulator final BASE_EMBEDDED {
public:
explicit KeyAccumulator(Isolate* isolate) : isolate_(isolate), length_(0) {}
void AddKey(Handle<Object> key, int check_limit);
void AddKeys(Handle<FixedArray> array, FixedArray::KeyFilter filter);
void AddKeys(Handle<JSObject> array, FixedArray::KeyFilter filter);
void PrepareForComparisons(int count);
Handle<FixedArray> GetKeys();
int GetLength() { return length_; }
private:
void EnsureCapacity(int capacity);
void Grow();
Isolate* isolate_;
Handle<FixedArray> keys_;
Handle<OrderedHashSet> set_;
int length_;
DISALLOW_COPY_AND_ASSIGN(KeyAccumulator);
};
} } // namespace v8::internal } } // namespace v8::internal
#endif // V8_OBJECTS_H_ #endif // V8_OBJECTS_H_
...@@ -197,38 +197,39 @@ RUNTIME_FUNCTION(Runtime_GetArrayKeys) { ...@@ -197,38 +197,39 @@ RUNTIME_FUNCTION(Runtime_GetArrayKeys) {
DCHECK(args.length() == 2); DCHECK(args.length() == 2);
CONVERT_ARG_HANDLE_CHECKED(JSObject, array, 0); CONVERT_ARG_HANDLE_CHECKED(JSObject, array, 0);
CONVERT_NUMBER_CHECKED(uint32_t, length, Uint32, args[1]); CONVERT_NUMBER_CHECKED(uint32_t, length, Uint32, args[1]);
if (array->elements()->IsDictionary()) {
Handle<FixedArray> keys = isolate->factory()->empty_fixed_array(); if (!array->elements()->IsDictionary()) {
for (PrototypeIterator iter(isolate, array,
PrototypeIterator::START_AT_RECEIVER);
!iter.IsAtEnd(); iter.Advance()) {
if (PrototypeIterator::GetCurrent(iter)->IsJSProxy() ||
PrototypeIterator::GetCurrent<JSObject>(iter)
->HasIndexedInterceptor()) {
// Bail out if we find a proxy or interceptor, likely not worth
// collecting keys in that case.
return *isolate->factory()->NewNumberFromUint(length);
}
Handle<JSObject> current = PrototypeIterator::GetCurrent<JSObject>(iter);
Handle<FixedArray> current_keys =
isolate->factory()->NewFixedArray(current->NumberOfOwnElements(NONE));
current->GetOwnElementKeys(*current_keys, NONE);
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, keys, FixedArray::UnionOfKeys(keys, current_keys));
}
// Erase any keys >= length.
// TODO(adamk): Remove this step when the contract of %GetArrayKeys
// is changed to let this happen on the JS side.
for (int i = 0; i < keys->length(); i++) {
if (NumberToUint32(keys->get(i)) >= length) keys->set_undefined(i);
}
return *isolate->factory()->NewJSArrayWithElements(keys);
} else {
RUNTIME_ASSERT(array->HasFastSmiOrObjectElements() || RUNTIME_ASSERT(array->HasFastSmiOrObjectElements() ||
array->HasFastDoubleElements()); array->HasFastDoubleElements());
uint32_t actual_length = static_cast<uint32_t>(array->elements()->length()); uint32_t actual_length = static_cast<uint32_t>(array->elements()->length());
return *isolate->factory()->NewNumberFromUint(Min(actual_length, length)); return *isolate->factory()->NewNumberFromUint(Min(actual_length, length));
} }
KeyAccumulator accumulator(isolate);
for (PrototypeIterator iter(isolate, array,
PrototypeIterator::START_AT_RECEIVER);
!iter.IsAtEnd(); iter.Advance()) {
if (PrototypeIterator::GetCurrent(iter)->IsJSProxy() ||
PrototypeIterator::GetCurrent<JSObject>(iter)
->HasIndexedInterceptor()) {
// Bail out if we find a proxy or interceptor, likely not worth
// collecting keys in that case.
return *isolate->factory()->NewNumberFromUint(length);
}
Handle<JSObject> current = PrototypeIterator::GetCurrent<JSObject>(iter);
Handle<FixedArray> current_keys =
isolate->factory()->NewFixedArray(current->NumberOfOwnElements(NONE));
current->GetOwnElementKeys(*current_keys, NONE);
accumulator.AddKeys(current_keys, FixedArray::ALL_KEYS);
}
// Erase any keys >= length.
// TODO(adamk): Remove this step when the contract of %GetArrayKeys
// is changed to let this happen on the JS side.
Handle<FixedArray> keys = accumulator.GetKeys();
for (int i = 0; i < keys->length(); i++) {
if (NumberToUint32(keys->get(i)) >= length) keys->set_undefined(i);
}
return *isolate->factory()->NewJSArrayWithElements(keys);
} }
......
...@@ -996,17 +996,24 @@ RUNTIME_FUNCTION(Runtime_OwnKeys) { ...@@ -996,17 +996,24 @@ RUNTIME_FUNCTION(Runtime_OwnKeys) {
// a fresh clone on each invocation. // a fresh clone on each invocation.
int length = contents->length(); int length = contents->length();
Handle<FixedArray> copy = isolate->factory()->NewFixedArray(length); Handle<FixedArray> copy = isolate->factory()->NewFixedArray(length);
for (int i = 0; i < length; i++) { int offset = 0;
Object* entry = contents->get(i); // Use an outer loop to avoid creating too many handles in the current
if (entry->IsString()) { // handle scope.
copy->set(i, entry); while (offset < length) {
} else { HandleScope scope(isolate);
DCHECK(entry->IsNumber()); offset += 100;
HandleScope scope(isolate); int end = Min(offset, length);
Handle<Object> entry_handle(entry, isolate); for (int i = offset - 100; i < end; i++) {
Handle<Object> entry_str = Object* entry = contents->get(i);
isolate->factory()->NumberToString(entry_handle); if (entry->IsString()) {
copy->set(i, *entry_str); copy->set(i, entry);
} else {
DCHECK(entry->IsNumber());
Handle<Object> entry_handle(entry, isolate);
Handle<Object> entry_str =
isolate->factory()->NumberToString(entry_handle);
copy->set(i, *entry_str);
}
} }
} }
return *isolate->factory()->NewJSArrayWithElements(copy); return *isolate->factory()->NewJSArrayWithElements(copy);
......
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