-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Conversation
{ | ||
return JavascriptOperators::GetTypeId(aValue) == TypeIds_Set; | ||
JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_ObjectIsAlreadyInitialized, _u("Set"), _u("Set")); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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
Curious : what is motivation behind the change ? cpu perf or memory ? #Resolved |
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(); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
return scriptContext->GetLibrary()->GetUndefined(); | ||
JavascriptSet* set = JavascriptOperators::TryFromVar<JavascriptSet>(args[0]); |
There was a problem hiding this comment.
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
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
JavascriptMap* map = JavascriptMap::FromVar(args[0]); | ||
bool | ||
JavascriptMap::DeleteFromSimpleVarMap(Var value) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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()) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
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(); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
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 |
743368f
to
87c16ba
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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%.
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%.