Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(web): optimize encodeInto() #15922

Merged
merged 10 commits into from
Sep 17, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions cli/bench/encode_into.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
const queueMicrotask = globalThis.queueMicrotask || process.nextTick;
let [total, count] = typeof Deno !== "undefined"
? Deno.args
: [process.argv[2], process.argv[3]];

total = total ? parseInt(total, 0) : 50;
count = count ? parseInt(count, 10) : 1000000;

function bench(fun) {
const start = Date.now();
for (let i = 0; i < count; i++) fun();
const elapsed = Date.now() - start;
const rate = Math.floor(count / (elapsed / 1000));
console.log(`time ${elapsed} ms rate ${rate}`);
if (--total) queueMicrotask(() => bench(fun));
}

const encoder = new TextEncoder();
const data = "hello world";
const out = new Uint8Array(100);
console.log(encoder.encodeInto(data, out));
bench(() => {
encoder.encodeInto(data, out);
});
8 changes: 7 additions & 1 deletion ext/web/08_text_encoding.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,16 @@
context: "Argument 2",
allowShared: true,
});
return ops.op_encoding_encode_into(source, destination);
ops.op_encoding_encode_into(source, destination, encodeIntoBuf);
return {
read: encodeIntoBuf[0],
written: encodeIntoBuf[1],
};
}
}

const encodeIntoBuf = new Uint32Array(2);

webidl.configurePrototype(TextEncoder);
const TextEncoderPrototype = TextEncoder.prototype;

Expand Down
64 changes: 26 additions & 38 deletions ext/web/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ use deno_core::Resource;
use deno_core::ResourceId;
use deno_core::U16String;
use deno_core::ZeroCopyBuf;
use deno_core::serde_v8;
use deno_core::v8;

use encoding_rs::CoderResult;
use encoding_rs::Decoder;
use encoding_rs::DecoderResult;
use encoding_rs::Encoding;
use serde::Serialize;
use std::borrow::Cow;
use std::cell::RefCell;
use std::fmt;
Expand Down Expand Up @@ -314,46 +316,32 @@ impl Resource for TextDecoderResource {
}
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct EncodeIntoResult {
read: usize,
written: usize,
}
littledivy marked this conversation as resolved.
Show resolved Hide resolved

#[op]
#[op(v8)]
fn op_encoding_encode_into(
input: String,
scope: &mut v8::HandleScope,
input: serde_v8::Value,
buffer: &mut [u8],
) -> EncodeIntoResult {
// Since `input` is already UTF-8, we can simply find the last UTF-8 code
// point boundary from input that fits in `buffer`, and copy the bytes up to
// that point.
let boundary = if buffer.len() >= input.len() {
input.len()
} else {
let mut boundary = buffer.len();

// The maximum length of a UTF-8 code point is 4 bytes.
for _ in 0..4 {
if input.is_char_boundary(boundary) {
break;
}
debug_assert!(boundary > 0);
boundary -= 1;
}

debug_assert!(input.is_char_boundary(boundary));
boundary
out_buf: &mut [u8],
) {
let s = v8::Local::<v8::String>::try_from(input.v8_value).unwrap();
littledivy marked this conversation as resolved.
Show resolved Hide resolved
assert!(out_buf.len() % 4 == 0);
// SAFETY: `out_buf` is guaranteed to be aligned to 4 bytes.
littledivy marked this conversation as resolved.
Show resolved Hide resolved
let out_buf: &mut [u32] = unsafe {
std::slice::from_raw_parts_mut(
out_buf.as_mut_ptr() as *mut u32,
out_buf.len() / std::mem::size_of::<u32>(),
)
littledivy marked this conversation as resolved.
Show resolved Hide resolved
};

buffer[..boundary].copy_from_slice(input[..boundary].as_bytes());

EncodeIntoResult {
// The `read` output parameter is measured in UTF-16 code units.
read: input[..boundary].encode_utf16().count(),
written: boundary,
}
let mut nchars = 0;
let written = s.write_utf8(
scope,
buffer,
Some(&mut nchars),
v8::WriteOptions::NO_NULL_TERMINATION
| v8::WriteOptions::REPLACE_INVALID_UTF8,
);
out_buf[0] = nchars as u32;
littledivy marked this conversation as resolved.
Show resolved Hide resolved
out_buf[1] = written as u32;
}

/// Creates a [`CancelHandle`] resource that can be used to cancel invocations of certain ops.
Expand Down