Skip to content
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

Optimization: Object Pooling for Enhanced Orthogonal Persistence #4465

Merged
merged 10 commits into from
Mar 20, 2024
9 changes: 5 additions & 4 deletions design/OrthogonalPersistence.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,12 @@ Unused stable variables of former versions can also be reclaimed by the GC.
The static heap is abandoned and former static objects need to be allocated in the dynamic heap.
This is because these objects may also need to survive upgrades and must not be not overwritten by new data segments. The incremental GC also operates on these objects, meaning that forwarding pointer resolution is also necessary for these objects.

The runtime system avoids any global Wasm variables for state that needs to be preserved on upgrades. Instead, such global runtime state is stored in the persistent metadata.
The incremental GC also operates on these objects, meaning that forwarding pointer resolution is also necessary for these objects.

Sharing optimization (pooling) is possible for compile-time-known objects, see below.
For memory and runtime efficiency, object pooling is implemented for compile-time-known constant objects (with side-effect-free initialization), i.e. those objects are already created on program initialization/upgrade in the dynamic heap and thereafter the reference to the corresponding prefabricated object is looked up whenever the constant value is needed at runtime.

The runtime systems avoids any global Wasm variables for state that needs to be preserved on upgrades.
Instead, such global runtime state is stored in the persistent metadata.

### Wasm Data Segments
Only passive Wasm data segments are used by the compiler and runtime system. In contrast to ordinary active data segments, passive segments can be explicitly loaded to a dynamic address.
Expand All @@ -112,10 +115,8 @@ When migrating from the old serialization-based stabilization to the new stable
The old stable memory remains equally accessible as secondary memory with the new support.

## Current Limitations
* Optimization potential for statically known objects: Currently, compile-time-known objects are always dynamically allocated. For an improved performance and size, they could be shared in the dynamic heap by remembering them in an additional pool table. The pool table needs to be registered as a transient GC root and recreated on canister upgrades.
* The incremental GC only allows 64 GB. Transitioning to a dynamic partition table would be necessary to go beyond this limit. This is to be be implemented in a separate PR.
* The floating point display format differs in Wasm64 for special values, e.g. `nan` becomes `NaN`. There is currently no support for hexadecimal floating point text formatting.
* Workaround for Rust needed to build PIC (position-independent code) libraries. Explicit use of `emscripten` via LLVM IR.
* `ic-wasm` would need to be extended to support memory64 and passive data segments. The Wasm optimizations in `test/bench` are thus currently deactivated.
* The Wasm profiler is no longer applicable because the underlying `parity-wasm` crate seems deprecated. A re-implementation of the profiler would be needed.

28 changes: 16 additions & 12 deletions rts/motoko-rts/src/gc/incremental/roots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ use motoko_rts_macros::ic_mem_fn;

use crate::{types::Value, visitor::is_non_null_pointer_field};

/// Root referring to all canister variables.
/// This root is reinitialized on each canister upgrade.
/// An array referring to the static program variables, being
/// - All canister variables.
/// - Pooled shared objects.
/// The array constitutes a GC root that is reinitialized on each canister upgrade.
/// The scalar sentinel denotes an uninitialized root.
#[cfg(feature = "ic")]
static mut STATIC_ROOT: Value = Value::from_scalar(0);
static mut STATIC_VARIABLES: Value = Value::from_scalar(0);

/// GC root set.
pub type Roots = [*mut Value; 6];
Expand All @@ -19,7 +21,7 @@ pub unsafe fn root_set() -> Roots {
region::region0_get_ptr_loc,
};
[
static_root_location(),
static_variables_location(),
continuation_table_loc(),
stable_actor_location(),
stable_type_descriptor().candid_data_location(),
Expand All @@ -41,21 +43,23 @@ pub unsafe fn visit_roots<C, V: Fn(&mut C, *mut Value)>(
}

#[cfg(feature = "ic")]
unsafe fn static_root_location() -> *mut Value {
&mut STATIC_ROOT as *mut Value
unsafe fn static_variables_location() -> *mut Value {
&mut STATIC_VARIABLES as *mut Value
}

#[ic_mem_fn(ic_only)]
pub unsafe fn set_static_root<M: crate::memory::Memory>(mem: &mut M, value: Value) {
pub unsafe fn set_static_variables<M: crate::memory::Memory>(mem: &mut M, array: Value) {
use super::barriers::write_with_barrier;
use crate::types::TAG_ARRAY;

let location = &mut STATIC_ROOT as *mut Value;
write_with_barrier(mem, location, value);
assert_eq!(array.tag(), TAG_ARRAY);
let location = &mut STATIC_VARIABLES as *mut Value;
write_with_barrier(mem, location, array);
}

#[no_mangle]
#[cfg(feature = "ic")]
pub unsafe extern "C" fn get_static_root() -> Value {
debug_assert!(STATIC_ROOT.is_non_null_ptr());
STATIC_ROOT
pub unsafe extern "C" fn get_static_variable(index: usize) -> Value {
debug_assert!(STATIC_VARIABLES.is_non_null_ptr());
STATIC_VARIABLES.as_array().get(index)
}
Loading