Skip to content

Commit

Permalink
process: use v8 fast api calls for hrtime
Browse files Browse the repository at this point in the history
Refs: #33374

PR-URL: #33600
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
  • Loading branch information
devsnek committed Jun 6, 2020
1 parent e983b1c commit d8eef83
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 58 deletions.
9 changes: 4 additions & 5 deletions lib/internal/process/per_thread.js
Expand Up @@ -41,7 +41,6 @@ function assert(x, msg) {
function wrapProcessMethods(binding) {
const {
hrtime: _hrtime,
hrtimeBigInt: _hrtimeBigInt,
cpuUsage: _cpuUsage,
memoryUsage: _memoryUsage,
resourceUsage: _resourceUsage
Expand Down Expand Up @@ -113,10 +112,10 @@ function wrapProcessMethods(binding) {
// The 3 entries filled in by the original process.hrtime contains
// the upper/lower 32 bits of the second part of the value,
// and the remaining nanoseconds of the value.
const hrValues = new Uint32Array(3);
const hrValues = new Uint32Array(_hrtime.buffer);

function hrtime(time) {
_hrtime(hrValues);
_hrtime.hrtime();

if (time !== undefined) {
if (!ArrayIsArray(time)) {
Expand All @@ -140,9 +139,9 @@ function wrapProcessMethods(binding) {

// Use a BigUint64Array in the closure because this is actually a bit
// faster than simply returning a BigInt from C++ in V8 7.1.
const hrBigintValues = new BigUint64Array(1);
const hrBigintValues = new BigUint64Array(_hrtime.buffer, 0, 1);
function hrtimeBigInt() {
_hrtimeBigInt(hrBigintValues);
_hrtime.hrtimeBigInt();
return hrBigintValues[0];
}

Expand Down
2 changes: 2 additions & 0 deletions src/api/environment.cc
Expand Up @@ -209,6 +209,8 @@ void SetIsolateCreateParamsForNode(Isolate::CreateParams* params) {
// heap based on the actual physical memory.
params->constraints.ConfigureDefaults(total_memory, 0);
}
params->embedder_wrapper_object_index = BaseObject::InternalFields::kSlot;
params->embedder_wrapper_type_index = std::numeric_limits<int>::max();
}

void SetIsolateErrorHandlers(v8::Isolate* isolate, const IsolateSettings& s) {
Expand Down
131 changes: 98 additions & 33 deletions src/node_process_methods.cc
Expand Up @@ -7,6 +7,7 @@
#include "node_process.h"
#include "util-inl.h"
#include "uv.h"
#include "v8-fast-api-calls.h"
#include "v8.h"

#include <vector>
Expand All @@ -33,7 +34,7 @@ namespace node {

using v8::Array;
using v8::ArrayBuffer;
using v8::BigUint64Array;
using v8::BackingStore;
using v8::Context;
using v8::Float64Array;
using v8::FunctionCallbackInfo;
Expand All @@ -46,7 +47,6 @@ using v8::Number;
using v8::Object;
using v8::String;
using v8::Uint32;
using v8::Uint32Array;
using v8::Value;

namespace per_process {
Expand Down Expand Up @@ -131,35 +131,6 @@ static void Cwd(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(cwd);
}


// Hrtime exposes libuv's uv_hrtime() high-resolution timer.

// This is the legacy version of hrtime before BigInt was introduced in
// JavaScript.
// The value returned by uv_hrtime() is a 64-bit int representing nanoseconds,
// so this function instead fills in an Uint32Array with 3 entries,
// to avoid any integer overflow possibility.
// The first two entries contain the second part of the value
// broken into the upper/lower 32 bits to be converted back in JS,
// because there is no Uint64Array in JS.
// The third entry contains the remaining nanosecond part of the value.
static void Hrtime(const FunctionCallbackInfo<Value>& args) {
uint64_t t = uv_hrtime();

Local<ArrayBuffer> ab = args[0].As<Uint32Array>()->Buffer();
uint32_t* fields = static_cast<uint32_t*>(ab->GetBackingStore()->Data());

fields[0] = (t / NANOS_PER_SEC) >> 32;
fields[1] = (t / NANOS_PER_SEC) & 0xffffffff;
fields[2] = t % NANOS_PER_SEC;
}

static void HrtimeBigInt(const FunctionCallbackInfo<Value>& args) {
Local<ArrayBuffer> ab = args[0].As<BigUint64Array>()->Buffer();
uint64_t* fields = static_cast<uint64_t*>(ab->GetBackingStore()->Data());
fields[0] = uv_hrtime();
}

static void Kill(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Local<Context> context = env->context();
Expand Down Expand Up @@ -452,6 +423,85 @@ static void ReallyExit(const FunctionCallbackInfo<Value>& args) {
env->Exit(code);
}

class FastHrtime : public BaseObject {
public:
static Local<Object> New(Environment* env) {
Local<v8::ObjectTemplate> otmpl = v8::ObjectTemplate::New(env->isolate());
otmpl->SetInternalFieldCount(FastHrtime::kInternalFieldCount);

auto create_func = [env](auto fast_func, auto slow_func) {
auto cfunc = v8::CFunction::Make(fast_func);
return v8::FunctionTemplate::New(env->isolate(),
slow_func,
Local<Value>(),
Local<v8::Signature>(),
0,
v8::ConstructorBehavior::kThrow,
v8::SideEffectType::kHasNoSideEffect,
&cfunc);
};

otmpl->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "hrtime"),
create_func(FastNumber, SlowNumber));
otmpl->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "hrtimeBigInt"),
create_func(FastBigInt, SlowBigInt));

Local<Object> obj = otmpl->NewInstance(env->context()).ToLocalChecked();

Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), 12);
new FastHrtime(env, obj, ab->GetBackingStore());
obj->Set(
env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "buffer"), ab)
.ToChecked();

return obj;
}

private:
FastHrtime(Environment* env,
Local<Object> object,
std::shared_ptr<v8::BackingStore> backing_store)
: BaseObject(env, object), backing_store_(backing_store) {}

void MemoryInfo(MemoryTracker* tracker) const override {}

SET_MEMORY_INFO_NAME(FastHrtime)
SET_SELF_SIZE(FastHrtime)

// This is the legacy version of hrtime before BigInt was introduced in
// JavaScript.
// The value returned by uv_hrtime() is a 64-bit int representing nanoseconds,
// so this function instead fills in an Uint32Array with 3 entries,
// to avoid any integer overflow possibility.
// The first two entries contain the second part of the value
// broken into the upper/lower 32 bits to be converted back in JS,
// because there is no Uint64Array in JS.
// The third entry contains the remaining nanosecond part of the value.
static void FastNumber(FastHrtime* receiver) {
uint64_t t = uv_hrtime();
uint32_t* fields = static_cast<uint32_t*>(receiver->backing_store_->Data());
fields[0] = (t / NANOS_PER_SEC) >> 32;
fields[1] = (t / NANOS_PER_SEC) & 0xffffffff;
fields[2] = t % NANOS_PER_SEC;
}

static void SlowNumber(const FunctionCallbackInfo<Value>& args) {
FastNumber(FromJSObject<FastHrtime>(args.Holder()));
}

static void FastBigInt(FastHrtime* receiver) {
uint64_t t = uv_hrtime();
uint64_t* fields = static_cast<uint64_t*>(receiver->backing_store_->Data());
fields[0] = t;
}

static void SlowBigInt(const FunctionCallbackInfo<Value>& args) {
FastBigInt(FromJSObject<FastHrtime>(args.Holder()));
}

std::shared_ptr<BackingStore> backing_store_;
};

static void InitializeProcessMethods(Local<Object> target,
Local<Value> unused,
Local<Context> context,
Expand All @@ -475,8 +525,6 @@ static void InitializeProcessMethods(Local<Object> target,
env->SetMethod(target, "_rawDebug", RawDebug);
env->SetMethod(target, "memoryUsage", MemoryUsage);
env->SetMethod(target, "cpuUsage", CPUUsage);
env->SetMethod(target, "hrtime", Hrtime);
env->SetMethod(target, "hrtimeBigInt", HrtimeBigInt);
env->SetMethod(target, "resourceUsage", ResourceUsage);

env->SetMethod(target, "_getActiveRequests", GetActiveRequests);
Expand All @@ -488,9 +536,26 @@ static void InitializeProcessMethods(Local<Object> target,
env->SetMethod(target, "reallyExit", ReallyExit);
env->SetMethodNoSideEffect(target, "uptime", Uptime);
env->SetMethod(target, "patchProcessObject", PatchProcessObject);

target
->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "hrtime"),
FastHrtime::New(env))
.ToChecked();
}

} // namespace node

namespace v8 {
template <>
class WrapperTraits<node::FastHrtime> {
public:
static const void* GetTypeInfo() {
static const int tag = 0;
return reinterpret_cast<const void*>(&tag);
}
};
} // namespace v8

NODE_MODULE_CONTEXT_AWARE_INTERNAL(process_methods,
node::InitializeProcessMethods)
52 changes: 32 additions & 20 deletions test/cctest/test_base_object_ptr.cc
Expand Up @@ -14,6 +14,10 @@ using v8::Isolate;
using v8::Local;
using v8::Object;

// Environments may come with existing BaseObject instances.
// This variable offsets the expected BaseObject counts.
static const int BASE_OBJECT_COUNT = 1;

class BaseObjectPtrTest : public EnvironmentTestFixture {};

class DummyBaseObject : public BaseObject {
Expand Down Expand Up @@ -47,12 +51,12 @@ TEST_F(BaseObjectPtrTest, ScopedDetached) {
Env env_{handle_scope, argv};
Environment* env = *env_;

EXPECT_EQ(env->base_object_count(), 0);
EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT);
{
BaseObjectPtr<DummyBaseObject> ptr = DummyBaseObject::NewDetached(env);
EXPECT_EQ(env->base_object_count(), 1);
EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT + 1);
}
EXPECT_EQ(env->base_object_count(), 0);
EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT);
}

TEST_F(BaseObjectPtrTest, ScopedDetachedWithWeak) {
Expand All @@ -63,14 +67,14 @@ TEST_F(BaseObjectPtrTest, ScopedDetachedWithWeak) {

BaseObjectWeakPtr<DummyBaseObject> weak_ptr;

EXPECT_EQ(env->base_object_count(), 0);
EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT);
{
BaseObjectPtr<DummyBaseObject> ptr = DummyBaseObject::NewDetached(env);
weak_ptr = ptr;
EXPECT_EQ(env->base_object_count(), 1);
EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT + 1);
}
EXPECT_EQ(weak_ptr.get(), nullptr);
EXPECT_EQ(env->base_object_count(), 0);
EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT);
}

