Replace PushIfAbsent by a Stack object and move StringBuilderJoin to JS

This significantly speeds up String(array).
BUG=

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

Cr-Commit-Position: refs/heads/master@{#34696}
This commit is contained in:
verwaest 2016-03-10 15:24:12 -08:00 committed by Commit bot
parent aba76874db
commit c91faa0b39
6 changed files with 73 additions and 122 deletions

View File

@ -74,11 +74,6 @@ function DefineIndexedProperty(array, i, value) {
}
// 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) {
@ -150,6 +145,17 @@ function SparseJoin(array, len, convert) {
}
function StringBuilderJoin(elements, length, separator) {
length = MinSimple(elements.length, length);
if (length == 0) return "";
var result = elements[0];
for (var i = 1; i < length; i++) {
result = result + separator + elements[i];
}
return result;
}
function UseSparseVariant(array, length, is_array, touched) {
// Only use the sparse variant on arrays that are likely to be sparse and the
// number of elements touched in the operation is relatively small compared to
@ -167,6 +173,36 @@ 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 Join(array, length, separator, convert) {
if (length == 0) return '';
@ -176,7 +212,8 @@ function Join(array, length, separator, convert) {
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.
@ -235,11 +272,11 @@ function Join(array, length, separator, convert) {
elements[i] = e;
}
}
return %StringBuilderJoin(elements, length, separator);
return StringBuilderJoin(elements, length, separator);
} 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);
}
}
@ -1959,6 +1996,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

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

@ -497,89 +497,6 @@ RUNTIME_FUNCTION(Runtime_StringBuilderConcat) {
}
RUNTIME_FUNCTION(Runtime_StringBuilderJoin) {
HandleScope scope(isolate);
DCHECK(args.length() == 3);
CONVERT_ARG_HANDLE_CHECKED(JSArray, array, 0);
int32_t array_length;
if (!args[1]->ToInt32(&array_length)) {
THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewInvalidStringLengthError());
}
CONVERT_ARG_HANDLE_CHECKED(String, separator, 2);
RUNTIME_ASSERT(array->HasFastObjectElements());
RUNTIME_ASSERT(array_length >= 0);
Handle<FixedArray> fixed_array(FixedArray::cast(array->elements()));
if (fixed_array->length() < array_length) {
array_length = fixed_array->length();
}
if (array_length == 0) {
return isolate->heap()->empty_string();
} else if (array_length == 1) {
Object* first = fixed_array->get(0);
RUNTIME_ASSERT(first->IsString());
return first;
}
int separator_length = separator->length();
RUNTIME_ASSERT(separator_length > 0);
int max_nof_separators =
(String::kMaxLength + separator_length - 1) / separator_length;
if (max_nof_separators < (array_length - 1)) {
THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewInvalidStringLengthError());
}
int length = (array_length - 1) * separator_length;
for (int i = 0; i < array_length; i++) {
Object* element_obj = fixed_array->get(i);
RUNTIME_ASSERT(element_obj->IsString());
String* element = String::cast(element_obj);
int increment = element->length();
if (increment > String::kMaxLength - length) {
STATIC_ASSERT(String::kMaxLength < kMaxInt);
length = kMaxInt; // Provoke exception;
break;
}
length += increment;
}
Handle<SeqTwoByteString> answer;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, answer, isolate->factory()->NewRawTwoByteString(length));
DisallowHeapAllocation no_gc;
uc16* sink = answer->GetChars();
#ifdef DEBUG
uc16* end = sink + length;
#endif
RUNTIME_ASSERT(fixed_array->get(0)->IsString());
String* first = String::cast(fixed_array->get(0));
String* separator_raw = *separator;
int first_length = first->length();
String::WriteToFlat(first, sink, 0, first_length);
sink += first_length;
for (int i = 1; i < array_length; i++) {
DCHECK(sink + separator_length <= end);
String::WriteToFlat(separator_raw, sink, 0, separator_length);
sink += separator_length;
RUNTIME_ASSERT(fixed_array->get(i)->IsString());
String* element = String::cast(fixed_array->get(i));
int element_length = element->length();
DCHECK(sink + element_length <= end);
String::WriteToFlat(element, sink, 0, element_length);
sink += element_length;
}
DCHECK(sink == end);
// Use %_FastOneByteArrayJoin instead.
DCHECK(!answer->IsOneByteRepresentation());
return *answer;
}
template <typename Char>
static void JoinSparseArrayWithSeparator(FixedArray* elements,
int elements_length,

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) \
@ -851,7 +850,6 @@ namespace internal {
F(StringCharCodeAtRT, 2, 1) \
F(StringCompare, 2, 1) \
F(StringBuilderConcat, 3, 1) \
F(StringBuilderJoin, 3, 1) \
F(SparseJoinWithSeparator, 3, 1) \
F(StringToArray, 2, 1) \
F(StringToLowerCase, 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);