1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-08 05:27:14 +09:00

AK: Make String::number() much faster for integer types

Instead of going through String::formatted(), we now have a specialized
code path for base-10 serialization directly to UTF-8.

This is roughly 5-10x faster than the previous implementation, depending
on how many digits we end up outputting.

1.07x speedup on MicroBench/for-in-indexed-properties.js
This commit is contained in:
Andreas Kling 2025-05-02 13:12:14 +02:00 committed by Andreas Kling
parent 4d84e1a8bc
commit cf6e2531d9
Notes: github-actions[bot] 2025-05-02 17:13:56 +00:00
3 changed files with 99 additions and 1 deletions

View file

@ -531,4 +531,57 @@ String String::roman_number_from(size_t value, Case target_case)
return builder.to_string_without_validation(); return builder.to_string_without_validation();
} }
template<Integral T>
String String::number(T value)
{
// Maximum number of base-10 digits for T + sign
constexpr size_t max_digits = sizeof(T) * 3 + 2;
char buffer[max_digits];
char* ptr = buffer + max_digits;
bool is_negative = false;
using UnsignedT = MakeUnsigned<T>;
UnsignedT unsigned_value;
if constexpr (IsSigned<T>) {
if (value < 0) {
is_negative = true;
// Handle signed min correctly
unsigned_value = static_cast<UnsignedT>(0) - static_cast<UnsignedT>(value);
} else {
unsigned_value = static_cast<UnsignedT>(value);
}
} else {
unsigned_value = value;
}
if (unsigned_value == 0) {
*--ptr = '0';
} else {
while (unsigned_value != 0) {
*--ptr = '0' + (unsigned_value % 10);
unsigned_value /= 10;
}
}
if (is_negative) {
*--ptr = '-';
}
size_t size = buffer + max_digits - ptr;
return from_utf8_without_validation(ReadonlyBytes { ptr, size });
}
template String String::number(char);
template String String::number(signed char);
template String String::number(unsigned char);
template String String::number(signed short);
template String String::number(unsigned short);
template String String::number(int);
template String String::number(unsigned int);
template String String::number(long);
template String String::number(unsigned long);
template String String::number(long long);
template String String::number(unsigned long long);
} }

View file

@ -185,7 +185,10 @@ public:
[[nodiscard]] u32 ascii_case_insensitive_hash() const; [[nodiscard]] u32 ascii_case_insensitive_hash() const;
template<Arithmetic T> template<Integral T>
[[nodiscard]] static String number(T);
template<FloatingPoint T>
[[nodiscard]] static String number(T value) [[nodiscard]] static String number(T value)
{ {
return MUST(formatted("{}", value)); return MUST(formatted("{}", value));

View file

@ -1561,3 +1561,45 @@ TEST_CASE(roman_numerals)
auto four_thousand = String::roman_number_from(4000, String::Case::Upper); auto four_thousand = String::roman_number_from(4000, String::Case::Upper);
EXPECT_EQ(four_thousand, "4000"sv); EXPECT_EQ(four_thousand, "4000"sv);
} }
BENCHMARK_CASE(string_number_u16)
{
for (size_t i = 0; i < 10'000'000; ++i) {
(void)String::number(static_cast<u16>(12345));
}
}
BENCHMARK_CASE(string_number_u32)
{
for (size_t i = 0; i < 10'000'000; ++i) {
(void)String::number(static_cast<u32>(123456789));
}
}
BENCHMARK_CASE(string_number_u64)
{
for (size_t i = 0; i < 10'000'000; ++i) {
(void)String::number(static_cast<u64>(123456789));
}
}
BENCHMARK_CASE(string_number_i16)
{
for (size_t i = 0; i < 10'000'000; ++i) {
(void)String::number(static_cast<i16>(-12345));
}
}
BENCHMARK_CASE(string_number_i32)
{
for (size_t i = 0; i < 10'000'000; ++i) {
(void)String::number(static_cast<i32>(-123456789));
}
}
BENCHMARK_CASE(string_number_i64)
{
for (size_t i = 0; i < 10'000'000; ++i) {
(void)String::number(static_cast<i64>(-123456789));
}
}