TEST_F(BaseObjectPtrTest, Undetached) {
Expand All @@ -79,12 +83,16 @@ TEST_F(BaseObjectPtrTest, Undetached) {
Env env_{handle_scope, argv};
Environment* env = *env_;

node::AddEnvironmentCleanupHook(isolate_, [](void* arg) {
EXPECT_EQ(static_cast<Environment*>(arg)->base_object_count(), 0);
}, env);
node::AddEnvironmentCleanupHook(
isolate_,
[](void* arg) {
EXPECT_EQ(static_cast<Environment*>(arg)->base_object_count(),
BASE_OBJECT_COUNT);
},
env);

BaseObjectPtr<DummyBaseObject> ptr = DummyBaseObject::New(env);
EXPECT_EQ(env->base_object_count(), 1);
EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT + 1);
}

TEST_F(BaseObjectPtrTest, GCWeak) {
Expand All @@ -101,21 +109,21 @@ TEST_F(BaseObjectPtrTest, GCWeak) {
weak_ptr = ptr;
ptr->MakeWeak();

EXPECT_EQ(env->base_object_count(), 1);
EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT + 1);
EXPECT_EQ(weak_ptr.get(), ptr.get());
EXPECT_EQ(weak_ptr->persistent().IsWeak(), false);

ptr.reset();
}

EXPECT_EQ(env->base_object_count(), 1);
EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT + 1);
EXPECT_NE(weak_ptr.get(), nullptr);
EXPECT_EQ(weak_ptr->persistent().IsWeak(), true);

v8::V8::SetFlagsFromString("--expose-gc");
isolate_->RequestGarbageCollectionForTesting(Isolate::kFullGarbageCollection);

EXPECT_EQ(env->base_object_count(), 0);
EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT);
EXPECT_EQ(weak_ptr.get(), nullptr);
}

Expand All @@ -126,7 +134,7 @@ TEST_F(BaseObjectPtrTest, Moveable) {
Environment* env = *env_;

BaseObjectPtr<DummyBaseObject> ptr = DummyBaseObject::NewDetached(env);
EXPECT_EQ(env->base_object_count(), 1);
EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT + 1);
BaseObjectWeakPtr<DummyBaseObject> weak_ptr { ptr };
EXPECT_EQ(weak_ptr.get(), ptr.get());

Expand All @@ -137,12 +145,12 @@ TEST_F(BaseObjectPtrTest, Moveable) {
BaseObjectWeakPtr<DummyBaseObject> weak_ptr2 = std::move(weak_ptr);
EXPECT_EQ(weak_ptr2.get(), ptr2.get());
EXPECT_EQ(weak_ptr.get(), nullptr);
EXPECT_EQ(env->base_object_count(), 1);
EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT + 1);

ptr2.reset();

EXPECT_EQ(weak_ptr2.get(), nullptr);
EXPECT_EQ(env->base_object_count(), 0);
EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT);
}

TEST_F(BaseObjectPtrTest, NestedClasses) {
Expand All @@ -163,14 +171,18 @@ TEST_F(BaseObjectPtrTest, NestedClasses) {
Env env_{handle_scope, argv};
Environment* env = *env_;

node::AddEnvironmentCleanupHook(isolate_, [](void* arg) {
EXPECT_EQ(static_cast<Environment*>(arg)->base_object_count(), 0);
}, env);
node::AddEnvironmentCleanupHook(
isolate_,
[](void* arg) {
EXPECT_EQ(static_cast<Environment*>(arg)->base_object_count(),
BASE_OBJECT_COUNT);
},
env);

ObjectWithPtr* obj =
new ObjectWithPtr(env, DummyBaseObject::MakeJSObject(env));
obj->ptr1 = DummyBaseObject::NewDetached(env);
obj->ptr2 = DummyBaseObject::New(env);

EXPECT_EQ(env->base_object_count(), 3);
EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT + 3);
}

0 comments on commit d8eef83

Please sign in to comment.