Skip to content

Add specialized types of Sets and Maps - #4816

Merged
chakrabot merged 7 commits into
chakra-core:masterfrom
MikeHolman:setopt_pr
Mar 20, 2018
Merged

Add specialized types of Sets and Maps#4816
chakrabot merged 7 commits into
chakra-core:masterfrom
MikeHolman:setopt_pr

Conversation

@MikeHolman

@MikeHolman MikeHolman commented Mar 13, 2018

Copy link
Copy Markdown
Contributor

This change separates sets into three different types: Int, Simple, and Complex.

Int sets are sets which contain only int values. They are represented with a bit vector.
Simple sets contain values that are comparable by pointer alone, and use a normal dictionary.
Complex sets are sets that need full value comparison, and use a dictionary with a custom comparator.

I also added two kinds of maps, Simple and Complex, which function the same as simple and complex sets.

Improves perf of ARES-6 by 6% and VueJS by about 4%.

if (setObject->kind != SetKind::EmptySet)
{
return JavascriptOperators::GetTypeId(aValue) == TypeIds_Set;
JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_ObjectIsAlreadyInitialized, _u("Set"), _u("Set"));

@akroshg akroshg Mar 13, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert (or telemetry assert) if that is the case? #Resolved


if (!JavascriptNumber::TryGetInt32Value<true>(doubleVal, &intVal))
{
return nullptr;

@akroshg akroshg Mar 13, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nullptr [](start = 18, length = 7)

returning nullptr or false in this function seems to be inconsistent with return type. #Resolved

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops good point


In reply to: 174315122 [](ancestors = 174315122)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i meant for nullptr in the failure case (similar to what we do for the TryFromVar methods)


In reply to: 174315376 [](ancestors = 174315376,174315122)

Comment thread lib/Runtime/Library/JavascriptSet.cpp Outdated
newIntSet->Set(intVal);

bool hasValue = set->Has(value);
this->list.Append(taggedInt, GetScriptContext()->GetRecycler());

@akroshg akroshg Mar 13, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetScriptContext()->GetRecycler() [](start = 37, length = 33)

nit: you can use GetRecycler() here directly. #Resolved

@akroshg

akroshg commented Mar 13, 2018

Copy link
Copy Markdown
Contributor

Curious : what is motivation behind the change ? cpu perf or memory ? #Resolved

@MikeHolman

MikeHolman commented Mar 13, 2018

Copy link
Copy Markdown
Contributor Author

Motivation was CPU perf. I think there will be small wins in VueJS and ARES-6 due to faster map/set lookups (running perf tests now) #Resolved

case SetKind::EmptySet:
return;
case SetKind::IntSet:
this->u.intSet->ClearAll();

@akroshg akroshg Mar 13, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we consider changing the kind to EmptySet ? #Pending

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered it, but figured that often after clearing a set you will want to put things in it again, so it's cheaper to avoid extra allocation


In reply to: 174316471 [](ancestors = 174316471)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good.


In reply to: 174319064 [](ancestors = 174319064,174316471)

ScriptContext* scriptContext = function->GetScriptContext();

return scriptContext->GetLibrary()->GetUndefined();
JavascriptSet* set = JavascriptOperators::TryFromVar<JavascriptSet>(args[0]);

@akroshg akroshg Mar 13, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think other functions below will require same love. :) #Resolved

Comment thread lib/Runtime/Library/JavascriptSet.h Outdated
// Deleting any element will also cause the set to be promoted to a SimpleVarSet
IntSet,
// A SimpleVarSet is a set containing only Vars which are comparable by pointer, and don't require
// pointer comparison

@akroshg akroshg Mar 13, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value comparison ? #Resolved


Var JavascriptSet::EntryHas(RecyclableObject* function, CallInfo callInfo, ...)
JavascriptSet::SetDataList::Iterator iter = this->list.GetIterator();
// TODO: we can use a more efficient Iterator, since we know there will be no side effects

@akroshg akroshg Mar 13, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a good point. Should we mark as JS_NO_REENTRANT region so that it is easy to explain that? #Resolved

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added lock to all of the general entrypoints


In reply to: 174318320 [](ancestors = 174318320)

@agarwal-sandeep

agarwal-sandeep commented Mar 14, 2018

Copy link
Copy Markdown
Collaborator
// 3.    If Type(x) is Null, return true.

This says if type is undefined or null the return is true but you changed it to false below? #Pending


Refers to: lib/Runtime/Language/JavascriptConversion.cpp:44 in 985b16f. [](commit_id = 985b16f, deletion_comment = False)


JavascriptMap* map = JavascriptMap::FromVar(args[0]);
bool
JavascriptMap::DeleteFromSimpleVarMap(Var value)

@agarwal-sandeep agarwal-sandeep Mar 14, 2018

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put assert that map kind is SimpleVarMap #Resolved


if (!JavascriptMap::Is(args[0]))
bool
JavascriptMap::Has(Var key)

@agarwal-sandeep agarwal-sandeep Mar 14, 2018

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For discussion: ContainsKey and TryGetValue only differ in terms of return.
Since the logic of Get and Has is same should we just use Get and return the result ignoring the value? #Pending

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i tend to agree, but it's only a few lines of code and there might be some extra cost from those stores and bigger code might make inlining less aggressive... obviously small costs, but the code duplication win is also pretty small so i'm not sure it's worth it
if it gets any more complicated we should definitely merge these paths though


In reply to: 174324761 [](ancestors = 174324761)

