url: improve canParse() performance for non-onebyte strings

PR-URL: https://github.com/nodejs/node/pull/58023
Reviewed-By: Daniel Lemire <daniel@lemire.me>
Reviewed-By: Vinícius Lourenço Claro Cardoso <contact@viniciusl.com.br>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Yagiz Nizipli 2025-04-27 10:52:29 -04:00 committed by GitHub
parent 647175ee0b
commit e0cf8ae62a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 62 additions and 18 deletions

View File

@ -10,6 +10,13 @@
namespace node {
using CFunctionCallbackWithalueAndOptions = bool (*)(
v8::Local<v8::Value>, v8::Local<v8::Value>, v8::FastApiCallbackOptions&);
using CFunctionCallbackWithMultipleValueAndOptions =
bool (*)(v8::Local<v8::Value>,
v8::Local<v8::Value>,
v8::Local<v8::Value>,
v8::FastApiCallbackOptions&);
using CFunctionCallbackWithOneByteString =
uint32_t (*)(v8::Local<v8::Value>, const v8::FastOneByteString&);
@ -92,6 +99,8 @@ class ExternalReferenceRegistry {
#define ALLOWED_EXTERNAL_REFERENCE_TYPES(V) \
V(CFunctionCallback) \
V(CFunctionCallbackWithalueAndOptions) \
V(CFunctionCallbackWithMultipleValueAndOptions) \
V(CFunctionCallbackWithOneByteString) \
V(CFunctionCallbackReturnBool) \
V(CFunctionCallbackReturnDouble) \

View File

@ -10,6 +10,7 @@
#include "path.h"
#include "util-inl.h"
#include "v8-fast-api-calls.h"
#include "v8-local-handle.h"
#include "v8.h"
#include <cstdint>
@ -21,7 +22,7 @@ namespace url {
using v8::CFunction;
using v8::Context;
using v8::FastOneByteString;
using v8::FastApiCallbackOptions;
using v8::FunctionCallbackInfo;
using v8::HandleScope;
using v8::Isolate;
@ -282,18 +283,45 @@ void BindingData::CanParse(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(can_parse);
}
bool BindingData::FastCanParse(Local<Value> receiver,
const FastOneByteString& input) {
bool BindingData::FastCanParse(
Local<Value> receiver,
Local<Value> input,
// NOLINTNEXTLINE(runtime/references) This is V8 api.
FastApiCallbackOptions& options) {
TRACK_V8_FAST_API_CALL("url.canParse");
return ada::can_parse(std::string_view(input.data, input.length));
auto isolate = options.isolate;
HandleScope handleScope(isolate);
Local<String> str;
if (!input->ToString(isolate->GetCurrentContext()).ToLocal(&str)) {
return false;
}
Utf8Value utf8(isolate, str);
return ada::can_parse(utf8.ToStringView());
}
bool BindingData::FastCanParseWithBase(Local<Value> receiver,
const FastOneByteString& input,
const FastOneByteString& base) {
bool BindingData::FastCanParseWithBase(
Local<Value> receiver,
Local<Value> input,
Local<Value> base,
// NOLINTNEXTLINE(runtime/references) This is V8 api.
FastApiCallbackOptions& options) {
TRACK_V8_FAST_API_CALL("url.canParse.withBase");
auto base_view = std::string_view(base.data, base.length);
return ada::can_parse(std::string_view(input.data, input.length), &base_view);
auto isolate = options.isolate;
HandleScope handleScope(isolate);
auto context = isolate->GetCurrentContext();
Local<String> input_str;
if (!input->ToString(context).ToLocal(&input_str)) {
return false;
}
Local<String> base_str;
if (!base->ToString(context).ToLocal(&base_str)) {
return false;
}
Utf8Value input_utf8(isolate, input_str);
Utf8Value base_utf8(isolate, base_str);
auto base_view = base_utf8.ToStringView();
return ada::can_parse(input_utf8.ToStringView(), &base_view);
}
CFunction BindingData::fast_can_parse_methods_[] = {

View File

@ -51,10 +51,15 @@ class BindingData : public SnapshotableObject {
static void CanParse(const v8::FunctionCallbackInfo<v8::Value>& args);
static bool FastCanParse(v8::Local<v8::Value> receiver,
const v8::FastOneByteString& input);
static bool FastCanParseWithBase(v8::Local<v8::Value> receiver,
const v8::FastOneByteString& input,
const v8::FastOneByteString& base);
v8::Local<v8::Value> input,
// NOLINTNEXTLINE(runtime/references) This is V8 api.
v8::FastApiCallbackOptions& options);
static bool FastCanParseWithBase(
v8::Local<v8::Value> receiver,
v8::Local<v8::Value> input,
v8::Local<v8::Value> base,
// NOLINTNEXTLINE(runtime/references) This is V8 api.
v8::FastApiCallbackOptions& options);
static void Format(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetOrigin(const v8::FunctionCallbackInfo<v8::Value>& args);

View File

@ -20,13 +20,17 @@ assert.throws(() => {
assert.strictEqual(URL.canParse('https://example.org'), true);
{
// V8 Fast API
// Only javascript methods can be optimized through %OptimizeFunctionOnNextCall
// This is why we surround the C++ method we want to optimize with a JS function.
function testFastPaths() {
// `canParse` binding has two overloads.
assert.strictEqual(URL.canParse('https://www.example.com/path/?query=param#hash'), true);
assert.strictEqual(URL.canParse('/', 'http://n'), true);
}
// Since our JS function contains other javascript functions,
// we need to specify which function we want to optimize. This is why
// the next line does not optimize "testFastPaths" but "URL.canParse"
eval('%PrepareFunctionForOptimization(URL.canParse)');
testFastPaths();
eval('%OptimizeFunctionOnNextCall(URL.canParse)');
@ -34,9 +38,7 @@ assert.strictEqual(URL.canParse('https://example.org'), true);
if (common.isDebug) {
const { getV8FastApiCallCount } = internalBinding('debug');
// TODO: the counts should be 1. The function is optimized, but the fast
// API is not called.
assert.strictEqual(getV8FastApiCallCount('url.canParse'), 0);
assert.strictEqual(getV8FastApiCallCount('url.canParse.withBase'), 0);
assert.strictEqual(getV8FastApiCallCount('url.canParse'), 1);
assert.strictEqual(getV8FastApiCallCount('url.canParse.withBase'), 1);
}
}