Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add specialized types of Sets and Maps #4816

Merged
merged 7 commits into from
Mar 20, 2018

Conversation

MikeHolman
Copy link
Contributor

@MikeHolman MikeHolman commented Mar 13, 2018

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%.

{
return JavascriptOperators::GetTypeId(aValue) == TypeIds_Set;
JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_ObjectIsAlreadyInitialized, _u("Set"), _u("Set"));
Copy link
Contributor

@akroshg akroshg Mar 13, 2018

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;
Copy link
Contributor

@akroshg akroshg Mar 13, 2018

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
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
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)


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

@akroshg akroshg Mar 13, 2018

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
Copy link
Contributor

akroshg commented Mar 13, 2018

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

@MikeHolman
Copy link
Contributor Author

MikeHolman commented Mar 13, 2018

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();
Copy link
Contributor

@akroshg akroshg Mar 13, 2018

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
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
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)


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

@akroshg akroshg Mar 13, 2018

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

// 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
Copy link
Contributor

@akroshg akroshg Mar 13, 2018

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
Copy link
Contributor

@akroshg akroshg Mar 13, 2018

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
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
Copy link
Collaborator

agarwal-sandeep commented Mar 14, 2018

// 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)
Copy link
Collaborator

@agarwal-sandeep agarwal-sandeep Mar 14, 2018

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)
Copy link
Collaborator

@agarwal-sandeep agarwal-sandeep Mar 14, 2018

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
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())
Copy link
Collaborator

@agarwal-sandeep agarwal-sandeep Mar 14, 2018

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
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);
Copy link
Collaborator

@agarwal-sandeep agarwal-sandeep Mar 14, 2018

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
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)
Copy link
Collaborator

@agarwal-sandeep agarwal-sandeep Mar 14, 2018

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
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)

}
this->PromoteToSimpleVarSet();
Copy link
Collaborator

@agarwal-sandeep agarwal-sandeep Mar 14, 2018

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
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;
Copy link
Contributor

@akroshg akroshg Mar 14, 2018

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

Copy link
Contributor Author

@MikeHolman MikeHolman Mar 14, 2018

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

// 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
Copy link
Contributor

@sethbrenith sethbrenith Mar 15, 2018

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
Copy link
Contributor

@sethbrenith sethbrenith Mar 15, 2018

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

Copy link
Contributor Author

@MikeHolman MikeHolman Mar 15, 2018

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:
Copy link
Contributor

@sethbrenith sethbrenith Mar 15, 2018

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

Copy link
Contributor Author

@MikeHolman MikeHolman Mar 15, 2018

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
Copy link
Contributor Author

MikeHolman commented Mar 15, 2018

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
Copy link
Contributor

@sethbrenith sethbrenith left a comment

Choose a reason for hiding this comment

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

:shipit:

Copy link
Contributor

@akroshg akroshg left a comment

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