Skip to content

Commit

Permalink
deps: cherry-pick a803fad from upstream V8
Browse files Browse the repository at this point in the history
Original commit message:

    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}

PR-URL: #19824
Refs: v8/v8@a803fad
Fixes: #19769
Reviewed-By: Yang Guo <yangguo@chromium.org>
Reviewed-By: Gibson Fahnestock <gibfahn@gmail.com>
  • Loading branch information
targos authored and gibfahn committed Apr 12, 2018
1 parent 09f5e25 commit f12db24
Show file tree
Hide file tree
Showing 9 changed files with 54 additions and 52 deletions.
2 changes: 1 addition & 1 deletion deps/v8/include/v8-version.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#define V8_MAJOR_VERSION 6
#define V8_MINOR_VERSION 2
#define V8_BUILD_NUMBER 414
#define V8_PATCH_LEVEL 52
#define V8_PATCH_LEVEL 53

// Use 1 for candidates and 0 otherwise.
// (Boolean macro values are not supported by all preprocessors.)
Expand Down
7 changes: 4 additions & 3 deletions deps/v8/src/code-stub-assembler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1187,8 +1187,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);
}

Expand Down Expand Up @@ -2508,7 +2508,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);
Expand Down
8 changes: 6 additions & 2 deletions deps/v8/src/compiler/js-native-context-specialization.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2255,14 +2255,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(
Expand Down
18 changes: 11 additions & 7 deletions deps/v8/src/ic/accessor-assembler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1091,7 +1091,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);
Expand All @@ -1102,7 +1102,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);
Expand All @@ -1112,10 +1115,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);
Expand Down Expand Up @@ -1161,7 +1165,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);
Expand Down
14 changes: 6 additions & 8 deletions deps/v8/src/objects-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -2679,33 +2679,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));
}

Expand Down
28 changes: 14 additions & 14 deletions deps/v8/src/objects.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6269,22 +6269,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;
}

Expand Down Expand Up @@ -6315,14 +6315,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);
}

Expand Down Expand Up @@ -6377,11 +6377,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(); }
Expand Down
17 changes: 5 additions & 12 deletions deps/v8/src/objects.h
Original file line number Diff line number Diff line change
Expand Up @@ -1953,17 +1953,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;

Expand Down Expand Up @@ -2190,7 +2183,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;
Expand Down
10 changes: 6 additions & 4 deletions deps/v8/src/objects/dictionary.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion deps/v8/test/cctest/test-weakmaps.cc
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,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);
}
}
Expand Down

0 comments on commit f12db24

Please sign in to comment.