-
Notifications
You must be signed in to change notification settings - Fork 105
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
Investigate whether we need to charge for data section #3907
Comments
Bench results:Data section (denoted as d) and code section (c): Single data sectionProportions of
Full output
Overlapped data sections (50% overlap)In this test data section overlapped by 50%, which seems lead to redundant memory copy during initialization.
Full output
|
Conclusion I
|
There was a strange non-linearity discovered in the benchmark test results.
It seems like an arbitrary constant time is spent on something and is not dependent on the data section size. After some investigation, the reason behind this was found. In https://github.com/wasmerio/wasmer/blob/2.3.0/lib/vm/src/instance/mod.rs#L1399 fn initialize_memories(
instance: &Instance,
data_initializers: &[DataInitializer<'_>],
) -> Result<(), Trap> {
// ........................
unsafe {
let mem_slice = get_memory_slice(init, instance);
let end = start + init.data.len();
let to_init = &mut mem_slice[start..end];
to_init.copy_from_slice(init.data); <== HERE
} This action causes a freshly allocated memory page to write some data in it, resulting in a This can easily be verified by writing something to this page beforehand or using Theory confirming test #[test]
fn foo() {
use wabt::Wat2Wasm;
let wat = r###"
(module
(import "env" "memory" (memory 1))
(data (i32.const 0) "\02\00\00\00\04")
)"###;
let wasm = Wat2Wasm::new()
.validate(true)
.write_debug_names(true)
.convert(wat)
.expect("failed to parse module");
let compiler = sandbox_wasmer::Singlepass::default();
let store = sandbox_wasmer::Store::new(&sandbox_wasmer::Universal::new(compiler).engine());
let module = Module::new(&store, wasm).unwrap();
struct MyResolver {
mem: Memory,
}
impl Resolver for MyResolver {
fn resolve(&self, _: u32, _field: &str, _: &str) -> Option<Export> {
Some(Extern::Memory(self.mem.clone()).to_export())
}
}
let import_object = MyResolver {
mem: Memory::new(&store, MemoryType::new(0x1000, None, false)).unwrap(),
};
unsafe {
let mem_ptr = *import_object.mem.get_vm_memory().from.vmmemory().as_ptr();
println!("Memory ptr: {:X?}", mem_ptr.base);
mem_ptr.base.write(0x01); // <== TRIGGERING PAGE FAULT BEFOREHAND
};
let t1 = std::time::Instant::now();
let instance = sandbox_wasmer::Instance::new(&module, &import_object).unwrap();
std::hint::black_box(instance);
println!("Time: {:?}", t1.elapsed());
} Test results: Original case:
Triggering page fault before
|
Bench test (multiple data segments, different OS pages)It initializes multiple segments in data section (segment size = 64 bytes) and every segment resides in a different OS page (OS page size = 4Kb). Bench result
|
Please disregard the previous benchmark results as they were conducted with huge pages enabledSo, to determine the weight of data section instantiation, we can separate it into three parameters:
Code, data size, number of data segments benchLet's conduct a benchmark test for the first two, where:
Full
As we can see, the weight of data section instantiation, in comparison to the code section, is at least ~5-10 times higher. The number of data segments contributes significantly less to the total result. OS pagesLet's examine how the number of OS pages contributes to the total weight of the data section. The benchmark was conducted in such a way that for every OS 4Kb page (denoted as 'p'), a single data segment of 1 byte size was created.
Full
Though, this OS 4Kb page weight
Conclusion:
|
Additional bench results: |
Another bench run from different machine, Result:
Full result
|
In continuation of the statement that it is not necessary to consider the number of segments, it was decided to limit the maximum number of segments with some limit. To assess this hypothetical limit, the number of data segments in programs in This bash script was used: wasm_count_max_data_seg_in_dir.sh#!/bin/bash
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <directory_path>"
exit 1
fi
dir_path="$1"
if [ ! -d "$dir_path" ]; then
echo "Error: Directory does not exist: $dir_path"
exit 1
fi
max_seg_num=0
# Loop through all files with the .wasm extension in the directory
for file in "$dir_path"/*.wasm; do
if [ -f "$file" ]; then
data_segment_num=$(wasm-objdump -h "${file}" | awk '/Data/ {print $NF}')
#echo "Output of $file: $data_segment_num"
if [ "$data_segment_num" -gt "$max_seg_num" ]; then
max_seg_num=$data_segment_num
fi
fi
done
echo '*************************************************************'
echo "MAX NUMBER of data segments: $max_seg_num"
echo '*************************************************************'
|
Presently we have a benchmark, which calculates weight for module instantiation depending on code size:
gear/pallets/gear/src/benchmarking/mod.rs
Lines 375 to 383 in e1fb708
Here we charge for instantiation (not the best naming yes):
gear/pallets/gear/src/queue.rs
Lines 159 to 163 in e1fb708
The problem is that code size is summary size of all WASM code sections. But in benchmark we increase only code section, so actually it's time for wasm functions code instantiation. On other hand we can increase data section and check what will happen. Maybe data section instantiation is heavier than code instantiation, so here we have potential problem with
gear::run
time execution. Maybe data section instantiation weight is significantly smaller than code section instantiation weight, so here we have potential weight gain in case we will charge for code section size and data section size separately.What can you do:
instantiate_module_per_kb
, so that two params will be taken into account, see two params example ingr_create_program_per_kb
.The text was updated successfully, but these errors were encountered: