From b0a562b8b08495d4dbdc7e06422b06d33e00bccb Mon Sep 17 00:00:00 2001 From: Osei Fortune Date: Sat, 31 Oct 2026 19:05:31 -0400 Subject: [PATCH] perf(runtime): cache the instance prototype per class meta CreateJsWrapper only had a prototype cache for JS-extended classes (ClassPrototypes). Every other native object returned to JS rebuilt its prototype on each wrap: objc_getClass(meta->name()), the constructor template lookup, GetFunction, a new "prototype" string and a property Get. Without extra protocols the constructor template is cached per meta, so its prototype is fixed. MetaInstancePrototypes keeps it per meta and the wrap goes straight to SetPrototype. --- NativeScript/runtime/ArgConverter.mm | 38 ++++++++++++++++++---------- NativeScript/runtime/Caches.cpp | 1 + NativeScript/runtime/Caches.h | 3 +++ 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/NativeScript/runtime/ArgConverter.mm b/NativeScript/runtime/ArgConverter.mm index d5464db8..ed32c9d7 100644 --- a/NativeScript/runtime/ArgConverter.mm +++ b/NativeScript/runtime/ArgConverter.mm @@ -928,19 +928,31 @@ tns::Assert(false, isolate); } } else { - Class knownClass = objc_getClass(meta->name()); - KnownUnknownClassPair pair(knownClass, klass); - Local ctorFuncTemplate = cache->ObjectCtorInitializer( - context, static_cast(meta), pair, additionalProtocols); - Local ctorFunc; - bool success = ctorFuncTemplate->GetFunction(context).ToLocal(&ctorFunc); - tns::Assert(success, isolate); - - Local prototypeValue; - success = - ctorFunc->Get(context, tns::ToV8String(isolate, "prototype")).ToLocal(&prototypeValue); - tns::Assert(success, isolate); - Local prototype = prototypeValue.As(); + auto cached = additionalProtocols.empty() ? cache->MetaInstancePrototypes.find(meta) + : cache->MetaInstancePrototypes.end(); + Local prototype; + bool success; + if (cached != cache->MetaInstancePrototypes.end()) { + prototype = cached->second->Get(isolate); + } else { + Class knownClass = objc_getClass(meta->name()); + KnownUnknownClassPair pair(knownClass, klass); + Local ctorFuncTemplate = cache->ObjectCtorInitializer( + context, static_cast(meta), pair, additionalProtocols); + Local ctorFunc; + success = ctorFuncTemplate->GetFunction(context).ToLocal(&ctorFunc); + tns::Assert(success, isolate); + + Local prototypeValue; + success = + ctorFunc->Get(context, tns::ToV8String(isolate, "prototype")).ToLocal(&prototypeValue); + tns::Assert(success, isolate); + prototype = prototypeValue.As(); + if (additionalProtocols.empty()) { + cache->MetaInstancePrototypes.emplace( + meta, std::make_unique>(isolate, prototype)); + } + } if (!receiver->SetPrototype(context, prototype).To(&success) || !success) { tns::Assert(false, isolate); diff --git a/NativeScript/runtime/Caches.cpp b/NativeScript/runtime/Caches.cpp index dc1a3344..75e42bcb 100644 --- a/NativeScript/runtime/Caches.cpp +++ b/NativeScript/runtime/Caches.cpp @@ -22,6 +22,7 @@ Caches::~Caches() { this->Prototypes.clear(); this->ClassPrototypes.clear(); + this->MetaInstancePrototypes.clear(); this->CtorFuncTemplates.clear(); this->CtorFuncs.clear(); this->ProtocolCtorFuncs.clear(); diff --git a/NativeScript/runtime/Caches.h b/NativeScript/runtime/Caches.h index 666d7e63..be8281a8 100644 --- a/NativeScript/runtime/Caches.h +++ b/NativeScript/runtime/Caches.h @@ -145,6 +145,9 @@ class Caches { std::unique_ptr>, TransparentStringHash, TransparentStringEqual> ClassPrototypes; + robin_hood::unordered_map>> + MetaInstancePrototypes; robin_hood::unordered_map< const BaseClassMeta*, std::unique_ptr>>