@@ -153,6 +153,15 @@ fn sandbox_lifecycle_benchmark(c: &mut Criterion) {
153153 ) ;
154154 }
155155
156+ // Isolates the cost of building a MultiUseSandbox from an
157+ // already-resident Snapshot. The Snapshot is loaded outside the
158+ // timed region.
159+ for size in SandboxSize :: all ( ) {
160+ group. bench_function ( format ! ( "sandbox_from_snapshot/{}" , size. name( ) ) , |b| {
161+ bench_sandbox_from_snapshot ( b, size)
162+ } ) ;
163+ }
164+
156165 group. finish ( ) ;
157166}
158167
@@ -347,6 +356,28 @@ fn bench_snapshot_restore(b: &mut criterion::Bencher, size: SandboxSize) {
347356 } ) ;
348357}
349358
359+ fn bench_sandbox_from_snapshot ( b : & mut criterion:: Bencher , size : SandboxSize ) {
360+ use hyperlight_host:: HostFunctions ;
361+ use hyperlight_host:: sandbox:: snapshot:: { OciTag , Snapshot } ;
362+
363+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
364+ let snap_path = dir. path ( ) . join ( "bench" ) ;
365+ let tag = OciTag :: new ( "latest" ) . unwrap ( ) ;
366+ {
367+ let mut sbox = create_multiuse_sandbox_with_size ( size) ;
368+ let snapshot = sbox. snapshot ( ) . unwrap ( ) ;
369+ snapshot. to_oci ( & snap_path, & tag) . unwrap ( ) ;
370+ }
371+ let loaded = std:: sync:: Arc :: new ( Snapshot :: from_oci ( & snap_path, tag) . unwrap ( ) ) ;
372+
373+ // Drop is not included.
374+ b. iter_batched (
375+ || ( ) ,
376+ |_| MultiUseSandbox :: from_snapshot ( loaded. clone ( ) , HostFunctions :: default ( ) , None ) . unwrap ( ) ,
377+ criterion:: BatchSize :: PerIteration ,
378+ ) ;
379+ }
380+
350381fn snapshots_benchmark ( c : & mut Criterion ) {
351382 let mut group = c. benchmark_group ( "snapshots" ) ;
352383
@@ -551,6 +582,148 @@ fn shared_memory_benchmark(c: &mut Criterion) {
551582 group. finish ( ) ;
552583}
553584
585+ // ============================================================================
586+ // Benchmark Category: Snapshot Files
587+ // ============================================================================
588+
589+ fn snapshot_file_benchmark ( c : & mut Criterion ) {
590+ use hyperlight_host:: HostFunctions ;
591+ use hyperlight_host:: sandbox:: snapshot:: { OciTag , Snapshot } ;
592+
593+ let mut group = c. benchmark_group ( "snapshot_files" ) ;
594+
595+ // Pre-create OCI snapshot images for all sizes.
596+ let dirs: Vec < _ > = SandboxSize :: all ( )
597+ . iter ( )
598+ . map ( |size| {
599+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
600+ let snap_path = dir. path ( ) . join ( size. name ( ) ) ;
601+ let snapshot = {
602+ let mut sbox = create_multiuse_sandbox_with_size ( * size) ;
603+ sbox. snapshot ( ) . unwrap ( )
604+ } ;
605+ snapshot
606+ . to_oci ( & snap_path, & OciTag :: new ( "latest" ) . unwrap ( ) )
607+ . unwrap ( ) ;
608+ ( dir, snapshot, snap_path)
609+ } )
610+ . collect ( ) ;
611+
612+ // Benchmark: save_snapshot. Wipe the layout between iterations
613+ // so each save measures a fresh write rather than a tag-append.
614+ for ( i, size) in SandboxSize :: all ( ) . iter ( ) . enumerate ( ) {
615+ let snap_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
616+ let path = snap_dir. path ( ) . join ( "bench" ) ;
617+ let snapshot = & dirs[ i] . 1 ;
618+ group. bench_function ( format ! ( "save_snapshot/{}" , size. name( ) ) , |b| {
619+ b. iter_batched (
620+ || {
621+ let _ = std:: fs:: remove_dir_all ( & path) ;
622+ } ,
623+ |_| {
624+ snapshot
625+ . to_oci ( & path, & OciTag :: new ( "latest" ) . unwrap ( ) )
626+ . unwrap ( )
627+ } ,
628+ criterion:: BatchSize :: PerIteration ,
629+ ) ;
630+ } ) ;
631+ }
632+
633+ // Benchmark: load_snapshot (parse manifest + config + mmap blob).
634+ for ( i, size) in SandboxSize :: all ( ) . iter ( ) . enumerate ( ) {
635+ let snap_path = dirs[ i] . 2 . clone ( ) ;
636+ group. bench_function ( format ! ( "load_snapshot/{}" , size. name( ) ) , |b| {
637+ b. iter ( || {
638+ let _ = Snapshot :: from_oci ( & snap_path, OciTag :: new ( "latest" ) . unwrap ( ) ) . unwrap ( ) ;
639+ } ) ;
640+ } ) ;
641+ }
642+
643+ // Benchmark: load_snapshot_unchecked (skip blob digest verification).
644+ for ( i, size) in SandboxSize :: all ( ) . iter ( ) . enumerate ( ) {
645+ let snap_path = dirs[ i] . 2 . clone ( ) ;
646+ group. bench_function ( format ! ( "load_snapshot_unchecked/{}" , size. name( ) ) , |b| {
647+ b. iter ( || {
648+ // SAFETY: snapshot path produced by `to_oci` in bench setup.
649+ let _ = unsafe {
650+ Snapshot :: from_oci_unchecked ( & snap_path, OciTag :: new ( "latest" ) . unwrap ( ) )
651+ }
652+ . unwrap ( ) ;
653+ } ) ;
654+ } ) ;
655+ }
656+
657+ // Benchmark: cold_start_via_evolve (new + evolve + call). Drop is not included.
658+ for size in SandboxSize :: all ( ) {
659+ group. bench_function ( format ! ( "cold_start_via_evolve/{}" , size. name( ) ) , |b| {
660+ b. iter_batched (
661+ || ( ) ,
662+ |_| {
663+ let mut sbox = create_multiuse_sandbox_with_size ( size) ;
664+ sbox. call :: < String > ( "Echo" , "hello\n " . to_string ( ) ) . unwrap ( ) ;
665+ sbox
666+ } ,
667+ criterion:: BatchSize :: PerIteration ,
668+ ) ;
669+ } ) ;
670+ }
671+
672+ // Benchmark: cold_start_via_snapshot (load + from_snapshot + call). Drop is not included.
673+ for ( i, size) in SandboxSize :: all ( ) . iter ( ) . enumerate ( ) {
674+ let snap_path = dirs[ i] . 2 . clone ( ) ;
675+ group. bench_function ( format ! ( "cold_start_via_snapshot/{}" , size. name( ) ) , |b| {
676+ b. iter_batched (
677+ || ( ) ,
678+ |_| {
679+ let loaded =
680+ Snapshot :: from_oci ( & snap_path, OciTag :: new ( "latest" ) . unwrap ( ) ) . unwrap ( ) ;
681+ let mut sbox = MultiUseSandbox :: from_snapshot (
682+ std:: sync:: Arc :: new ( loaded) ,
683+ HostFunctions :: default ( ) ,
684+ None ,
685+ )
686+ . unwrap ( ) ;
687+ sbox. call :: < String > ( "Echo" , "hello\n " . to_string ( ) ) . unwrap ( ) ;
688+ sbox
689+ } ,
690+ criterion:: BatchSize :: PerIteration ,
691+ ) ;
692+ } ) ;
693+ }
694+
695+ // Benchmark: cold_start_via_snapshot_unchecked (load unchecked + from_snapshot + call). Drop is not included.
696+ for ( i, size) in SandboxSize :: all ( ) . iter ( ) . enumerate ( ) {
697+ let snap_path = dirs[ i] . 2 . clone ( ) ;
698+ group. bench_function (
699+ format ! ( "cold_start_via_snapshot_unchecked/{}" , size. name( ) ) ,
700+ |b| {
701+ b. iter_batched (
702+ || ( ) ,
703+ |_| {
704+ // SAFETY: snapshot path produced by `to_oci` in bench setup.
705+ let loaded = unsafe {
706+ Snapshot :: from_oci_unchecked ( & snap_path, OciTag :: new ( "latest" ) . unwrap ( ) )
707+ }
708+ . unwrap ( ) ;
709+ let mut sbox = MultiUseSandbox :: from_snapshot (
710+ std:: sync:: Arc :: new ( loaded) ,
711+ HostFunctions :: default ( ) ,
712+ None ,
713+ )
714+ . unwrap ( ) ;
715+ sbox. call :: < String > ( "Echo" , "hello\n " . to_string ( ) ) . unwrap ( ) ;
716+ sbox
717+ } ,
718+ criterion:: BatchSize :: PerIteration ,
719+ ) ;
720+ } ,
721+ ) ;
722+ }
723+
724+ group. finish ( ) ;
725+ }
726+
554727criterion_group ! {
555728 name = benches;
556729 config = Criterion :: default ( ) ;
@@ -561,6 +734,7 @@ criterion_group! {
561734 guest_call_benchmark_large_param,
562735 function_call_serialization_benchmark,
563736 sample_workloads_benchmark,
564- shared_memory_benchmark
737+ shared_memory_benchmark,
738+ snapshot_file_benchmark
565739}
566740criterion_main ! ( benches) ;
0 commit comments