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)
#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
MaybeHandle<Object> ThrowArrayLengthRangeError(Isolate* isolate) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidArrayLength),
......@@ -885,78 +873,26 @@ class ElementsAccessorBase : public ElementsAccessor {
from, from_start, *to, from_kind, to_start, packed_size, copy_size);
}
virtual Handle<FixedArray> AddElementsToFixedArray(
Handle<JSObject> receiver, Handle<FixedArray> to,
FixedArray::KeyFilter filter) final {
virtual void AddElementsToKeyAccumulator(Handle<JSObject> receiver,
KeyAccumulator* accumulator,
FixedArray::KeyFilter filter) final {
Handle<FixedArrayBase> from(receiver->elements());
int len0 = to->length();
#ifdef ENABLE_SLOW_DCHECKS
if (FLAG_enable_slow_asserts) {
for (int i = 0; i < len0; i++) {
DCHECK(!to->get(i)->IsTheHole());
}
}
#endif
// Optimize if 'other' is empty.
// We cannot optimize if 'this' is empty, as other may have holes.
uint32_t len1 = ElementsAccessorSubclass::GetCapacityImpl(*receiver, *from);
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++;
}
uint32_t add_length =
ElementsAccessorSubclass::GetCapacityImpl(*receiver, *from);
if (add_length == 0) return;
accumulator->PrepareForComparisons(add_length);
int prev_key_count = accumulator->GetLength();
for (uint32_t i = 0; i < add_length; i++) {
if (!ElementsAccessorSubclass::HasEntryImpl(*from, i)) continue;
Handle<Object> value = ElementsAccessorSubclass::GetImpl(from, i);
DCHECK(!value->IsTheHole());
DCHECK(!value->IsAccessorPair());
DCHECK(!value->IsExecutableAccessorInfo());
if (filter == FixedArray::NON_SYMBOL_KEYS && value->IsSymbol()) {
continue;
}
accumulator->AddKey(value, prev_key_count);
}
DCHECK(extra == entry);
return result;
}
static uint32_t GetCapacityImpl(JSObject* holder,
......
......@@ -100,9 +100,9 @@ class ElementsAccessor {
virtual void GrowCapacityAndConvert(Handle<JSObject> object,
uint32_t capacity) = 0;
virtual Handle<FixedArray> AddElementsToFixedArray(
Handle<JSObject> receiver, Handle<FixedArray> to,
FixedArray::KeyFilter filter) = 0;
virtual void AddElementsToKeyAccumulator(Handle<JSObject> receiver,
KeyAccumulator* accumulator,
FixedArray::KeyFilter filter) = 0;
// Returns a shared ElementsAccessor for the specified ElementsKind.
static ElementsAccessor* ForKind(ElementsKind elements_kind) {
......
This diff is collapsed.
......@@ -2472,17 +2472,6 @@ class FixedArray: public FixedArrayBase {
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.
void CopyTo(int pos, FixedArray* dest, int dest_pos, int len);
......@@ -3661,6 +3650,9 @@ class OrderedHashTable: public FixedArray {
// exisiting iterators can be updated.
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() {
return Smi::cast(get(kNumberOfElementsIndex))->value();
}
......@@ -3680,6 +3672,26 @@ class OrderedHashTable: public FixedArray {
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)); }
bool IsObsolete() {
......@@ -3726,7 +3738,7 @@ class OrderedHashTable: public FixedArray {
// optimize that case.
static const int kClearedTableSentinel = -1;
private:
protected:
static Handle<Derived> Rehash(Handle<Derived> table, int new_capacity);
void SetNumberOfBuckets(int num) {
......@@ -3768,6 +3780,9 @@ class OrderedHashSet: public OrderedHashTable<
OrderedHashSet, JSSetIterator, 1> {
public:
DECLARE_CAST(OrderedHashSet)
static Handle<OrderedHashSet> Add(Handle<OrderedHashSet> table,
Handle<Object> value);
};
......@@ -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
#endif // V8_OBJECTS_H_
......@@ -197,38 +197,39 @@ RUNTIME_FUNCTION(Runtime_GetArrayKeys) {
DCHECK(args.length() == 2);
CONVERT_ARG_HANDLE_CHECKED(JSObject, array, 0);
CONVERT_NUMBER_CHECKED(uint32_t, length, Uint32, args[1]);
if (array->elements()->IsDictionary()) {
Handle<FixedArray> keys = isolate->factory()->empty_fixed_array();
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 {
if (!array->elements()->IsDictionary()) {
RUNTIME_ASSERT(array->HasFastSmiOrObjectElements() ||
array->HasFastDoubleElements());
uint32_t actual_length = static_cast<uint32_t>(array->elements()->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) {
// a fresh clone on each invocation.
int length = contents->length();
Handle<FixedArray> copy = isolate->factory()->NewFixedArray(length);
for (int i = 0; i < length; i++) {
Object* entry = contents->get(i);
if (entry->IsString()) {
copy->set(i, entry);
} else {
DCHECK(entry->IsNumber());
HandleScope scope(isolate);
Handle<Object> entry_handle(entry, isolate);
Handle<Object> entry_str =
isolate->factory()->NumberToString(entry_handle);
copy->set(i, *entry_str);
int offset = 0;
// Use an outer loop to avoid creating too many handles in the current
// handle scope.
while (offset < length) {
HandleScope scope(isolate);
offset += 100;
int end = Min(offset, length);
for (int i = offset - 100; i < end; i++) {
Object* entry = contents->get(i);
if (entry->IsString()) {
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);
......
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