Skip to content

Commit b526139

Browse files
committed
perf(ffi/v8): inline Value storage to eliminate heap allocation per primitive
The Value type previously used std::shared_ptr<ValueStorage> for every value, causing a heap allocation + atomic ref count on every Value creation. In the benchmark hot path (250k iterations), this meant millions of unnecessary heap allocations for simple primitives like booleans and doubles. Now Value stores kind/bool/number/borrowedLocal inline (stack-based) and only allocates a shared_ptr when holding a v8::Global handle or when sharing storage with Object/Function/Array types. This eliminates heap allocation for: - Value() (undefined) - Value(bool) - Value(double/int) - Value::null() - Value::borrowed(runtime, local) Tests: macOS v8 713/0
1 parent fe9e2ee commit b526139

2 files changed

Lines changed: 127 additions & 88 deletions

File tree

NativeScript/ffi/v8/NativeApiV8Runtime.h

Lines changed: 50 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ struct RuntimeState {
126126
};
127127

128128
struct ValueStorage {
129-
enum class Kind {
129+
enum class Kind : uint8_t {
130130
Undefined,
131131
Null,
132132
Bool,
@@ -279,34 +279,37 @@ class String {
279279

280280
class Value {
281281
public:
282-
Value()
283-
: storage_(
284-
std::make_shared<v8engine::ValueStorage>(v8engine::ValueStorage::Kind::Undefined)) {}
282+
Value() : kind_(v8engine::ValueStorage::Kind::Undefined) {}
285283

286-
Value(bool value)
287-
: storage_(std::make_shared<v8engine::ValueStorage>(v8engine::ValueStorage::Kind::Bool)) {
288-
storage_->boolValue = value;
289-
}
284+
Value(bool value) : kind_(v8engine::ValueStorage::Kind::Bool), boolValue_(value) {}
290285

291-
Value(double value)
292-
: storage_(std::make_shared<v8engine::ValueStorage>(v8engine::ValueStorage::Kind::Number)) {
293-
storage_->numberValue = value;
294-
}
286+
Value(double value) : kind_(v8engine::ValueStorage::Kind::Number), numberValue_(value) {}
295287

296288
Value(int value) : Value(static_cast<double>(value)) {}
297289
Value(uint32_t value) : Value(static_cast<double>(value)) {}
298290

299291
Value(Runtime& runtime, const Value& value) {
300-
if (value.storage_->kind == v8engine::ValueStorage::Kind::V8Borrowed) {
292+
if (value.kind_ == v8engine::ValueStorage::Kind::V8Borrowed) {
293+
// Promote borrowed to owned
301294
storage_ =
302295
std::make_shared<v8engine::ValueStorage>(v8engine::ValueStorage::Kind::V8);
303-
storage_->value.Reset(runtime.isolate(), value.storage_->borrowedValue);
296+
storage_->value.Reset(runtime.isolate(), value.borrowedValue_);
297+
kind_ = v8engine::ValueStorage::Kind::V8;
304298
return;
305299
}
300+
kind_ = value.kind_;
301+
boolValue_ = value.boolValue_;
302+
numberValue_ = value.numberValue_;
303+
borrowedValue_ = value.borrowedValue_;
306304
storage_ = value.storage_;
307305
}
308-
Value(Runtime& runtime, Value&& value) : storage_(std::move(value.storage_)) {}
309-
Value(Runtime& runtime, const String& value) : storage_(value.storage_) {}
306+
Value(Runtime& runtime, Value&& value)
307+
: kind_(value.kind_),
308+
boolValue_(value.boolValue_),
309+
numberValue_(value.numberValue_),
310+
borrowedValue_(value.borrowedValue_),
311+
storage_(std::move(value.storage_)) {}
312+
Value(Runtime& runtime, const String& value);
310313
Value(Runtime& runtime, const Object& object);
311314
Value(Runtime& runtime, const Function& function);
312315
Value(Runtime& runtime, const Array& array);
@@ -317,7 +320,7 @@ class Value {
317320

318321
static Value null() {
319322
Value value;
320-
value.storage_ = std::make_shared<v8engine::ValueStorage>(v8engine::ValueStorage::Kind::Null);
323+
value.kind_ = v8engine::ValueStorage::Kind::Null;
321324
return value;
322325
}
323326

@@ -339,34 +342,48 @@ class Value {
339342

340343
v8::Local<v8::Value> local(Runtime& runtime) const {
341344
v8::Isolate* isolate = runtime.isolate();
342-
switch (storage_->kind) {
345+
switch (kind_) {
343346
case v8engine::ValueStorage::Kind::Undefined:
344347
return v8::Undefined(isolate);
345348
case v8engine::ValueStorage::Kind::Null:
346349
return v8::Null(isolate);
347350
case v8engine::ValueStorage::Kind::Bool:
348-
return v8::Boolean::New(isolate, storage_->boolValue);
351+
return v8::Boolean::New(isolate, boolValue_);
349352
case v8engine::ValueStorage::Kind::Number:
350-
return v8::Number::New(isolate, storage_->numberValue);
353+
return v8::Number::New(isolate, numberValue_);
351354
case v8engine::ValueStorage::Kind::V8:
352355
return storage_->value.Get(isolate);
353356
case v8engine::ValueStorage::Kind::V8Borrowed:
354-
return storage_->borrowedValue;
357+
return borrowedValue_;
355358
}
356359
}
357360

358361
Value(Runtime& runtime, v8::Local<v8::Value> value)
359-
: storage_(std::make_shared<v8engine::ValueStorage>(v8engine::ValueStorage::Kind::V8)) {
362+
: kind_(v8engine::ValueStorage::Kind::V8),
363+
storage_(std::make_shared<v8engine::ValueStorage>(v8engine::ValueStorage::Kind::V8)) {
360364
storage_->value.Reset(runtime.isolate(), value);
361365
}
362366

363367
static Value borrowed(Runtime&, v8::Local<v8::Value> value) {
364368
Value result;
365-
result.storage_->kind = v8engine::ValueStorage::Kind::V8Borrowed;
366-
result.storage_->borrowedValue = value;
369+
result.kind_ = v8engine::ValueStorage::Kind::V8Borrowed;
370+
result.borrowedValue_ = value;
367371
return result;
368372
}
369373

374+
// Access the shared storage (for Object/Function/Array interop)
375+
std::shared_ptr<v8engine::ValueStorage> storage() const { return storage_; }
376+
377+
static Value fromStorage(std::shared_ptr<v8engine::ValueStorage> s) {
378+
Value v;
379+
v.kind_ = s->kind;
380+
v.boolValue_ = s->boolValue;
381+
v.numberValue_ = s->numberValue;
382+
v.borrowedValue_ = s->borrowedValue;
383+
v.storage_ = std::move(s);
384+
return v;
385+
}
386+
370387
private:
371388
friend class Runtime;
372389
friend class Object;
@@ -376,6 +393,10 @@ class Value {
376393
friend class Function;
377394
friend class Array;
378395

396+
v8engine::ValueStorage::Kind kind_ = v8engine::ValueStorage::Kind::Undefined;
397+
bool boolValue_ = false;
398+
double numberValue_ = 0;
399+
v8::Local<v8::Value> borrowedValue_;
379400
std::shared_ptr<v8engine::ValueStorage> storage_;
380401
};
381402

@@ -506,9 +527,7 @@ class Object {
506527
}
507528

508529
operator Value() const {
509-
Value value;
510-
value.storage_ = storage_;
511-
return value;
530+
return Value::fromStorage(storage_);
512531
}
513532

514533
protected:
@@ -631,9 +650,7 @@ class Function : public Object {
631650
}
632651

633652
operator Value() const {
634-
Value value;
635-
value.storage_ = storage_;
636-
return value;
653+
return Value::fromStorage(storage_);
637654
}
638655
};
639656

@@ -672,9 +689,7 @@ class Array : public Object {
672689
}
673690

674691
operator Value() const {
675-
Value value;
676-
value.storage_ = storage_;
677-
return value;
692+
return Value::fromStorage(storage_);
678693
}
679694
};
680695

@@ -709,9 +724,7 @@ class BigInt {
709724
}
710725

711726
operator Value() const {
712-
Value value;
713-
value.storage_ = storage_;
714-
return value;
727+
return Value::fromStorage(storage_);
715728
}
716729

717730
private:
@@ -748,9 +761,7 @@ class ArrayBuffer : public Object {
748761
}
749762

750763
operator Value() const {
751-
Value value;
752-
value.storage_ = storage_;
753-
return value;
764+
return Value::fromStorage(storage_);
754765
}
755766
};
756767
} // namespace engine

0 commit comments

Comments
 (0)