Skip to content

Commit

Permalink
perf(core) Reduce script name and script code copies (denoland#18298)
Browse files Browse the repository at this point in the history
Reduce the number of copies and allocations of script code by carrying
around ownership/reference information from creation time.

As an advantage, this allows us to maintain the identity of `&'static
str`-based scripts and use v8's external 1-byte strings (to avoid
incorrectly passing non-ASCII strings, debug `assert!`s gate all string
reference paths).

Benchmark results:

Perf improvements -- ~0.1 - 0.2ms faster, but should reduce garbage
w/external strings and reduces data copies overall. May also unlock some
more interesting optimizations in the future.

This requires adding some generics to functions, but manual
monomorphization has been applied (outer/inner function) to avoid code
bloat.
  • Loading branch information
mmastrac committed Mar 21, 2023
1 parent 253b556 commit 0b4770f
Show file tree
Hide file tree
Showing 19 changed files with 457 additions and 176 deletions.
5 changes: 3 additions & 2 deletions bench_util/js_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ pub fn bench_js_async_with(
opts.benching_inner
};
let looped = loop_code(inner_iters, src);
let src = looped.as_ref();
// Get a &'static str by leaking -- this is fine because it's benchmarking code
let src = Box::leak(looped.into_boxed_str());
if is_profiling() {
for _ in 0..opts.profiling_outer {
tokio_runtime.block_on(inner_async(src, &mut runtime));
Expand All @@ -115,7 +116,7 @@ pub fn bench_js_async_with(
}
}

async fn inner_async(src: &str, runtime: &mut JsRuntime) {
async fn inner_async(src: &'static str, runtime: &mut JsRuntime) {
runtime.execute_script("inner_loop", src).unwrap();
runtime.run_event_loop(false).await.unwrap();
}
7 changes: 4 additions & 3 deletions cli/emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::cache::FastInsecureHasher;
use crate::cache::ParsedSourceCache;

use deno_core::error::AnyError;
use deno_core::ModuleCode;
use deno_core::ModuleSpecifier;
use deno_graph::MediaType;
use std::sync::Arc;
Expand All @@ -27,11 +28,11 @@ pub fn emit_parsed_source(
source: &Arc<str>,
emit_options: &deno_ast::EmitOptions,
emit_config_hash: u64,
) -> Result<String, AnyError> {
) -> Result<ModuleCode, AnyError> {
let source_hash = get_source_hash(source, emit_config_hash);

if let Some(emit_code) = emit_cache.get_emit_code(specifier, source_hash) {
Ok(emit_code)
Ok(emit_code.into())
} else {
// this will use a cached version if it exists
let parsed_source = parsed_source_cache.get_or_parse_module(
Expand All @@ -42,6 +43,6 @@ pub fn emit_parsed_source(
let transpiled_source = parsed_source.transpile(emit_options)?;
debug_assert!(transpiled_source.source_map.is_none());
emit_cache.set_emit_code(specifier, source_hash, &transpiled_source.text);
Ok(transpiled_source.text)
Ok(transpiled_source.text.into())
}
}
4 changes: 2 additions & 2 deletions cli/lsp/tsc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2854,7 +2854,7 @@ fn start(runtime: &mut JsRuntime, debug: bool) -> Result<(), AnyError> {
let init_config = json!({ "debug": debug });
let init_src = format!("globalThis.serverInit({init_config});");

runtime.execute_script(&located_script_name!(), &init_src)?;
runtime.execute_script(located_script_name!(), init_src)?;
Ok(())
}

Expand Down Expand Up @@ -3442,7 +3442,7 @@ pub fn request(
};
let mark = performance.mark("request", Some(request_params.clone()));
let request_src = format!("globalThis.serverRequest({request_params});");
runtime.execute_script(&located_script_name!(), &request_src)?;
runtime.execute_script(located_script_name!(), request_src)?;

let op_state = runtime.op_state();
let mut op_state = op_state.borrow_mut();
Expand Down
17 changes: 10 additions & 7 deletions cli/module_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use deno_core::error::AnyError;
use deno_core::futures::future::FutureExt;
use deno_core::futures::Future;
use deno_core::resolve_url;
use deno_core::ModuleCode;
use deno_core::ModuleLoader;
use deno_core::ModuleSource;
use deno_core::ModuleSpecifier;
Expand All @@ -30,7 +31,7 @@ use std::rc::Rc;
use std::str;

struct ModuleCodeSource {
pub code: String,
pub code: ModuleCode,
pub found_url: ModuleSpecifier,
pub media_type: MediaType,
}
Expand Down Expand Up @@ -91,7 +92,7 @@ impl CliModuleLoader {
specifier,
..
})) => Ok(ModuleCodeSource {
code: source.to_string(),
code: source.into(),
found_url: specifier.clone(),
media_type: *media_type,
}),
Expand All @@ -101,13 +102,15 @@ impl CliModuleLoader {
specifier,
..
})) => {
let code = match media_type {
let code: ModuleCode = match media_type {
MediaType::JavaScript
| MediaType::Unknown
| MediaType::Cjs
| MediaType::Mjs
| MediaType::Json => source.to_string(),
MediaType::Dts | MediaType::Dcts | MediaType::Dmts => "".to_string(),
| MediaType::Json => source.into(),
MediaType::Dts | MediaType::Dcts | MediaType::Dmts => {
Default::default()
}
MediaType::TypeScript
| MediaType::Mts
| MediaType::Cts
Expand Down Expand Up @@ -191,7 +194,7 @@ impl CliModuleLoader {
)?
};
ModuleCodeSource {
code,
code: code.into(),
found_url: specifier.clone(),
media_type: MediaType::from_specifier(specifier),
}
Expand All @@ -208,7 +211,7 @@ impl CliModuleLoader {
code_without_source_map(code_source.code)
};
Ok(ModuleSource {
code: code.into_bytes().into_boxed_slice(),
code,
module_url_specified: specifier.to_string(),
module_url_found: code_source.found_url.to_string(),
module_type: match code_source.media_type {
Expand Down
10 changes: 5 additions & 5 deletions cli/standalone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ impl ModuleLoader for EmbeddedModuleLoader {
async move {
if let Some((source, _)) = is_data_uri {
return Ok(deno_core::ModuleSource {
code: source.into_bytes().into_boxed_slice(),
code: source.into(),
module_type: deno_core::ModuleType::JavaScript,
module_url_specified: module_specifier.to_string(),
module_url_found: module_specifier.to_string(),
Expand All @@ -192,7 +192,7 @@ impl ModuleLoader for EmbeddedModuleLoader {
.to_owned();

Ok(deno_core::ModuleSource {
code: code.into_bytes().into_boxed_slice(),
code: code.into(),
module_type: match module.kind {
eszip::ModuleKind::JavaScript => deno_core::ModuleType::JavaScript,
eszip::ModuleKind::Json => deno_core::ModuleType::Json,
Expand Down Expand Up @@ -384,16 +384,16 @@ pub async fn run(
options,
);
worker.execute_main_module(main_module).await?;
worker.dispatch_load_event(&located_script_name!())?;
worker.dispatch_load_event(located_script_name!())?;

loop {
worker.run_event_loop(false).await?;
if !worker.dispatch_beforeunload_event(&located_script_name!())? {
if !worker.dispatch_beforeunload_event(located_script_name!())? {
break;
}
}

worker.dispatch_unload_event(&located_script_name!())?;
worker.dispatch_unload_event(located_script_name!())?;
std::process::exit(0);
}

Expand Down
24 changes: 13 additions & 11 deletions cli/tools/coverage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use deno_core::serde_json;
use deno_core::sourcemap::SourceMap;
use deno_core::url::Url;
use deno_core::LocalInspectorSession;
use deno_core::ModuleCode;
use regex::Regex;
use std::fs;
use std::fs::File;
Expand Down Expand Up @@ -170,16 +171,16 @@ struct CoverageReport {

fn generate_coverage_report(
script_coverage: &ScriptCoverage,
script_source: &str,
script_source: String,
maybe_source_map: &Option<Vec<u8>>,
output: &Option<PathBuf>,
) -> CoverageReport {
let maybe_source_map = maybe_source_map
.as_ref()
.map(|source_map| SourceMap::from_slice(source_map).unwrap());
let text_lines = TextLines::new(script_source);
let text_lines = TextLines::new(&script_source);

let comment_ranges = deno_ast::lex(script_source, MediaType::JavaScript)
let comment_ranges = deno_ast::lex(&script_source, MediaType::JavaScript)
.into_iter()
.filter(|item| {
matches!(item.inner, deno_ast::TokenOrComment::Comment { .. })
Expand Down Expand Up @@ -680,22 +681,22 @@ pub async fn cover_files(
})?;

// Check if file was transpiled
let original_source = &file.source;
let transpiled_code = match file.media_type {
let original_source = file.source.clone();
let transpiled_code: ModuleCode = match file.media_type {
MediaType::JavaScript
| MediaType::Unknown
| MediaType::Cjs
| MediaType::Mjs
| MediaType::Json => file.source.as_ref().to_string(),
MediaType::Dts | MediaType::Dmts | MediaType::Dcts => "".to_string(),
| MediaType::Json => file.source.into(),
MediaType::Dts | MediaType::Dmts | MediaType::Dcts => Default::default(),
MediaType::TypeScript
| MediaType::Jsx
| MediaType::Mts
| MediaType::Cts
| MediaType::Tsx => {
let source_hash = get_source_hash(&file.source, ps.emit_options_hash);
match ps.emit_cache.get_emit_code(&file.specifier, source_hash) {
Some(code) => code,
Some(code) => code.into(),
None => {
return Err(anyhow!(
"Missing transpiled source code for: \"{}\".
Expand All @@ -710,15 +711,16 @@ pub async fn cover_files(
}
};

let source_map = source_map_from_code(&transpiled_code);
let coverage_report = generate_coverage_report(
&script_coverage,
&transpiled_code,
&source_map_from_code(&transpiled_code),
transpiled_code.take_as_string(),
&source_map,
&out_mode,
);

if !coverage_report.found_lines.is_empty() {
reporter.report(&coverage_report, original_source)?;
reporter.report(&coverage_report, &original_source)?;
}
}

Expand Down
4 changes: 2 additions & 2 deletions cli/tsc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -824,9 +824,9 @@ pub fn exec(request: Request) -> Result<Response, AnyError> {
});

runtime
.execute_script(&located_script_name!(), startup_source)
.execute_script(located_script_name!(), startup_source)
.context("Could not properly start the compiler runtime.")?;
runtime.execute_script(&located_script_name!(), &exec_source)?;
runtime.execute_script(located_script_name!(), exec_source)?;

let op_state = runtime.op_state();
let mut op_state = op_state.borrow_mut();
Expand Down
36 changes: 21 additions & 15 deletions cli/util/text_encoding.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.

use deno_core::ModuleCode;
use encoding_rs::*;
use std::borrow::Cow;
use std::io::Error;
Expand Down Expand Up @@ -53,11 +54,12 @@ pub fn strip_bom(text: &str) -> &str {
}
}

static SOURCE_MAP_PREFIX: &str =
"//# sourceMappingURL=data:application/json;base64,";
static SOURCE_MAP_PREFIX: &[u8] =
b"//# sourceMappingURL=data:application/json;base64,";

pub fn source_map_from_code(code: &str) -> Option<Vec<u8>> {
let last_line = code.rsplit(|u| u == '\n').next()?;
pub fn source_map_from_code(code: &ModuleCode) -> Option<Vec<u8>> {
let bytes = code.as_bytes();
let last_line = bytes.rsplit(|u| *u == b'\n').next()?;
if last_line.starts_with(SOURCE_MAP_PREFIX) {
let input = last_line.split_at(SOURCE_MAP_PREFIX.len()).1;
let decoded_map = base64::decode(input)
Expand All @@ -68,17 +70,18 @@ pub fn source_map_from_code(code: &str) -> Option<Vec<u8>> {
}
}

pub fn code_without_source_map(mut code: String) -> String {
if let Some(last_line_index) = code.rfind('\n') {
if code[last_line_index + 1..].starts_with(SOURCE_MAP_PREFIX) {
code.truncate(last_line_index + 1);
code
} else {
code
/// Truncate the source code before the source map.
pub fn code_without_source_map(mut code: ModuleCode) -> ModuleCode {
let bytes = code.as_bytes();
for i in (0..bytes.len()).rev() {
if bytes[i] == b'\n' {
if bytes[i + 1..].starts_with(SOURCE_MAP_PREFIX) {
code.truncate(i + 1);
}
return code;
}
} else {
code
}
code
}

#[cfg(test)]
Expand Down Expand Up @@ -155,8 +158,11 @@ mod tests {
"\n",
);

fn run_test(input: &str, output: &str) {
assert_eq!(code_without_source_map(input.to_string()), output);
fn run_test(input: &'static str, output: &'static str) {
assert_eq!(
code_without_source_map(input.into()).take_as_string(),
output
);
}
}
}
Loading

0 comments on commit 0b4770f

Please sign in to comment.