return scriptContext->GetLibrary()->CreateMapIterator(map, JavascriptMapIteratorKind::Value);
JavascriptMap::MapDataList::Iterator iter = this->list.GetIterator();
// TODO: we can use a more efficient Iterator, since we know there will be no side effects
while (iter.Next())

@agarwal-sandeep agarwal-sandeep Mar 14, 2018

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Next [](start = 16, length = 4)

There is comment "Nodes can be deleted while iterating so validate current" in MapOrSetDataList.h Next() does that have any effect? #Pending

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in general case, this iterator can be exposed to users, so elements might get deleted during iteration, but here that can't happen


In reply to: 174325329 [](ancestors = 174325329)


ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
newSimpleSet->Add(simpleVar, node);

@agarwal-sandeep agarwal-sandeep Mar 14, 2018

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of adding to newIntSet fist and then to this->list as done in AddToEmptySet for case if Append throws then there is no inconsistency. #Pending

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i thought the same. unfortunately i can't really do it here, because i need the node before adding to the set. i thought i might be able to add a dummy node and then update it, but seemed like might just be better to rely on our OOM failfast to maintain consistency


In reply to: 174326068 [](ancestors = 174326068)

}

void
JavascriptSet::AddToEmptySet(Var value)

@agarwal-sandeep agarwal-sandeep Mar 14, 2018

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AddToEmptySet [](start = 15, length = 13)

nit: Can we put assert in all TryAddTo* for map kind? #Resolved

@MikeHolman

Copy link
Copy Markdown
Contributor Author
// 3.    If Type(x) is Null, return true.

could be wrong, but I thought that if aLeft != aRight, then this equality check can't match


In reply to: 372860936 [](ancestors = 372860936)


Refers to: lib/Runtime/Language/JavascriptConversion.cpp:44 in 985b16f. [](commit_id = 985b16f, deletion_comment = False)

return set->Remove(value);
return false;
}
this->PromoteToSimpleVarSet();

@agarwal-sandeep agarwal-sandeep Mar 14, 2018

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this->PromoteToSimpleVarSet(); [](start = 8, length = 30)

Why is a conversion required? #Resolved

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's hard to maintain list after deleting elements from an int set, because we don't have pointer to the element in the list. there might be some more complex work that can allow us to push the conversion until point where we need an iterator, but it seemed like pretty big complication, and scenario i'm looking at doesn't use delete (it just uses clear, which is fine).
I think this deserves a comment for now, and if it comes up, maybe we can improve this in future


In reply to: 174327605 [](ancestors = 174327605)

if (JavascriptOperators::IsUndefinedOrNullType(leftType))
{
return leftType == rightType;
return false;

@akroshg akroshg Mar 14, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if both values are undefined but belonged to different script context? #Pending

@MikeHolman MikeHolman Mar 14, 2018

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have thought they should be marshalled to correct script context by here? If that's not true, then Booleans are broken already in the existing code. #Pending

Comment thread lib/Runtime/Library/JavascriptMap.h Outdated
// An EmptyMap is a map containing no elements
EmptyMap,
// A SimpleVarMap is a map containing only Vars which are comparable by pointer, and don't require
// pointer comparison

@sethbrenith sethbrenith Mar 15, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pointer comparison [](start = 15, length = 18)

value comparison #Resolved

}
case TypeIds_String:
case TypeIds_Symbol:
// TODO: try to canonicalize Int64/Uint64 to TaggedInt

@sethbrenith sethbrenith Mar 15, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TODO seems like a correctness issue, not an opportunity for future optimization, so I'm leaving a comment here to follow up. In particular, passing a 64-bit int to Has could incorrectly return false because it failed to canonicalize. #Resolved

@MikeHolman MikeHolman Mar 15, 2018

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I’ll add cases for these. #Closed

#endif
}
case TypeIds_String:
case TypeIds_Symbol:

@sethbrenith sethbrenith Mar 15, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeIds_Symbol [](start = 13, length = 14)

It looks like there's also an existing problem where SameValueComparer doesn't have any special case for Symbols and hashes them by reference. Would you like to fix that while you're working on related things, or should I open a separate bug? #Resolved

@MikeHolman MikeHolman Mar 15, 2018

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure I can fix it. #Closed

@MikeHolman

MikeHolman commented Mar 15, 2018

Copy link
Copy Markdown
Contributor Author

I realized that I have a bug with -0. I can’t canonicalize it to 0 when inserting into set/map, because when iterating over the set it’s expected that you get -0 out. (Same with Int64 types). #Resolved

@MikeHolman
MikeHolman force-pushed the setopt_pr branch 5 times, most recently from 743368f to 87c16ba Compare March 15, 2018 21:52

@sethbrenith sethbrenith left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@akroshg akroshg left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@chakrabot
chakrabot merged commit f9639b7 into chakra-core:master Mar 20, 2018
chakrabot pushed a commit that referenced this pull request Mar 20, 2018
Merge pull request #4816 from MikeHolman:setopt_pr

This change separates sets into three different types: Int, Simple, and Complex.

Int sets are sets which contain only int values. They are represented with a bit vector.
Simple sets contain values that are comparable by pointer alone, and use a normal dictionary.
Complex sets are sets that need full value comparison, and use a dictionary with a custom comparator.

I also added two kinds of maps, Simple and Complex, which function the same as simple and complex sets.

Improves perf of ARES-6 by 6% and VueJS by about 4%.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants