Make sure the identity hash is uniform (at least in the lower bits).
In the current implementation of hash code for objects (identity hash), we do not bother to shift the hash when we retrieve it from the hash-length bitfield in a property array. (Even worse, we store shifted value even if we do not have property array or inside dictionaries.) That means that the hash-code for objects is always divisible by 1024. Since our hash table uses a simple masking with (2^logsize - 1) to obtain the bucket, we get terrible hash collisions - essentially, our hash table degenerates to a linked list for fewer than 1024 elements. This CL always shifts the hash code so that the value in the lowest 21 bits is uniformly distributed. This results in big improvements on medium to large hash tables. A program storing 1M elements into a WeakMap gets roughly 17x faster. A program retrieving 1M elements from a Map improves even more dramatically (>100x). const a = []; for (let i = 0; i < 1e6; i++) a[i] = {}; const m = new Map(); console.time("Map.set"); for (let i = 0; i < 1e6; i++) { m.set(a[i], i); } console.timeEnd("Map.set"); console.time("Map.get"); let s = 0; for (let i = 0; i < 1e6; i++) { s += m.get(a[i]); } console.timeEnd("Map.get"); const w = new WeakMap(); console.time("WeakMap.set"); for (let i = 0; i < 1e6; i++) { w.set(a[i], i); } console.timeEnd("WeakMap.set"); Before the fix: Map.set: 157.575000 Map.get: 28333.182000 WeakMap.set: 6923.826000 After the fix: Map.set: 178.382000 Map.get: 185.930000 WeakMap.set: 409.529000 Note that Map does not suffer from the hash collision on insertion because it uses chaining (insertion into linked list is fast regardless of size!), and we cleverly avoid lookup in the hash table on update if the key does not have identity hash yet. This is in contrast to the WeakMap, which uses open-addressing, and deals with collisions on insertion. Bug: v8:6916 Change-Id: Ic5497bd4501e3b767b3f4acb7efb4784cbb3a2e4 Reviewed-on: https://chromium-review.googlesource.com/713616 Reviewed-by: Benedikt Meurer <bmeurer@chromium.org> Commit-Queue: Benedikt Meurer <bmeurer@chromium.org> Cr-Commit-Position: refs/heads/master@{#48480}
This commit is contained in:
parent
e34debaf2b
commit
a803fad068
@ -1253,8 +1253,8 @@ TNode<Int32T> CodeStubAssembler::LoadHashForJSObject(
|
||||
{
|
||||
Node* length_and_hash_int32 = LoadAndUntagToWord32ObjectField(
|
||||
properties_or_hash, PropertyArray::kLengthAndHashOffset);
|
||||
var_hash.Bind(Word32And(length_and_hash_int32,
|
||||
Int32Constant(PropertyArray::kHashMask)));
|
||||
var_hash.Bind(
|
||||
DecodeWord32<PropertyArray::HashField>(length_and_hash_int32));
|
||||
Goto(&done);
|
||||
}
|
||||
|
||||
@ -2644,7 +2644,8 @@ void CodeStubAssembler::InitializePropertyArrayLength(Node* property_array,
|
||||
CSA_ASSERT(
|
||||
this,
|
||||
IntPtrOrSmiLessThanOrEqual(
|
||||
length, IntPtrOrSmiConstant(PropertyArray::kMaxLength, mode), mode));
|
||||
length, IntPtrOrSmiConstant(PropertyArray::LengthField::kMax, mode),
|
||||
mode));
|
||||
StoreObjectFieldNoWriteBarrier(
|
||||
property_array, PropertyArray::kLengthAndHashOffset,
|
||||
ParameterToTagged(length, mode), MachineRepresentation::kTaggedSigned);
|
||||
|
@ -2438,14 +2438,18 @@ Node* JSNativeContextSpecialization::BuildExtendPropertiesBackingStore(
|
||||
jsgraph()->SmiConstant(PropertyArray::kNoHashSentinel));
|
||||
hash = graph()->NewNode(common()->TypeGuard(Type::SignedSmall()), hash,
|
||||
control);
|
||||
hash =
|
||||
graph()->NewNode(simplified()->NumberShiftLeft(), hash,
|
||||
jsgraph()->Constant(PropertyArray::HashField::kShift));
|
||||
} else {
|
||||
hash = effect = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForPropertyArrayLengthAndHash()),
|
||||
properties, effect, control);
|
||||
effect = graph()->NewNode(
|
||||
common()->BeginRegion(RegionObservability::kNotObservable), effect);
|
||||
hash = graph()->NewNode(simplified()->NumberBitwiseAnd(), hash,
|
||||
jsgraph()->Constant(JSReceiver::kHashMask));
|
||||
hash =
|
||||
graph()->NewNode(simplified()->NumberBitwiseAnd(), hash,
|
||||
jsgraph()->Constant(PropertyArray::HashField::kMask));
|
||||
}
|
||||
|
||||
Node* new_length_and_hash = graph()->NewNode(
|
||||
|
@ -1174,7 +1174,7 @@ void AccessorAssembler::ExtendPropertiesBackingStore(Node* object,
|
||||
// TODO(gsathya): Clean up the type conversions by creating smarter
|
||||
// helpers that do the correct op based on the mode.
|
||||
VARIABLE(var_properties, MachineRepresentation::kTaggedPointer);
|
||||
VARIABLE(var_hash, MachineRepresentation::kWord32);
|
||||
VARIABLE(var_encoded_hash, MachineRepresentation::kWord32);
|
||||
VARIABLE(var_length, ParameterRepresentation(mode));
|
||||
|
||||
Node* properties = LoadObjectField(object, JSObject::kPropertiesOrHashOffset);
|
||||
@ -1185,7 +1185,10 @@ void AccessorAssembler::ExtendPropertiesBackingStore(Node* object,
|
||||
|
||||
BIND(&if_smi_hash);
|
||||
{
|
||||
var_hash.Bind(SmiToWord32(properties));
|
||||
Node* hash = SmiToWord32(properties);
|
||||
Node* encoded_hash =
|
||||
Word32Shl(hash, Int32Constant(PropertyArray::HashField::kShift));
|
||||
var_encoded_hash.Bind(encoded_hash);
|
||||
var_length.Bind(IntPtrOrSmiConstant(0, mode));
|
||||
var_properties.Bind(EmptyFixedArrayConstant());
|
||||
Goto(&extend_store);
|
||||
@ -1195,10 +1198,11 @@ void AccessorAssembler::ExtendPropertiesBackingStore(Node* object,
|
||||
{
|
||||
Node* length_and_hash_int32 = LoadAndUntagToWord32ObjectField(
|
||||
var_properties.value(), PropertyArray::kLengthAndHashOffset);
|
||||
var_hash.Bind(Word32And(length_and_hash_int32,
|
||||
Int32Constant(PropertyArray::kHashMask)));
|
||||
Node* length_intptr = ChangeInt32ToIntPtr(Word32And(
|
||||
length_and_hash_int32, Int32Constant(PropertyArray::kLengthMask)));
|
||||
var_encoded_hash.Bind(Word32And(
|
||||
length_and_hash_int32, Int32Constant(PropertyArray::HashField::kMask)));
|
||||
Node* length_intptr = ChangeInt32ToIntPtr(
|
||||
Word32And(length_and_hash_int32,
|
||||
Int32Constant(PropertyArray::LengthField::kMask)));
|
||||
Node* length = WordToParameter(length_intptr, mode);
|
||||
var_length.Bind(length);
|
||||
Goto(&extend_store);
|
||||
@ -1244,7 +1248,7 @@ void AccessorAssembler::ExtendPropertiesBackingStore(Node* object,
|
||||
Node* new_capacity_int32 =
|
||||
TruncateWordToWord32(ParameterToWord(new_capacity, mode));
|
||||
Node* new_length_and_hash_int32 =
|
||||
Word32Or(var_hash.value(), new_capacity_int32);
|
||||
Word32Or(var_encoded_hash.value(), new_capacity_int32);
|
||||
StoreObjectField(new_properties, PropertyArray::kLengthAndHashOffset,
|
||||
SmiFromWord32(new_length_and_hash_int32));
|
||||
StoreObjectField(object, JSObject::kPropertiesOrHashOffset, new_properties);
|
||||
|
@ -2662,33 +2662,31 @@ SYNCHRONIZED_SMI_ACCESSORS(FixedArrayBase, length, kLengthOffset)
|
||||
int PropertyArray::length() const {
|
||||
Object* value_obj = READ_FIELD(this, kLengthAndHashOffset);
|
||||
int value = Smi::ToInt(value_obj);
|
||||
return value & kLengthMask;
|
||||
return LengthField::decode(value);
|
||||
}
|
||||
|
||||
void PropertyArray::initialize_length(int len) {
|
||||
SLOW_DCHECK(len >= 0);
|
||||
SLOW_DCHECK(len < kMaxLength);
|
||||
SLOW_DCHECK(len < LengthField::kMax);
|
||||
WRITE_FIELD(this, kLengthAndHashOffset, Smi::FromInt(len));
|
||||
}
|
||||
|
||||
int PropertyArray::synchronized_length() const {
|
||||
Object* value_obj = ACQUIRE_READ_FIELD(this, kLengthAndHashOffset);
|
||||
int value = Smi::ToInt(value_obj);
|
||||
return value & kLengthMask;
|
||||
return LengthField::decode(value);
|
||||
}
|
||||
|
||||
int PropertyArray::Hash() const {
|
||||
Object* value_obj = READ_FIELD(this, kLengthAndHashOffset);
|
||||
int value = Smi::ToInt(value_obj);
|
||||
int hash = value & kHashMask;
|
||||
return hash;
|
||||
return HashField::decode(value);
|
||||
}
|
||||
|
||||
void PropertyArray::SetHash(int masked_hash) {
|
||||
DCHECK_EQ(masked_hash & JSReceiver::kHashMask, masked_hash);
|
||||
void PropertyArray::SetHash(int hash) {
|
||||
Object* value_obj = READ_FIELD(this, kLengthAndHashOffset);
|
||||
int value = Smi::ToInt(value_obj);
|
||||
value = (value & kLengthMask) | masked_hash;
|
||||
value = HashField::update(value, hash);
|
||||
WRITE_FIELD(this, kLengthAndHashOffset, Smi::FromInt(value));
|
||||
}
|
||||
|
||||
|
@ -6387,22 +6387,22 @@ Handle<SeededNumberDictionary> JSObject::NormalizeElements(
|
||||
|
||||
namespace {
|
||||
|
||||
Object* SetHashAndUpdateProperties(HeapObject* properties, int masked_hash) {
|
||||
DCHECK_NE(PropertyArray::kNoHashSentinel, masked_hash);
|
||||
DCHECK_EQ(masked_hash & JSReceiver::kHashMask, masked_hash);
|
||||
Object* SetHashAndUpdateProperties(HeapObject* properties, int hash) {
|
||||
DCHECK_NE(PropertyArray::kNoHashSentinel, hash);
|
||||
DCHECK(PropertyArray::HashField::is_valid(hash));
|
||||
|
||||
if (properties == properties->GetHeap()->empty_fixed_array() ||
|
||||
properties == properties->GetHeap()->empty_property_dictionary()) {
|
||||
return Smi::FromInt(masked_hash);
|
||||
return Smi::FromInt(hash);
|
||||
}
|
||||
|
||||
if (properties->IsPropertyArray()) {
|
||||
PropertyArray::cast(properties)->SetHash(masked_hash);
|
||||
PropertyArray::cast(properties)->SetHash(hash);
|
||||
return properties;
|
||||
}
|
||||
|
||||
DCHECK(properties->IsDictionary());
|
||||
NameDictionary::cast(properties)->SetHash(masked_hash);
|
||||
NameDictionary::cast(properties)->SetHash(hash);
|
||||
return properties;
|
||||
}
|
||||
|
||||
@ -6433,14 +6433,14 @@ int GetIdentityHashHelper(Isolate* isolate, JSReceiver* object) {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void JSReceiver::SetIdentityHash(int masked_hash) {
|
||||
void JSReceiver::SetIdentityHash(int hash) {
|
||||
DisallowHeapAllocation no_gc;
|
||||
DCHECK_NE(PropertyArray::kNoHashSentinel, masked_hash);
|
||||
DCHECK_EQ(masked_hash & JSReceiver::kHashMask, masked_hash);
|
||||
DCHECK_NE(PropertyArray::kNoHashSentinel, hash);
|
||||
DCHECK(PropertyArray::HashField::is_valid(hash));
|
||||
|
||||
HeapObject* existing_properties = HeapObject::cast(raw_properties_or_hash());
|
||||
Object* new_properties =
|
||||
SetHashAndUpdateProperties(existing_properties, masked_hash);
|
||||
SetHashAndUpdateProperties(existing_properties, hash);
|
||||
set_raw_properties_or_hash(new_properties);
|
||||
}
|
||||
|
||||
@ -6495,11 +6495,11 @@ Smi* JSObject::GetOrCreateIdentityHash(Isolate* isolate) {
|
||||
return Smi::cast(hash_obj);
|
||||
}
|
||||
|
||||
int masked_hash = isolate->GenerateIdentityHash(JSReceiver::kHashMask);
|
||||
DCHECK_NE(PropertyArray::kNoHashSentinel, masked_hash);
|
||||
int hash = isolate->GenerateIdentityHash(PropertyArray::HashField::kMax);
|
||||
DCHECK_NE(PropertyArray::kNoHashSentinel, hash);
|
||||
|
||||
SetIdentityHash(masked_hash);
|
||||
return Smi::FromInt(masked_hash);
|
||||
SetIdentityHash(hash);
|
||||
return Smi::FromInt(hash);
|
||||
}
|
||||
|
||||
Object* JSProxy::GetIdentityHash() { return hash(); }
|
||||
|
@ -1954,17 +1954,10 @@ class PropertyArray : public HeapObject {
|
||||
// No weak fields.
|
||||
typedef BodyDescriptor BodyDescriptorWeak;
|
||||
|
||||
static const int kLengthMask = 0x3ff;
|
||||
#if V8_TARGET_ARCH_64_BIT
|
||||
static const int kHashMask = 0x7ffffc00;
|
||||
STATIC_ASSERT(kLengthMask + kHashMask == 0x7fffffff);
|
||||
#else
|
||||
static const int kHashMask = 0x3ffffc00;
|
||||
STATIC_ASSERT(kLengthMask + kHashMask == 0x3fffffff);
|
||||
#endif
|
||||
|
||||
static const int kMaxLength = kLengthMask;
|
||||
STATIC_ASSERT(kMaxLength > kMaxNumberOfDescriptors);
|
||||
static const int kLengthFieldSize = 10;
|
||||
class LengthField : public BitField<int, 0, kLengthFieldSize> {};
|
||||
class HashField : public BitField<int, kLengthFieldSize,
|
||||
kSmiValueSize - kLengthFieldSize - 1> {};
|
||||
|
||||
static const int kNoHashSentinel = 0;
|
||||
|
||||
@ -2191,7 +2184,7 @@ class JSReceiver: public HeapObject {
|
||||
MUST_USE_RESULT static MaybeHandle<FixedArray> GetOwnEntries(
|
||||
Handle<JSReceiver> object, PropertyFilter filter);
|
||||
|
||||
static const int kHashMask = PropertyArray::kHashMask;
|
||||
static const int kHashMask = PropertyArray::HashField::kMask;
|
||||
|
||||
// Layout description.
|
||||
static const int kPropertiesOrHashOffset = HeapObject::kHeaderSize;
|
||||
|
@ -138,14 +138,16 @@ class BaseNameDictionary : public Dictionary<Derived, Shape> {
|
||||
return Smi::ToInt(this->get(kNextEnumerationIndexIndex));
|
||||
}
|
||||
|
||||
void SetHash(int masked_hash) {
|
||||
DCHECK_EQ(masked_hash & JSReceiver::kHashMask, masked_hash);
|
||||
this->set(kObjectHashIndex, Smi::FromInt(masked_hash));
|
||||
void SetHash(int hash) {
|
||||
DCHECK(PropertyArray::HashField::is_valid(hash));
|
||||
this->set(kObjectHashIndex, Smi::FromInt(hash));
|
||||
}
|
||||
|
||||
int Hash() const {
|
||||
Object* hash_obj = this->get(kObjectHashIndex);
|
||||
return Smi::ToInt(hash_obj);
|
||||
int hash = Smi::ToInt(hash_obj);
|
||||
DCHECK(PropertyArray::HashField::is_valid(hash));
|
||||
return hash;
|
||||
}
|
||||
|
||||
// Creates a new dictionary.
|
||||
|
@ -184,7 +184,7 @@ TEST(Regress2060a) {
|
||||
Handle<JSObject> object = factory->NewJSObject(function, TENURED);
|
||||
CHECK(!heap->InNewSpace(*object));
|
||||
CHECK(!first_page->Contains(object->address()));
|
||||
int32_t hash = object->GetOrCreateHash(isolate)->value();
|
||||
int32_t hash = key->GetOrCreateHash(isolate)->value();
|
||||
JSWeakCollection::Set(weakmap, key, object, hash);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user