Skip to content

Support embedder-provided CoW memory initialization images #14347

Description

@benjamincburns

Feature

Make it possible to initialize an instance's linear memory from an embedder-provided, file-backed CoW image, independently of the compiled module.

Benefit

I'm running largish (hundreds of MiB) pre-initialized modules using Wasmtime's copy-on-write memory initialization. My setup also uses lifecycle hooks, similar to AWS Lambda SnapStart. I call an init hook, let it do application-specific setup, then capture the resulting state.

This saves initialization work, but increases the size of the pre-initialized module, increasing the time that cold requests spend faulting those pages into memory.

I'd like to compile the module once, run the init hook while observing which code and linear-memory pages become resident, and capture the resulting linear memory separately. New instances would use that captured memory as their CoW initialization image, with the compiled code unchanged.

That would let me prefetch the observed working set without recompiling and changing the layout I'm measuring. It also lets me pack the selected pages together on disk while preserving their guest addresses, for added prefetch efficiency.

I can't find a public API for supplying that replacement memory image, which is why I'm requesting this feature.

Implementation

I have a Linux-specific proof-of-concept that installs an immutable file-backed image into an existing memory's CoW slot. It supports page-aligned extents mapping file offsets to linear-memory offsets, including images whose physical page order differs from their logical order.

Obviously my proof-of-concept approach is less efficient than one that supplies the image on instantiation, that way we'd avoid redundant memory initialization work. All the same, here's a snippet showing use of the proof-of-concept. CowMemoryImage and Memory::restore_cow_image are my additions.

// snapshot_file contains the captured linear memory.
// Its contents must remain unchanged while any image or instance uses it.
let image = unsafe {
    CowMemoryImage::from_file(snapshot_file, 0, captured_length_bytes)?
};

// `memory` belongs to a newly instantiated, idle `store`.
// Grow it to the captured size before installing the image.
let current_pages = memory.size(&store);
if captured_pages > current_pages {
    memory.grow(&mut store, captured_pages - current_pages)?;
}

if let Err(error) = unsafe {
    memory.restore_cow_image(&mut store, &image)
} {
    // The prototype does not promise an unchanged memory on failure.
    drop(store);
    return Err(error);
}

// Restore captured globals and other required instance state
// before calling into the guest.

I'm open to whatever alternative API you guys might prefer.

I'm only asking for the linear-memory mechanism here. I haven't tested it yet, but I think I can accomplish a similar packing/prefetching scheme for code pages without modification to Wasmtime via Module::deserialize_raw and CustomCodeMemory. If that's not the case, I'll likely open a new ticket for a strategy to support a similar prefetch/packing scheme for code pages.

Alternatives

I couldn't think of any strong alternatives that don't remove some benefit offered by the above.

Baking the captured memory into data segments and recompiling could work, provided I can map the linear-memory offsets into the final artifact. However, packing selected pages together would then require custom loading rather than using Wasmtime's normal file-loading path. Further, unless recompilation preserves the compiled code's layout, the code-page offsets collected during init would need a separate translation before I could use them for prefetching and packing.

Copying the captured bytes into each new instance loses the sharing and demand-paging benefits of file-backed CoW initialization.

I guess theoretically I could do something via MemoryCreator, but I'd have to bring my own CoW machinery, and it wouldn't directly support the pooling allocator we use.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions