Skip to content

Component benchmarks #93

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

Merged
merged 1 commit into from
Jul 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 2 additions & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ RUN rustup default ${RUST_TOOLCHAIN} \
&& cargo install --locked wasm-tools \
&& cargo install wkg \
&& cargo install wac-cli \
&& cargo install cargo-component --locked
&& cargo install cargo-component --locked \
&& cargo install wit-bindgen-cli --locked
13 changes: 4 additions & 9 deletions .github/workflows/dep_build_wasm_examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,19 +78,14 @@ jobs:
cache-to: ${{ env.CACHE_TO }}
- name: Build Modules
run: |
for FILENAME in $(find . -name '*.c')
do
echo Building ${FILENAME}
docker run --rm -i -v "${PWD}:/tmp/host" ghcr.io/${{ github.repository_owner }}/wasm-clang-builder:latest /opt/wasi-sdk/bin/clang -flto -ffunction-sections -mexec-model=reactor -O3 -z stack-size=4096 -Wl,--initial-memory=65536 -Wl,--export=__data_end -Wl,--export=__heap_base,--export=malloc,--export=free,--export=__wasm_call_ctors -Wl,--strip-all,--no-entry -Wl,--allow-undefined -Wl,--gc-sections -o /tmp/host/${FILENAME%.*}-wasi-libc.wasm /tmp/host/${FILENAME}
cargo run -p hyperlight-wasm-aot compile ${FILENAME%.*}-wasi-libc.wasm ${FILENAME%.*}.aot
cp ${FILENAME%.*}.aot ${FILENAME%.*}.wasm
done
just ensure-tools
just build-wasm-examples release
shell: bash
working-directory: src/wasmsamples
- name: Upload Wasm Modules
uses: actions/upload-artifact@v4
with:
name: guest-modules
path: |
src/wasmsamples/*.wasm
src/wasmsamples/*.aot
x64/release/*.wasm
x64/release/*.aot
1 change: 1 addition & 0 deletions .github/workflows/dep_rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ jobs:
# this must be build before the formatting and other jobs run
# because the component model example depends on the wasm component built here
just ensure-tools
just compile-wit
just build-rust-component-examples ${{ matrix.config }}

- name: Fmt
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -479,3 +479,4 @@ target/
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
src/component_sample/**/*.wasm
src/wasmsamples/components/bindings/
25 changes: 17 additions & 8 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ build-wasm-examples-command := if os() == "windows" { "./src/hyperlight_wasm/scr
mkdir-arg := if os() == "windows" { "-Force" } else { "-p" }
latest-release:= if os() == "windows" {"$(git tag -l --sort=v:refname | select -last 2 | select -first 1)"} else {`git tag -l --sort=v:refname | tail -n 2 | head -n 1`}
wit-world := if os() == "windows" { "$env:WIT_WORLD=\"" + justfile_directory() + "\\src\\component_sample\\wit\\component-world.wasm" + "\";" } else { "WIT_WORLD=" + justfile_directory() + "/src/component_sample/wit/component-world.wasm" }
wit-world-c := if os() == "windows" { "$env:WIT_WORLD=\"" + justfile_directory() + "\\src\\wasmsamples\\components\\runcomponent-world.wasm" + "\";" } else { "WIT_WORLD=" + justfile_directory() + "/src/wasmsamples/components/runcomponent-world.wasm" }

set windows-shell := ["pwsh.exe", "-NoLogo", "-Command"]

Expand All @@ -14,8 +15,9 @@ make-vendor-tar:
-C ./src wasm_runtime hyperlight_wasm_macro

ensure-tools:
cargo install --locked wasm-tools --version 1.235.0
cargo install wasm-tools --locked --version 1.235.0
cargo install cargo-component --locked --version 0.21.1
cargo install wit-bindgen-cli --locked --version 0.43.0

build-all target=default-target: (build target) (build-wasm-examples target) (build-rust-wasm-examples target) (build-wasm-runtime target) (build-rust-component-examples target)

Expand All @@ -26,10 +28,14 @@ mkdir-redist target=default-target:
mkdir {{ mkdir-arg }} x64
mkdir {{ mkdir-arg }} x64/{{ target }}

compile-wit:
wasm-tools component wit ./src/wasmsamples/components/runcomponent.wit -w -o ./src/wasmsamples/components/runcomponent-world.wasm
wasm-tools component wit ./src/component_sample/wit/example.wit -w -o ./src/component_sample/wit/component-world.wasm

build-wasm-runtime target=default-target:
cd ./src/wasm_runtime && cargo build --verbose --profile={{ if target == "debug" {"dev"} else { target } }} && rm -R target

build-wasm-examples target=default-target:
build-wasm-examples target=default-target: (compile-wit)
{{ build-wasm-examples-command }} {{target}}

build-rust-wasm-examples target=default-target: (mkdir-redist target)
Expand All @@ -38,8 +44,7 @@ build-rust-wasm-examples target=default-target: (mkdir-redist target)
cargo run -p hyperlight-wasm-aot compile ./src/rust_wasm_samples/target/wasm32-unknown-unknown/{{ target }}/rust_wasm_samples.wasm ./x64/{{ target }}/rust_wasm_samples.aot
cp ./x64/{{ target }}/rust_wasm_samples.aot ./x64/{{ target }}/rust_wasm_samples.wasm

build-rust-component-examples target=default-target:
wasm-tools component wit ./src/component_sample/wit/example.wit -w -o ./src/component_sample/wit/component-world.wasm
build-rust-component-examples target=default-target: (compile-wit)
# use cargo component so we don't get all the wasi imports https://github.com/bytecodealliance/cargo-component?tab=readme-ov-file#relationship-with-wasm32-wasip2
# we also explicitly target wasm32-unknown-unknown since cargo component might try to pull in wasi imports https://github.com/bytecodealliance/cargo-component/issues/290
rustup target add wasm32-unknown-unknown
Expand Down Expand Up @@ -101,10 +106,14 @@ examples-components target=default-target features="": (build-rust-component-exa
{{ wit-world }} cargo run {{ if features =="" {''} else {"--no-default-features -F " + features } }} --profile={{ if target == "debug" {"dev"} else { target } }} --example component_example

# warning, compares to and then OVERWRITES the given baseline
bench-ci baseline target=default-target features="":
cd src/hyperlight_wasm && cargo bench --profile={{ if target == "debug" {"dev"} else { target } }} {{ if features =="" {''} else { "--features " + features } }} -- --verbose --save-baseline {{baseline}}
bench target=default-target features="":
cd src/hyperlight_wasm && cargo bench --profile={{ if target == "debug" {"dev"} else { target } }} {{ if features =="" {''} else { "--features " + features } }} -- --verbose
bench-ci baseline target="release" features="":
cd src/hyperlight_wasm && cargo bench --profile={{ if target == "debug" {"dev"} else { target } }} {{ if features =="" {''} else { "--features " + features } }} --bench benchmarks -- --verbose --save-baseline {{baseline}}
cd src/hyperlight_wasm; {{wit-world-c}} cargo bench --profile={{ if target == "debug" {"dev"} else { target } }} {{ if features =="" {''} else { "--features " + features } }} --bench benchmarks_components -- --verbose --save-baseline {{baseline}}-components
bench target="release" features="": (bench-wasm target features) (bench-components target features)
bench-wasm target="release" features="":
cd src/hyperlight_wasm && cargo bench --profile={{ if target == "debug" {"dev"} else { target } }} {{ if features =="" {''} else { "--features " + features } }} --bench benchmarks -- --verbose
bench-components target="release" features="":
cd src/hyperlight_wasm; {{wit-world-c}} cargo bench --profile={{ if target == "debug" {"dev"} else { target } }} {{ if features =="" {''} else { "--features " + features } }} --bench benchmarks_components -- --verbose
bench-download os hypervisor cpu tag="":
gh release download {{ tag }} -D ./src/hyperlight_wasm/target/ -p benchmarks_{{ os }}_{{ hypervisor }}_{{ cpu }}.tar.gz
mkdir {{ mkdir-arg }} ./src/hyperlight_wasm/target/criterion
Expand Down
5 changes: 5 additions & 0 deletions src/hyperlight_wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,8 @@ mshv3 = ["hyperlight-host/mshv3"]
[[bench]]
name = "benchmarks"
harness = false

[[bench]]
name = "benchmarks_components"
harness = false

110 changes: 110 additions & 0 deletions src/hyperlight_wasm/benches/benchmarks_components.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use std::sync::{Arc, Mutex};

use criterion::{Bencher, Criterion, criterion_group, criterion_main};
use hyperlight_wasm::{LoadedWasmSandbox, SandboxBuilder};

use crate::bindings::example::runcomponent::Guest;

extern crate alloc;
mod bindings {
hyperlight_component_macro::host_bindgen!(
"../../src/wasmsamples/components/runcomponent-world.wasm"
);
}

pub struct State {}
impl State {
pub fn new() -> Self {
State {}
}
}

impl Default for State {
fn default() -> Self {
Self::new()
}
}

impl bindings::example::runcomponent::Host for State {
fn r#get_time_since_boot_microsecond(&mut self) -> i64 {
let res = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap()
.as_micros();
i64::try_from(res).unwrap()
}
}

impl bindings::example::runcomponent::RuncomponentImports for State {
type Host = State;

fn r#host(&mut self) -> impl ::core::borrow::BorrowMut<Self::Host> {
self
}
}

fn wasm_component_guest_call_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("wasm_component_guest_functions");

let bench_guest_function = |b: &mut Bencher<'_>, ext| {
let (sb, rt) = get_loaded_wasm_sandbox(ext);
let mut wrapped = bindings::RuncomponentSandbox { sb, rt };
let instance = bindings::example::runcomponent::RuncomponentExports::guest(&mut wrapped);

b.iter(|| {
instance.echo("Hello World!".to_string());
});
};

group.bench_function("wasm_guest_call", |b: &mut Bencher<'_>| {
bench_guest_function(b, "wasm");
});

group.bench_function("wasm_guest_call_aot", |b: &mut Bencher<'_>| {
bench_guest_function(b, "aot");
});

group.finish();
}

fn wasm_component_sandbox_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("wasm_component_sandboxes");
let create_wasm_sandbox = || {
get_loaded_wasm_sandbox("wasm");
};

group.bench_function("create_sandbox", |b| {
b.iter_with_large_drop(create_wasm_sandbox);
});

group.bench_function("create_sandbox_and_drop", |b| {
b.iter(create_wasm_sandbox);
});

group.finish();
}

fn get_loaded_wasm_sandbox(
ext: &str,
) -> (
LoadedWasmSandbox,
Arc<Mutex<bindings::RuncomponentResources<State>>>,
) {
let state = State::new();
let mut sandbox = SandboxBuilder::new().build().unwrap();
let rt = bindings::register_host_functions(&mut sandbox, state);

let sb = sandbox.load_runtime().unwrap();

let sb = sb
.load_module(format!("../../x64/release/runcomponent.{ext}",))
.unwrap();
(sb, rt)
}

criterion_group! {
name = benches_components;
config = Criterion::default();//.warm_up_time(Duration::from_millis(50)); // If warm_up_time is default 3s warmup, the benchmark will fail due memory error
targets = wasm_component_guest_call_benchmark, wasm_component_sandbox_benchmark
}
criterion_main!(benches_components);
53 changes: 50 additions & 3 deletions src/hyperlight_wasm/scripts/build-wasm-examples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ OUTPUT_DIR=$(realpath $OUTPUT_DIR)

if [ -f "/.dockerenv" ] || grep -q docker /proc/1/cgroup; then
# running in a container so use the installed wasi-sdk as the devcontainer has this installed
for FILENAME in $(find . -name '*.c')
for FILENAME in $(find . -name '*.c' -not -path './components/*')
do
echo Building ${FILENAME}
# Build the wasm file with wasi-libc for wasmtime
Expand All @@ -23,6 +23,29 @@ if [ -f "/.dockerenv" ] || grep -q docker /proc/1/cgroup; then
cargo run -p hyperlight-wasm-aot compile ${OUTPUT_DIR}/${FILENAME%.*}-wasi-libc.wasm ${OUTPUT_DIR}/${FILENAME%.*}.aot
cp ${OUTPUT_DIR}/${FILENAME%.*}.aot ${OUTPUT_DIR}/${FILENAME%.*}.wasm
done

for WIT_FILE in ${PWD}/components/*.wit; do
COMPONENT_NAME=$(basename ${WIT_FILE} .wit)
echo Building component: ${COMPONENT_NAME}

# Generate bindings for the component
wit-bindgen c ${WIT_FILE} --out-dir ${PWD}/components/bindings

# Build the wasm file with wasi-libc for wasmtime
/opt/wasi-sdk/bin/wasm32-wasip2-clang \
-ffunction-sections -mexec-model=reactor -O3 -z stack-size=4096 \
-Wl,--initial-memory=65536 -Wl,--export=__data_end -Wl,--export=__heap_base,--export=malloc,--export=free,--export=__wasm_call_ctors \
-Wl,--strip-all,--no-entry -Wl,--allow-undefined -Wl,--gc-sections \
-o ${OUTPUT_DIR}/${COMPONENT_NAME}-p2.wasm \
${PWD}/components/${COMPONENT_NAME}.c \
${PWD}/components/bindings/${COMPONENT_NAME}.c \
${PWD}/components/bindings/${COMPONENT_NAME}_component_type.o

# Build AOT for Wasmtime
cargo run -p hyperlight-wasm-aot compile --component ${OUTPUT_DIR}/${COMPONENT_NAME}-p2.wasm ${OUTPUT_DIR}/${COMPONENT_NAME}.aot
cp ${OUTPUT_DIR}/${COMPONENT_NAME}.aot ${OUTPUT_DIR}/${COMPONENT_NAME}.wasm
done

else
# not running in a container so use the docker image to build the wasm files
echo Building docker image that has Wasm sdk. Should be quick if preivoulsy built and no changes to dockerfile.
Expand All @@ -33,17 +56,41 @@ else

docker build --build-arg GCC_VERSION=12 --build-arg WASI_SDK_VERSION_FULL=25.0 --cache-from ghcr.io/hyperlight-dev/wasm-clang-builder:latest -t wasm-clang-builder:latest . 2> ${OUTPUT_DIR}/dockerbuild.log

for FILENAME in $(find . -name '*.c')
for FILENAME in $(find . -name '*.c' -not -path './components/*')
do
echo Building ${FILENAME}
# Build the wasm file with wasi-libc for wasmtime
docker run --rm -i -v "${PWD}:/tmp/host" -v "${OUTPUT_DIR}:/tmp/output" wasm-clang-builder:latest /opt/wasi-sdk/bin/clang -flto -ffunction-sections -mexec-model=reactor -O3 -z stack-size=4096 -Wl,--initial-memory=65536 -Wl,--export=__data_end -Wl,--export=__heap_base,--export=malloc,--export=free,--export=__wasm_call_ctors -Wl,--strip-all,--no-entry -Wl,--allow-undefined -Wl,--gc-sections -o /tmp/output/${FILENAME%.*}-wasi-libc.wasm /tmp/host/${FILENAME}
docker run --rm -i -v "${PWD}:/tmp/host" -v "${OUTPUT_DIR}:/tmp/output/" wasm-clang-builder:latest /opt/wasi-sdk/bin/clang -flto -ffunction-sections -mexec-model=reactor -O3 -z stack-size=4096 -Wl,--initial-memory=65536 -Wl,--export=__data_end -Wl,--export=__heap_base,--export=malloc,--export=free,--export=__wasm_call_ctors -Wl,--strip-all,--no-entry -Wl,--allow-undefined -Wl,--gc-sections -o /tmp/output/${FILENAME%.*}-wasi-libc.wasm /tmp/host/${FILENAME}

# Build AOT for Wasmtime; note that Wasmtime does not support
# interpreting, so its wasm binary is secretly an AOT binary.
cargo run -p hyperlight-wasm-aot compile ${OUTPUT_DIR}/${FILENAME%.*}-wasi-libc.wasm ${OUTPUT_DIR}/${FILENAME%.*}.aot
cp ${OUTPUT_DIR}/${FILENAME%.*}.aot ${OUTPUT_DIR}/${FILENAME%.*}.wasm
done

echo Building components
# Iterate over all .wit files in the components folder
for WIT_FILE in ${PWD}/components/*.wit; do
COMPONENT_NAME=$(basename ${WIT_FILE} .wit)
echo Building component: ${COMPONENT_NAME}

# Generate bindings for the component
wit-bindgen c ${WIT_FILE} --out-dir ${PWD}/components/bindings

# Build the wasm file with wasi-libc for wasmtime
docker run --rm -i -v "${PWD}:/tmp/host" -v "${OUTPUT_DIR}:/tmp/output/" wasm-clang-builder:latest /opt/wasi-sdk/bin/wasm32-wasip2-clang \
-ffunction-sections -mexec-model=reactor -O3 -z stack-size=4096 \
-Wl,--initial-memory=65536 -Wl,--export=__data_end -Wl,--export=__heap_base,--export=malloc,--export=free,--export=__wasm_call_ctors \
-Wl,--strip-all,--no-entry -Wl,--allow-undefined -Wl,--gc-sections \
-o /tmp/output/${COMPONENT_NAME}-p2.wasm \
/tmp/host/components/${COMPONENT_NAME}.c \
/tmp/host/components/bindings/${COMPONENT_NAME}.c \
/tmp/host/components/bindings/${COMPONENT_NAME}_component_type.o

# Build AOT for Wasmtime
cargo run -p hyperlight-wasm-aot compile --component ${OUTPUT_DIR}/${COMPONENT_NAME}-p2.wasm ${OUTPUT_DIR}/${COMPONENT_NAME}.aot
cp ${OUTPUT_DIR}/${COMPONENT_NAME}.aot ${OUTPUT_DIR}/${COMPONENT_NAME}.wasm
done
fi

popd
16 changes: 16 additions & 0 deletions src/wasmsamples/compile-wasm.bat
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,22 @@ for /R "%1" %%i in (*.c) do (
copy %2\%%~ni.aot %2\%%~ni.wasm
)

echo Building components
for %%j in (%~1\components\*.wit) do (
set "COMPONENT_NAME=%%~nj"
echo Building component: !COMPONENT_NAME!

rem Generate bindings for the component
wit-bindgen c %%j --out-dir %~1\components\bindings

rem Build the wasm file with wasi-libc for wasmtime
%dockercmd% run --rm -i -v !dockerinput!:/tmp/host1 -v !dockeroutput!:/tmp/host2 wasm-clang-builder /opt/wasi-sdk/bin/wasm32-wasip2-clang -ffunction-sections -mexec-model=reactor -O3 -z stack-size=4096 -Wl,--initial-memory=65536 -Wl,--export=__data_end -Wl,--export=__heap_base,--export=malloc,--export=free,--export=__wasm_call_ctors -Wl,--strip-all,--no-entry -Wl,--allow-undefined -Wl,--gc-sections -o /tmp/host2/!COMPONENT_NAME!-p2.wasm /tmp/host1/components/!COMPONENT_NAME!.c /tmp/host1/components/bindings/!COMPONENT_NAME!.c /tmp/host1/components/bindings/!COMPONENT_NAME!_component_type.o

rem Build AOT for Wasmtime
cargo run -p hyperlight-wasm-aot compile --component %2\!COMPONENT_NAME!-p2.wasm %2\!COMPONENT_NAME!.aot
copy %2\!COMPONENT_NAME!.aot %2\!COMPONENT_NAME!.wasm
)

goto :EOF
:Error
echo Usage - compile-wasm ^<source directory^> ^<output directory^>
11 changes: 11 additions & 0 deletions src/wasmsamples/components/runcomponent.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include "bindings/runcomponent.h"
#include <stdlib.h>
#include <string.h>

void exports_example_runcomponent_guest_echo(runcomponent_string_t *msg, runcomponent_string_t *ret)
{
ret->len = msg->len;
ret->ptr = (uint8_t *) malloc(ret->len);
memcpy(ret->ptr, msg->ptr, ret->len);
runcomponent_string_free(msg);
}
14 changes: 14 additions & 0 deletions src/wasmsamples/components/runcomponent.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package example:runcomponent;

interface guest {
echo: func(msg: string) -> string;
}

interface host {
get-time-since-boot-microsecond: func() -> s64;
}

world runcomponent {
export guest;
import host;
}