From 81d4ed27d8cbdcc483113742d228ca3542efea3e Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Mon, 14 Apr 2025 23:42:31 +0200 Subject: [PATCH] LibJS: Add cache for the string "[object Object]" This is very frequently returned by Object.prototype.toString(), so we benefit from caching it instead of recreating it every time. Takes Speedometer2.1/EmberJS-Debug-TodoMVC from ~4000ms to ~3700ms on my M3 MacBook Pro. --- Libraries/LibJS/Runtime/ObjectPrototype.cpp | 4 ++++ Libraries/LibJS/Runtime/VM.cpp | 20 +++++++++++--------- Libraries/LibJS/Runtime/VM.h | 3 ++- Libraries/LibJS/Runtime/Value.cpp | 20 ++++++++++---------- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/Libraries/LibJS/Runtime/ObjectPrototype.cpp b/Libraries/LibJS/Runtime/ObjectPrototype.cpp index b9e85ba6d2f..7dba90fe8e9 100644 --- a/Libraries/LibJS/Runtime/ObjectPrototype.cpp +++ b/Libraries/LibJS/Runtime/ObjectPrototype.cpp @@ -193,6 +193,10 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::to_string) tag = to_string_tag.as_string().utf8_string_view(); // 17. Return the string-concatenation of "[object ", tag, and "]". + + // OPTIMIZATION: The VM has a cache for the extremely common "[object Object]" string. + if (tag == "Object"sv) + return vm.cached_strings.object_Object; return PrimitiveString::create(vm, MUST(String::formatted("[object {}]", tag))); } diff --git a/Libraries/LibJS/Runtime/VM.cpp b/Libraries/LibJS/Runtime/VM.cpp index 2bf87066011..671265ecedb 100644 --- a/Libraries/LibJS/Runtime/VM.cpp +++ b/Libraries/LibJS/Runtime/VM.cpp @@ -74,7 +74,7 @@ VM::VM(OwnPtr custom_data, ErrorMessages error_messages) m_empty_string = m_heap.allocate(String {}); - typeof_strings = { + cached_strings = { .number = m_heap.allocate("number"_string), .undefined = m_heap.allocate("undefined"_string), .object = m_heap.allocate("object"_string), @@ -83,6 +83,7 @@ VM::VM(OwnPtr custom_data, ErrorMessages error_messages) .boolean = m_heap.allocate("boolean"_string), .bigint = m_heap.allocate("bigint"_string), .function = m_heap.allocate("function"_string), + .object_Object = m_heap.allocate("[object Object]"_string), }; for (size_t i = 0; i < single_ascii_character_strings.size(); ++i) @@ -239,14 +240,15 @@ void VM::gather_roots(HashMap& roots) for (auto string : m_single_ascii_character_strings) roots.set(string, GC::HeapRoot { .type = GC::HeapRoot::Type::VM }); - roots.set(typeof_strings.number, GC::HeapRoot { .type = GC::HeapRoot::Type::VM }); - roots.set(typeof_strings.undefined, GC::HeapRoot { .type = GC::HeapRoot::Type::VM }); - roots.set(typeof_strings.object, GC::HeapRoot { .type = GC::HeapRoot::Type::VM }); - roots.set(typeof_strings.string, GC::HeapRoot { .type = GC::HeapRoot::Type::VM }); - roots.set(typeof_strings.symbol, GC::HeapRoot { .type = GC::HeapRoot::Type::VM }); - roots.set(typeof_strings.boolean, GC::HeapRoot { .type = GC::HeapRoot::Type::VM }); - roots.set(typeof_strings.bigint, GC::HeapRoot { .type = GC::HeapRoot::Type::VM }); - roots.set(typeof_strings.function, GC::HeapRoot { .type = GC::HeapRoot::Type::VM }); + roots.set(cached_strings.number, GC::HeapRoot { .type = GC::HeapRoot::Type::VM }); + roots.set(cached_strings.undefined, GC::HeapRoot { .type = GC::HeapRoot::Type::VM }); + roots.set(cached_strings.object, GC::HeapRoot { .type = GC::HeapRoot::Type::VM }); + roots.set(cached_strings.string, GC::HeapRoot { .type = GC::HeapRoot::Type::VM }); + roots.set(cached_strings.symbol, GC::HeapRoot { .type = GC::HeapRoot::Type::VM }); + roots.set(cached_strings.boolean, GC::HeapRoot { .type = GC::HeapRoot::Type::VM }); + roots.set(cached_strings.bigint, GC::HeapRoot { .type = GC::HeapRoot::Type::VM }); + roots.set(cached_strings.function, GC::HeapRoot { .type = GC::HeapRoot::Type::VM }); + roots.set(cached_strings.object_Object, GC::HeapRoot { .type = GC::HeapRoot::Type::VM }); #define __JS_ENUMERATE(SymbolName, snake_name) \ roots.set(m_well_known_symbols.snake_name, GC::HeapRoot { .type = GC::HeapRoot::Type::VM }); diff --git a/Libraries/LibJS/Runtime/VM.h b/Libraries/LibJS/Runtime/VM.h index 1d5ee4580e6..f1309bdf4e6 100644 --- a/Libraries/LibJS/Runtime/VM.h +++ b/Libraries/LibJS/Runtime/VM.h @@ -229,7 +229,8 @@ public: GC::Ptr boolean; GC::Ptr bigint; GC::Ptr function; - } typeof_strings; + GC::Ptr object_Object; + } cached_strings; void run_queued_promise_jobs(); void enqueue_promise_job(GC::Ref()>> job, Realm*); diff --git a/Libraries/LibJS/Runtime/Value.cpp b/Libraries/LibJS/Runtime/Value.cpp index eedf42c81f3..76045464b81 100644 --- a/Libraries/LibJS/Runtime/Value.cpp +++ b/Libraries/LibJS/Runtime/Value.cpp @@ -326,38 +326,38 @@ GC::Ref Value::typeof_(VM& vm) const { // 9. If val is a Number, return "number". if (is_number()) - return *vm.typeof_strings.number; + return *vm.cached_strings.number; switch (m_value.tag) { // 4. If val is undefined, return "undefined". case UNDEFINED_TAG: - return *vm.typeof_strings.undefined; + return *vm.cached_strings.undefined; // 5. If val is null, return "object". case NULL_TAG: - return *vm.typeof_strings.object; + return *vm.cached_strings.object; // 6. If val is a String, return "string". case STRING_TAG: - return *vm.typeof_strings.string; + return *vm.cached_strings.string; // 7. If val is a Symbol, return "symbol". case SYMBOL_TAG: - return *vm.typeof_strings.symbol; + return *vm.cached_strings.symbol; // 8. If val is a Boolean, return "boolean". case BOOLEAN_TAG: - return *vm.typeof_strings.boolean; + return *vm.cached_strings.boolean; // 10. If val is a BigInt, return "bigint". case BIGINT_TAG: - return *vm.typeof_strings.bigint; + return *vm.cached_strings.bigint; // 11. Assert: val is an Object. case OBJECT_TAG: // B.3.6.3 Changes to the typeof Operator, https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot-typeof // 12. If val has an [[IsHTMLDDA]] internal slot, return "undefined". if (as_object().is_htmldda()) - return *vm.typeof_strings.undefined; + return *vm.cached_strings.undefined; // 13. If val has a [[Call]] internal slot, return "function". if (is_function()) - return *vm.typeof_strings.function; + return *vm.cached_strings.function; // 14. Return "object". - return *vm.typeof_strings.object; + return *vm.cached_strings.object; default: VERIFY_NOT_REACHED(); }