erksch
erksch4mo ago

Embedding RustyV8: Fatal JavaScript out of memory: Reached heap limit

I created this Criterion benchmark to estimate the cost of serialization, but it runs out of memory. Am I supposed to destroy json_v8_str and _json_obj explicitly?
let platform = v8::new_default_platform(0, false).make_shared();
v8::V8::initialize_platform(platform);
v8::V8::initialize();

let isolate = &mut v8::Isolate::new(Default::default());
let isolate_scope = &mut v8::HandleScope::new(isolate);
let context = v8::Context::new(isolate_scope, Default::default());
let scope = &mut v8::ContextScope::new(isolate_scope, context);
let global = context.global(scope);

// JSON
let json_str = r#"{"key": "value", "number": 42}"#;
let json_v8_str = v8::String::new(scope, json_str).unwrap();
let json_obj = v8::json::parse(scope, json_v8_str).unwrap();

let key = v8::String::new(scope, "key").unwrap();
let new_value = v8::String::new(scope, "newValue").unwrap();
let obj = json_obj.to_object(scope).unwrap();
obj.set(scope, key.into(), new_value.into());

let modified_json = v8::json::stringify(scope, obj.into()).unwrap();
let rust_str = modified_json.to_rust_string_lossy(scope);
assert_eq!(rust_str, r#"{"key":"newValue","number":42}"#);

c.bench_function("v8 json deserialize", |b| {
b.iter(|| {
let json_str = r#"{"key": "value", "number": 42}"#;
let json_v8_str = v8::String::new(scope, json_str).unwrap();
let _json_obj = v8::json::parse(scope, json_v8_str).unwrap();
})
});
let platform = v8::new_default_platform(0, false).make_shared();
v8::V8::initialize_platform(platform);
v8::V8::initialize();

let isolate = &mut v8::Isolate::new(Default::default());
let isolate_scope = &mut v8::HandleScope::new(isolate);
let context = v8::Context::new(isolate_scope, Default::default());
let scope = &mut v8::ContextScope::new(isolate_scope, context);
let global = context.global(scope);

// JSON
let json_str = r#"{"key": "value", "number": 42}"#;
let json_v8_str = v8::String::new(scope, json_str).unwrap();
let json_obj = v8::json::parse(scope, json_v8_str).unwrap();

let key = v8::String::new(scope, "key").unwrap();
let new_value = v8::String::new(scope, "newValue").unwrap();
let obj = json_obj.to_object(scope).unwrap();
obj.set(scope, key.into(), new_value.into());

let modified_json = v8::json::stringify(scope, obj.into()).unwrap();
let rust_str = modified_json.to_rust_string_lossy(scope);
assert_eq!(rust_str, r#"{"key":"newValue","number":42}"#);

c.bench_function("v8 json deserialize", |b| {
b.iter(|| {
let json_str = r#"{"key": "value", "number": 42}"#;
let json_v8_str = v8::String::new(scope, json_str).unwrap();
let _json_obj = v8::json::parse(scope, json_v8_str).unwrap();
})
});
<--- Last few GCs --->

[417610:0xc03ef79a0000] 14221 ms: Mark-Compact (reduce) 1399.0 (1399.9) -> 1399.0 (1400.9) MB, pooled: 0 MB, 562.58 / 0.00 ms (+ 0.0 ms in 0 steps since start of marking, biggest step 0.0 ms, walltime since start of marking 739 ms) (average mu = 0.809[417610:0xc03ef79a0000] 15192 ms: Mark-Compact 1400.0 (1400.9) -> 1400.0 (1404.9) MB, pooled: 0 MB, 969.39 / 0.00 ms (average mu = 0.600, current mu = 0.002) allocation failure; scavenge might not succeed


<--- JS stacktrace --->



#
# Fatal JavaScript out of memory: Reached heap limit
<--- Last few GCs --->

[417610:0xc03ef79a0000] 14221 ms: Mark-Compact (reduce) 1399.0 (1399.9) -> 1399.0 (1400.9) MB, pooled: 0 MB, 562.58 / 0.00 ms (+ 0.0 ms in 0 steps since start of marking, biggest step 0.0 ms, walltime since start of marking 739 ms) (average mu = 0.809[417610:0xc03ef79a0000] 15192 ms: Mark-Compact 1400.0 (1400.9) -> 1400.0 (1404.9) MB, pooled: 0 MB, 969.39 / 0.00 ms (average mu = 0.600, current mu = 0.002) allocation failure; scavenge might not succeed


<--- JS stacktrace --->



#
# Fatal JavaScript out of memory: Reached heap limit
3 Replies
bartlomieju
bartlomieju4mo ago
You should create and drop HandleScope for each iterator, otherwise V8 can't do garbage collection and runs our of memory
erksch
erkschOP3mo ago
Indeed that solved the issue.
c.bench_function("v8 json deserialize", |b| {
b.iter(|| {
let scope = &mut HandleScope::new(scope);
let json_str = r#"{"key": "value", "number": 42}"#;
let json_v8_str = v8::String::new(scope, json_str).unwrap();
let _json_obj = v8::json::parse(scope, json_v8_str).unwrap();
})
});
c.bench_function("v8 json deserialize", |b| {
b.iter(|| {
let scope = &mut HandleScope::new(scope);
let json_str = r#"{"key": "value", "number": 42}"#;
let json_v8_str = v8::String::new(scope, json_str).unwrap();
let _json_obj = v8::json::parse(scope, json_v8_str).unwrap();
})
});
@bartlomieju I was thinking about implementing a custom deserializer (e.g. from bincode), and did a trivial benchmark of String::new, but it seems rather slow? I assume the V8 deserializer has a more direct/lowlevel approach? Would it be feasible to do in Rust using the RustyV8 bindings?
c.bench_function("v8 1k string new", |b| {
let json_str = r#"{"key": "value", "number": 42}"#;
b.iter(|| {
let scope = &mut HandleScope::new(scope);
for _ in 0..1000 {
black_box(v8::String::new(scope, json_str).unwrap());
}
})
});
c.bench_function("v8 1k string new", |b| {
let json_str = r#"{"key": "value", "number": 42}"#;
b.iter(|| {
let scope = &mut HandleScope::new(scope);
for _ in 0..1000 {
black_box(v8::String::new(scope, json_str).unwrap());
}
})
});
bartlomieju
bartlomieju3mo ago
Not really, strings are slow because they require a memcopy