Replace PushIfAbsent by a Stack object

This significantly speeds up String(array).
BUG=

Review URL: https://codereview.chromium.org/1775403008

Cr-Commit-Position: refs/heads/master@{#34745}
This commit is contained in:
verwaest 2016-03-14 07:44:04 -07:00 committed by Commit bot
parent 421a67b0f4
commit d358357478
8 changed files with 156 additions and 178 deletions

View File

@ -73,17 +73,13 @@ function DefineIndexedProperty(array, i, value) {
}
}
function KeySortCompare(a, b) {
return a - b;
}
// Global list of arrays visited during toString, toLocaleString and
// join invocations.
var visited_arrays = new InternalArray();
// Gets a sorted array of array keys. Useful for operations on sparse
// arrays. Dupes have not been removed.
function GetSortedArrayKeys(array, indices) {
var keys = new InternalArray();
if (IS_NUMBER(indices)) {
var keys = new InternalArray();
// It's an interval
var limit = indices;
for (var i = 0; i < limit; ++i) {
@ -92,61 +88,33 @@ function GetSortedArrayKeys(array, indices) {
keys.push(i);
}
}
} else {
var length = indices.length;
for (var k = 0; k < length; ++k) {
var key = indices[k];
if (!IS_UNDEFINED(key)) {
var e = array[key];
if (!IS_UNDEFINED(e) || key in array) {
keys.push(key);
}
}
}
keys.sort(function(a, b) { return a - b; });
}
return keys;
return InnerArraySort(indices, indices.length, KeySortCompare);
}
function SparseJoinWithSeparatorJS(array, len, convert, separator) {
var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
var totalLength = 0;
var elements = new InternalArray(keys.length * 2);
var previousKey = -1;
for (var i = 0; i < keys.length; i++) {
function SparseJoinWithSeparatorJS(array, keys, length, convert, separator) {
var keys_length = keys.length;
var elements = new InternalArray(keys_length * 2);
for (var i = 0; i < keys_length; i++) {
var key = keys[i];
if (key != previousKey) { // keys may contain duplicates.
var e = array[key];
if (!IS_STRING(e)) e = convert(e);
elements[i * 2] = key;
elements[i * 2 + 1] = e;
previousKey = key;
}
var e = array[key];
elements[i * 2] = key;
elements[i * 2 + 1] = IS_STRING(e) ? e : convert(e);
}
return %SparseJoinWithSeparator(elements, len, separator);
return %SparseJoinWithSeparator(elements, length, separator);
}
// Optimized for sparse arrays if separator is ''.
function SparseJoin(array, len, convert) {
var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
var last_key = -1;
function SparseJoin(array, keys, convert) {
var keys_length = keys.length;
var elements = new InternalArray(keys_length);
var elements_length = 0;
for (var i = 0; i < keys_length; i++) {
var key = keys[i];
if (key != last_key) {
var e = array[key];
if (!IS_STRING(e)) e = convert(e);
elements[elements_length++] = e;
last_key = key;
}
var e = array[keys[i]];
elements[i] = IS_STRING(e) ? e : convert(e);
}
return %StringBuilderConcat(elements, elements_length, '');
return %StringBuilderConcat(elements, keys_length, '');
}
@ -167,98 +135,122 @@ function UseSparseVariant(array, length, is_array, touched) {
(touched > estimated_elements * 4);
}
function Stack() {
this.length = 0;
this.values = new InternalArray();
}
// Predeclare the instance variables on the prototype. Otherwise setting them in
// the constructor will leak the instance through settings on Object.prototype.
Stack.prototype.length = null;
Stack.prototype.values = null;
function StackPush(stack, value) {
stack.values[stack.length++] = value;
}
function StackPop(stack) {
stack.values[--stack.length] = null
}
function StackHas(stack, v) {
var length = stack.length;
var values = stack.values;
for (var i = 0; i < length; i++) {
if (values[i] === v) return true;
}
return false;
}
// Global list of arrays visited during toString, toLocaleString and
// join invocations.
var visited_arrays = new Stack();
function DoJoin(array, length, is_array, separator, convert) {
if (UseSparseVariant(array, length, is_array, length)) {
%NormalizeElements(array);
var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, length));
if (separator === '') {
if (keys.length === 0) return '';
return SparseJoin(array, keys, convert);
} else {
return SparseJoinWithSeparatorJS(array, keys, length, convert, separator);
}
}
// Fast case for one-element arrays.
if (length === 1) {
var e = array[0];
return IS_STRING(e) ? e : convert(e);
}
// Construct an array for the elements.
var elements = new InternalArray(length);
// We pull the empty separator check outside the loop for speed!
if (separator === '') {
for (var i = 0; i < length; i++) {
var e = array[i];
elements[i] = IS_STRING(e) ? e : convert(e);
}
return %StringBuilderConcat(elements, length, '');
}
// Non-empty separator case.
// If the first element is a number then use the heuristic that the
// remaining elements are also likely to be numbers.
var e = array[0];
if (IS_NUMBER(e)) {
elements[0] = %_NumberToString(e);
for (var i = 1; i < length; i++) {
e = array[i];
if (IS_NUMBER(e)) {
elements[i] = %_NumberToString(e);
} else {
elements[i] = IS_STRING(e) ? e : convert(e);
}
}
} else {
elements[0] = IS_STRING(e) ? e : convert(e);
for (var i = 1; i < length; i++) {
e = array[i];
elements[i] = IS_STRING(e) ? e : convert(e);
}
}
return %StringBuilderJoin(elements, length, separator);
}
function Join(array, length, separator, convert) {
if (length == 0) return '';
if (length === 0) return '';
var is_array = IS_ARRAY(array);
if (is_array) {
// If the array is cyclic, return the empty string for already
// visited arrays.
if (!%PushIfAbsent(visited_arrays, array)) return '';
if (StackHas(visited_arrays, array)) return '';
StackPush(visited_arrays, array);
}
// Attempt to convert the elements.
try {
if (UseSparseVariant(array, length, is_array, length)) {
%NormalizeElements(array);
if (separator.length == 0) {
return SparseJoin(array, length, convert);
} else {
return SparseJoinWithSeparatorJS(array, length, convert, separator);
}
}
// Fast case for one-element arrays.
if (length == 1) {
var e = array[0];
if (IS_STRING(e)) return e;
return convert(e);
}
// Construct an array for the elements.
var elements = new InternalArray(length);
// We pull the empty separator check outside the loop for speed!
if (separator.length == 0) {
var elements_length = 0;
for (var i = 0; i < length; i++) {
var e = array[i];
if (!IS_STRING(e)) e = convert(e);
elements[elements_length++] = e;
}
elements.length = elements_length;
return %StringBuilderConcat(elements, elements_length, '');
}
// Non-empty separator case.
// If the first element is a number then use the heuristic that the
// remaining elements are also likely to be numbers.
var e = array[0];
if (!IS_NUMBER(e)) {
if (!IS_STRING(e)) e = convert(e);
elements[0] = e;
for (var i = 1; i < length; i++) {
e = array[i];
if (!IS_STRING(e)) e = convert(e);
elements[i] = e;
}
} else {
elements[0] = %_NumberToString(e);
for (var i = 1; i < length; i++) {
e = array[i];
if (IS_NUMBER(e)) {
e = %_NumberToString(e);
} else if (!IS_STRING(e)) {
e = convert(e);
}
elements[i] = e;
}
}
return %StringBuilderJoin(elements, length, separator);
return DoJoin(array, length, is_array, separator, convert);
} finally {
// Make sure to remove the last element of the visited array no
// matter what happens.
if (is_array) visited_arrays.length = visited_arrays.length - 1;
if (is_array) StackPop(visited_arrays);
}
}
function ConvertToString(x) {
if (IS_NULL_OR_UNDEFINED(x)) {
return '';
} else {
return TO_STRING(x);
}
if (IS_NULL_OR_UNDEFINED(x)) return '';
return TO_STRING(x);
}
function ConvertToLocaleString(e) {
if (IS_NULL_OR_UNDEFINED(e)) {
return '';
} else {
return TO_STRING(e.toLocaleString());
}
if (IS_NULL_OR_UNDEFINED(e)) return '';
return TO_STRING(e.toLocaleString());
}
@ -1959,6 +1951,10 @@ utils.Export(function(to) {
to.InnerArraySort = InnerArraySort;
to.InnerArrayToLocaleString = InnerArrayToLocaleString;
to.PackedArrayReverse = PackedArrayReverse;
to.Stack = Stack;
to.StackHas = StackHas;
to.StackPush = StackPush;
to.StackPop = StackPop;
});
%InstallToContext([

View File

@ -19,6 +19,10 @@ var MakeTypeError;
var MaxSimple;
var MinSimple;
var ObjectHasOwnProperty;
var Stack;
var StackHas;
var StackPop;
var StackPush;
var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol");
utils.Import(function(from) {
@ -26,6 +30,10 @@ utils.Import(function(from) {
MaxSimple = from.MaxSimple;
MinSimple = from.MinSimple;
ObjectHasOwnProperty = from.ObjectHasOwnProperty;
Stack = from.Stack;
StackHas = from.StackHas;
StackPop = from.StackPop;
StackPush = from.StackPush;
});
// -------------------------------------------------------------------
@ -78,7 +86,8 @@ function JSONParse(text, reviver) {
function SerializeArray(value, replacer, stack, indent, gap) {
if (!%PushIfAbsent(stack, value)) throw MakeTypeError(kCircularStructure);
if (StackHas(stack, value)) throw MakeTypeError(kCircularStructure);
StackPush(stack, value);
var stepback = indent;
indent += gap;
var partial = new InternalArray();
@ -101,13 +110,14 @@ function SerializeArray(value, replacer, stack, indent, gap) {
} else {
final = "[]";
}
stack.pop();
StackPop(stack);
return final;
}
function SerializeObject(value, replacer, stack, indent, gap) {
if (!%PushIfAbsent(stack, value)) throw MakeTypeError(kCircularStructure);
if (StackHas(stack, value)) throw MakeTypeError(kCircularStructure);
StackPush(stack, value);
var stepback = indent;
indent += gap;
var partial = new InternalArray();
@ -146,7 +156,7 @@ function SerializeObject(value, replacer, stack, indent, gap) {
} else {
final = "{}";
}
stack.pop();
StackPop(stack);
return final;
}
@ -241,7 +251,7 @@ function JSONStringify(value, replacer, space) {
if (!IS_CALLABLE(replacer) && !property_list && !gap && !IS_PROXY(value)) {
return %BasicJSONStringify(value);
}
return JSONSerialize('', {'': value}, replacer, new InternalArray(), "", gap);
return JSONSerialize('', {'': value}, replacer, new Stack(), "", gap);
}
// -------------------------------------------------------------------
@ -279,7 +289,7 @@ function JsonSerializeAdapter(key, object) {
var holder = {};
holder[key] = object;
// No need to pass the actual holder since there is no replacer function.
return JSONSerialize(key, holder, UNDEFINED, new InternalArray(), "", "");
return JSONSerialize(key, holder, UNDEFINED, new Stack(), "", "");
}
%InstallToContext(["json_serialize_adapter", JsonSerializeAdapter]);

View File

@ -17482,9 +17482,6 @@ template Handle<NameDictionary>
Dictionary<NameDictionary, NameDictionaryShape, Handle<Name> >::
EnsureCapacity(Handle<NameDictionary>, int, Handle<Name>);
template bool Dictionary<SeededNumberDictionary, SeededNumberDictionaryShape,
uint32_t>::HasComplexElements();
template int HashTable<SeededNumberDictionary, SeededNumberDictionaryShape,
uint32_t>::FindEntry(uint32_t);
@ -18325,6 +18322,21 @@ void Dictionary<Derived, Shape, Key>::AddEntry(
dictionary->ElementAdded();
}
bool SeededNumberDictionary::HasComplexElements() {
if (!requires_slow_elements()) return false;
int capacity = this->Capacity();
for (int i = 0; i < capacity; i++) {
Object* k = this->KeyAt(i);
if (this->IsKey(k)) {
DCHECK(!IsDeleted(i));
PropertyDetails details = this->DetailsAt(i);
if (details.type() == ACCESSOR_CONSTANT) return true;
PropertyAttributes attr = details.attributes();
if (attr & ALL_ATTRIBUTES_MASK) return true;
}
}
return false;
}
void SeededNumberDictionary::UpdateMaxNumberKey(uint32_t key,
bool used_as_prototype) {
@ -18432,23 +18444,6 @@ int Dictionary<Derived, Shape, Key>::NumberOfElementsFilterAttributes(
}
template <typename Derived, typename Shape, typename Key>
bool Dictionary<Derived, Shape, Key>::HasComplexElements() {
int capacity = this->Capacity();
for (int i = 0; i < capacity; i++) {
Object* k = this->KeyAt(i);
if (this->IsKey(k) && !k->FilterKey(ALL_PROPERTIES)) {
if (this->IsDeleted(i)) continue;
PropertyDetails details = this->DetailsAt(i);
if (details.type() == ACCESSOR_CONSTANT) return true;
PropertyAttributes attr = details.attributes();
if (attr & ALL_ATTRIBUTES_MASK) return true;
}
}
return false;
}
template <typename Dictionary>
struct EnumIndexComparator {
explicit EnumIndexComparator(Dictionary* dict) : dict(dict) {}

View File

@ -3485,10 +3485,6 @@ class Dictionary: public HashTable<Derived, Shape, Key> {
return NumberOfElementsFilterAttributes(ENUMERABLE_STRINGS);
}
// Returns true if the dictionary contains any elements that are non-writable,
// non-configurable, non-enumerable, or have getters/setters.
bool HasComplexElements();
enum SortMode { UNSORTED, SORTED };
// Fill in details for properties into storage.
@ -3721,6 +3717,10 @@ class SeededNumberDictionary
void UpdateMaxNumberKey(uint32_t key, bool used_as_prototype);
// Returns true if the dictionary contains any elements that are non-writable,
// non-configurable, non-enumerable, or have getters/setters.
bool HasComplexElements();
// If slow elements are required we will never go back to fast-case
// for the elements kept in this dictionary. We require slow
// elements if an element has been added at an index larger than

View File

@ -88,29 +88,6 @@ RUNTIME_FUNCTION(Runtime_TransitionElementsKind) {
}
// Push an object unto an array of objects if it is not already in the
// array. Returns true if the element was pushed on the stack and
// false otherwise.
RUNTIME_FUNCTION(Runtime_PushIfAbsent) {
HandleScope scope(isolate);
DCHECK(args.length() == 2);
CONVERT_ARG_HANDLE_CHECKED(JSArray, array, 0);
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, element, 1);
RUNTIME_ASSERT(array->HasFastSmiOrObjectElements());
int length = Smi::cast(array->length())->value();
FixedArray* elements = FixedArray::cast(array->elements());
for (int i = 0; i < length; i++) {
if (elements->get(i) == *element) return isolate->heap()->false_value();
}
// Strict not needed. Used for cycle detection in Array join implementation.
RETURN_FAILURE_ON_EXCEPTION(
isolate, JSObject::AddDataElement(array, length, element, NONE));
JSObject::ValidateElements(array);
return isolate->heap()->true_value();
}
// Moves all own elements of an object, that are below a limit, to positions
// starting at zero. All undefined values are placed after non-undefined values,
// and are followed by non-existing element. Does not change the length

View File

@ -642,13 +642,6 @@ RUNTIME_FUNCTION(Runtime_SparseJoinWithSeparator) {
RUNTIME_ASSERT(elements_length <= elements_array->elements()->length());
RUNTIME_ASSERT((elements_length & 1) == 0); // Even length.
FixedArray* elements = FixedArray::cast(elements_array->elements());
for (int i = 0; i < elements_length; i += 2) {
RUNTIME_ASSERT(elements->get(i)->IsNumber());
CONVERT_NUMBER_CHECKED(uint32_t, position, Uint32, elements->get(i));
RUNTIME_ASSERT(position < array_length);
RUNTIME_ASSERT(elements->get(i + 1)->IsString());
}
{
DisallowHeapAllocation no_gc;
for (int i = 0; i < elements_length; i += 2) {

View File

@ -35,7 +35,6 @@ namespace internal {
F(FinishArrayPrototypeSetup, 1, 1) \
F(SpecialArrayFunctions, 0, 1) \
F(TransitionElementsKind, 2, 1) \
F(PushIfAbsent, 2, 1) \
F(RemoveArrayHoles, 2, 1) \
F(MoveArrayContents, 2, 1) \
F(EstimateNumberOfElements, 1, 1) \

View File

@ -0,0 +1,8 @@
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
Object.defineProperty(Object.prototype, "length", {set() { throw "error" }});
Object.defineProperty(Object.prototype, "values", {set() { throw "error" }});
JSON.stringify({}, v=>v);