Commit 1b35c0fa authored by Z Nguyen-Huu's avatar Z Nguyen-Huu Committed by Commit Bot

Improve NumberToString when cache miss and Smi

Cache miss was handled in runtime before. This change add fast path for
Smi in this case.

Perf show 30% improvement for the following example.
Before 67 ms
After 42 ms

const start = new Date();
const MAX = 1000000;
for (var i = 0; i < MAX; i++) {
    i.toString();
}
const end = new Date();
console.log("Time :"+ (end-start));

Change-Id: I162e9c35f58551ca6a5a0efe79fb7c7b482a8594
Bug: v8:10477
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2332866
Commit-Queue: Z Nguyen-Huu <duongn@microsoft.com>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#69362}
parent 1f743d2f
......@@ -67,6 +67,55 @@ macro ToCharCode(input: int32): char8 {
%RawDownCast<char8>(input - 10 + kAsciiLowerCaseA);
}
@export
macro NumberToStringSmi(x: int32, radix: int32): String labels Slow {
const isNegative: bool = x < 0;
let n: int32 = x;
if (!isNegative) {
// Fast case where the result is a one character string.
if (x < radix) {
return StringFromSingleCharCode(ToCharCode(n));
}
} else {
assert(isNegative);
if (n == kMinInt32) {
goto Slow;
}
n = 0 - n;
}
// Calculate length and pre-allocate the result string.
let temp: int32 = n;
let length: int32 = isNegative ? 1 : 0;
while (temp > 0) {
temp = temp / radix;
length = length + 1;
}
assert(length > 0);
const strSeq: SeqOneByteString = AllocateSeqOneByteString(Unsigned(length));
let cursor: intptr = Convert<intptr>(length) - 1;
while (n > 0) {
const digit: int32 = n % radix;
n = n / radix;
strSeq.chars[cursor] = ToCharCode(digit);
cursor = cursor - 1;
}
if (isNegative) {
assert(cursor == 0);
// Insert '-' to result.
strSeq.chars[0] = 45;
} else {
assert(cursor == -1);
// In sync with Factory::SmiToString: If radix = 10 and positive number,
// update hash for string.
if (radix == 10) {
assert(strSeq.hash_field == kNameEmptyHashField);
strSeq.hash_field = MakeArrayIndexHash(Unsigned(x), Unsigned(length));
}
}
return strSeq;
}
// https://tc39.github.io/ecma262/#sec-number.prototype.tostring
transitioning javascript builtin NumberPrototypeToString(
js-implicit context: NativeContext, receiver: JSAny)(...arguments): String {
......@@ -93,47 +142,8 @@ transitioning javascript builtin NumberPrototypeToString(
// value using the radix specified by radixNumber.
if (TaggedIsSmi(x)) {
const isNegative: bool = x < 0;
let n: int32 = Convert<int32>(UnsafeCast<Smi>(x));
if (!isNegative) {
// Fast case where the result is a one character string.
if (x < radixNumber) {
return StringFromSingleCharCode(ToCharCode(n));
}
} else {
assert(isNegative);
if (n == kMinInt32) {
return runtime::DoubleToStringWithRadix(x, radixNumber);
}
n = 0 - n;
}
const radix: int32 = Convert<int32>(radixNumber);
// Calculate length and pre-allocate the result string.
let temp: int32 = n;
let length: int32 = isNegative ? 1 : 0;
while (temp > 0) {
temp = temp / radix;
length = length + 1;
}
assert(length > 0);
const strSeq = UnsafeCast<SeqOneByteString>(
AllocateSeqOneByteString(Unsigned(length)));
let cursor: intptr = Convert<intptr>(length) - 1;
while (n > 0) {
const digit: int32 = n % radix;
n = n / radix;
strSeq.chars[cursor] = ToCharCode(digit);
cursor = cursor - 1;
}
if (isNegative) {
assert(cursor == 0);
// Insert '-' to result.
strSeq.chars[0] = 45;
} else {
assert(cursor == -1);
}
return strSeq;
return NumberToStringSmi(Convert<int32>(x), Convert<int32>(radixNumber))
otherwise return runtime::DoubleToStringWithRadix(x, radixNumber);
}
if (x == -0) {
......
......@@ -6717,12 +6717,53 @@ TNode<String> CodeStubAssembler::NumberToString(TNode<Number> input,
Signed(ChangeUint32ToWord(Int32Add(hash, hash)));
TNode<Object> smi_key =
UnsafeLoadFixedArrayElement(number_string_cache, entry_index);
GotoIf(TaggedNotEqual(smi_key, smi_input.value()), bailout);
Label if_smi_cache_missed(this);
GotoIf(TaggedNotEqual(smi_key, smi_input.value()), &if_smi_cache_missed);
// Smi match, return value from cache entry.
result = CAST(UnsafeLoadFixedArrayElement(number_string_cache, entry_index,
kTaggedSize));
Goto(&done);
BIND(&if_smi_cache_missed);
{
// Generate string and update string hash field.
result = NumberToStringSmi(SmiToInt32(smi_input.value()),
Int32Constant(10), bailout);
Label resize_cache(this), store_to_cache(this);
// Resize when the cache is not full-size.
const int kFullCacheSize =
isolate()->heap()->MaxNumberToStringCacheSize();
Branch(IntPtrLessThan(number_string_cache_length,
IntPtrConstant(kFullCacheSize)),
&resize_cache, &store_to_cache);
BIND(&resize_cache);
{
// Allocate and initialize the new_cache.
TNode<FixedArrayBase> new_cache =
AllocateFixedArray(HOLEY_ELEMENTS, IntPtrConstant(kFullCacheSize),
CodeStubAssembler::kAllowLargeObjectAllocation);
FillFixedArrayWithValue(HOLEY_ELEMENTS, new_cache, IntPtrConstant(0),
IntPtrConstant(kFullCacheSize),
RootIndex::kUndefinedValue);
StoreRoot(RootIndex::kNumberStringCache, new_cache);
Goto(&done);
}
BIND(&store_to_cache);
{
// Store string into cache.
StoreFixedArrayElement(number_string_cache, entry_index,
smi_input.value());
StoreFixedArrayElement(number_string_cache,
IntPtrAdd(entry_index, IntPtrConstant(1)),
result.value());
Goto(&done);
}
}
}
BIND(&done);
return result.value();
......
......@@ -9,7 +9,7 @@ extern class Name extends PrimitiveHeapObject {
}
bitfield struct NameHash extends uint32 {
hash_not_commputed: bool: 1 bit;
hash_not_computed: bool: 1 bit;
is_not_integer_index_mask: bool: 1 bit;
array_index_value: uint32: 24 bit;
array_index_length: uint32: 6 bit;
......@@ -38,7 +38,7 @@ type PublicSymbol extends Symbol;
type PrivateSymbol extends Symbol;
const kNameEmptyHashField: NameHash = NameHash{
hash_not_commputed: true,
hash_not_computed: true,
is_not_integer_index_mask: true,
array_index_value: 0,
array_index_length: 0
......@@ -46,3 +46,44 @@ const kNameEmptyHashField: NameHash = NameHash{
const kMaxCachedArrayIndexLength: constexpr uint32
generates 'Name::kMaxCachedArrayIndexLength';
const kMaxArrayIndexSize: constexpr uint32
generates 'Name::kMaxArrayIndexSize';
const kNofHashBitFields: constexpr int31
generates 'Name::kNofHashBitFields';
const kArrayIndexValueBits: constexpr int31
generates 'Name::kArrayIndexValueBits';
const kDoesNotContainCachedArrayIndexMask: constexpr uint32
generates 'Name::kDoesNotContainCachedArrayIndexMask';
const kIsNotIntegerIndexMask: constexpr uint32
generates 'Name::kIsNotIntegerIndexMask';
macro ContainsCachedArrayIndex(hash: uint32): bool {
return (hash & kDoesNotContainCachedArrayIndexMask) == 0;
}
const kArrayIndexValueBitsShift: uint32 = kNofHashBitFields;
const kArrayIndexLengthBitsShift: uint32 =
kNofHashBitFields + kArrayIndexValueBits;
macro TenToThe(exponent: uint32): uint32 {
assert(exponent <= 9);
let answer: int32 = 1;
for (let i: int32 = 0; i < Signed(exponent); i++) {
answer = answer * 10;
}
return Unsigned(answer);
}
macro MakeArrayIndexHash(value: uint32, length: uint32): NameHash {
// This is in sync with StringHasher::MakeArrayIndexHash.
assert(length <= kMaxArrayIndexSize);
const one: uint32 = 1;
assert(TenToThe(kMaxCachedArrayIndexLength) < (one << kArrayIndexValueBits));
let hash: uint32 = value;
hash = (hash << kArrayIndexValueBitsShift) |
(length << kArrayIndexLengthBitsShift);
assert((hash & kIsNotIntegerIndexMask) == 0);
assert(
(length <= kMaxCachedArrayIndexLength) == ContainsCachedArrayIndex(hash));
return %RawDownCast<NameHash>(hash);
}
......@@ -76,9 +76,9 @@ extern class ThinString extends String {
type DirectString extends String;
@export
macro AllocateSeqOneByteString(length: uint32): String {
macro AllocateSeqOneByteString(length: uint32): SeqOneByteString {
assert(length <= kStringMaxLength);
if (length == 0) return kEmptyString;
if (length == 0) return UnsafeCast<SeqOneByteString>(kEmptyString);
return new SeqOneByteString{
map: kOneByteStringMap,
hash_field: kNameEmptyHashField,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment