pub struct GraphExecutor<B: TensorBackend + 'static> { /* private fields */ }Expand description
Executes compiled graph programs on a concrete tensor backend.
A graph executor owns backend execution state only: backend runtime caches,
extension runtime state, and reusable execution workspace. Compilation
state lives in GraphCompiler.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{GraphCompiler, GraphExecutor, TracedTensor};
let x = TracedTensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
let y = (&x + &x).unwrap();
let mut compiler = GraphCompiler::new();
let program = compiler.compile(&y).unwrap();
let mut executor = GraphExecutor::new(CpuBackend::new());
let out = executor.run(&program).unwrap();
assert_eq!(out.as_slice::<f64>().unwrap(), &[2.0, 4.0]);Implementations§
Source§impl<B: TensorBackend + 'static> GraphExecutor<B>
impl<B: TensorBackend + 'static> GraphExecutor<B>
Sourcepub fn new(backend: B) -> Self
pub fn new(backend: B) -> Self
Create an executor with the given backend and bounded default caches.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{GraphExecutor};
let executor = GraphExecutor::new(CpuBackend::new());
assert_eq!(executor.cache_stats().extensions.entries, 0);Sourcepub fn backend(&self) -> &B
pub fn backend(&self) -> &B
Borrow the backend used by this executor.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{GraphExecutor};
let executor = GraphExecutor::new(CpuBackend::new());
let _backend = executor.backend();Sourcepub fn reclaim_outputs(&mut self, outputs: Vec<Tensor>)
pub fn reclaim_outputs(&mut self, outputs: Vec<Tensor>)
Return output tensors to the executor backend’s reusable buffer pool.
This is useful for tight benchmark or serving loops that consume an output before the next run and want backend-level output allocation behavior to match caching allocators.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{GraphCompiler, GraphExecutor, TracedTensor};
let x = TracedTensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
let mut compiler = GraphCompiler::new();
let program = compiler.compile(&x.neg()).unwrap();
let mut executor = GraphExecutor::new(CpuBackend::new());
let out = executor.run(&program).unwrap();
assert_eq!(out.as_slice::<f64>().unwrap(), &[-2.0]);
executor.reclaim_outputs(vec![out]);Sourcepub fn reclaim_value_outputs(&mut self, outputs: Vec<TensorValue>)
pub fn reclaim_value_outputs(&mut self, outputs: Vec<TensorValue>)
Return compact value outputs to the backend pool when ownership is unique.
Lazy owned views are intentionally ignored because their base storage may be aliased by view metadata.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{GraphExecutor, Tensor, TensorValue};
let tensor = Tensor::from_vec_col_major(vec![1], vec![3.0_f64]).unwrap();
let mut executor = GraphExecutor::new(CpuBackend::new());
executor.reclaim_value_outputs(vec![TensorValue::from_tensor(tensor)]);Sourcepub fn extension_executor(&self) -> &ExtensionExecutor<B>
pub fn extension_executor(&self) -> &ExtensionExecutor<B>
Borrow the extension runtime executor owned by this graph executor.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{GraphExecutor};
let executor = GraphExecutor::new(CpuBackend::new());
assert_eq!(executor.extension_executor().cache_stats().entries, 0);Sourcepub fn extension_executor_mut(&mut self) -> &mut ExtensionExecutor<B>
pub fn extension_executor_mut(&mut self) -> &mut ExtensionExecutor<B>
Mutably borrow the extension runtime executor owned by this graph executor.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{GraphExecutor};
let mut executor = GraphExecutor::new(CpuBackend::new());
executor.extension_executor_mut().clear_caches();Sourcepub fn register_extension(
&mut self,
register: impl FnOnce(&mut ExtensionExecutor<B>) -> Result<(), ExtensionRuntimeRegistryError>,
) -> Result<(), ExtensionRuntimeRegistryError>
pub fn register_extension( &mut self, register: impl FnOnce(&mut ExtensionExecutor<B>) -> Result<(), ExtensionRuntimeRegistryError>, ) -> Result<(), ExtensionRuntimeRegistryError>
Register one extension runtime on this executor.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::GraphExecutor;
let mut executor = GraphExecutor::new(CpuBackend::new());
executor.register_extension(|_| Ok(())).unwrap();Sourcepub fn run(&mut self, program: &GraphProgram) -> Result<Tensor>
pub fn run(&mut self, program: &GraphProgram) -> Result<Tensor>
Run a one-output program using the program’s default input tensors.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{GraphCompiler, GraphExecutor, TracedTensor};
let x = TracedTensor::from_vec_col_major(vec![1], vec![3.0_f64]).unwrap();
let mut compiler = GraphCompiler::new();
let program = compiler.compile(&x.neg()).unwrap();
let mut executor = GraphExecutor::new(CpuBackend::new());
let out = executor.run(&program).unwrap();
assert_eq!(out.as_slice::<f64>().unwrap(), &[-3.0]);Sourcepub fn run_value(&mut self, program: &GraphProgram) -> Result<TensorValue>
pub fn run_value(&mut self, program: &GraphProgram) -> Result<TensorValue>
Run a one-output program and preserve lazy owned output views.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{GraphCompiler, GraphExecutor, TensorValue, TracedTensor};
let x = TracedTensor::from_vec_col_major(
vec![2, 3],
vec![1.0_f64, 2.0, 3.0, 4.0, 5.0, 6.0],
)
.unwrap();
let y = x.transpose(&[1, 0]).unwrap();
let mut compiler = GraphCompiler::new();
let program = compiler.compile(&y).unwrap();
let mut executor = GraphExecutor::new(CpuBackend::new());
let value = executor.run_value(&program).unwrap();
assert!(matches!(&value, TensorValue::View(_)));
assert_eq!(value.shape(), &[3, 2]);
assert_eq!(
value.to_tensor().unwrap().as_slice::<f64>().unwrap(),
&[1.0, 3.0, 5.0, 2.0, 4.0, 6.0]
);Sourcepub fn run_many(&mut self, program: &GraphProgram) -> Result<Vec<Tensor>>
pub fn run_many(&mut self, program: &GraphProgram) -> Result<Vec<Tensor>>
Run a program using the program’s default input tensors.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{GraphCompiler, GraphExecutor, TracedTensor};
let x = TracedTensor::from_vec_col_major(vec![1], vec![3.0_f64]).unwrap();
let y = x.neg();
let mut compiler = GraphCompiler::new();
let program = compiler.compile_many(&[&x, &y]).unwrap();
let mut executor = GraphExecutor::new(CpuBackend::new());
let outputs = executor.run_many(&program).unwrap();
assert_eq!(outputs.len(), 2);Sourcepub fn run_many_values(
&mut self,
program: &GraphProgram,
) -> Result<Vec<TensorValue>>
pub fn run_many_values( &mut self, program: &GraphProgram, ) -> Result<Vec<TensorValue>>
Run a program and preserve lazy owned output views.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{GraphCompiler, GraphExecutor, TensorValue, TracedTensor};
let x = TracedTensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 2.0, 3.0, 4.0]).unwrap();
let y = x.transpose(&[1, 0]).unwrap();
let mut compiler = GraphCompiler::new();
let program = compiler.compile_many(&[&y]).unwrap();
let mut executor = GraphExecutor::new(CpuBackend::new());
let outputs = executor.run_many_values(&program).unwrap();
assert_eq!(outputs.len(), 1);
assert!(matches!(&outputs[0], TensorValue::View(_)));
assert_eq!(outputs[0].shape(), &[2, 2]);Sourcepub fn run_with_inputs(
&mut self,
program: &GraphProgram,
bindings: &[(&TracedTensor, &Tensor)],
) -> Result<Tensor>
pub fn run_with_inputs( &mut self, program: &GraphProgram, bindings: &[(&TracedTensor, &Tensor)], ) -> Result<Tensor>
Run a one-output program with explicit runtime placeholder bindings.
Explicit bindings override program defaults and are validated against the ordered input specs captured in the compiled program.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{DType, GraphCompiler, GraphExecutor, Tensor, TracedTensor};
let x = TracedTensor::input_symbolic_shape(DType::F64, 1).unwrap();
let y = (&x + &x).unwrap();
let mut compiler = GraphCompiler::new();
let program = compiler
.compile_with_input_specs(&y, &[(&x, DType::F64, &[2])])
.unwrap();
let bound = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
let mut executor = GraphExecutor::new(CpuBackend::new());
let out = executor.run_with_inputs(&program, &[(&x, &bound)]).unwrap();
assert_eq!(out.as_slice::<f64>().unwrap(), &[2.0, 4.0]);Sourcepub fn run_value_with_inputs(
&mut self,
program: &GraphProgram,
bindings: &[(&TracedTensor, &Tensor)],
) -> Result<TensorValue>
pub fn run_value_with_inputs( &mut self, program: &GraphProgram, bindings: &[(&TracedTensor, &Tensor)], ) -> Result<TensorValue>
Run a one-output program with explicit bindings and preserve lazy output views.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{DType, GraphCompiler, GraphExecutor, Tensor, TensorValue, TracedTensor};
let x = TracedTensor::input_symbolic_shape(DType::F64, 2).unwrap();
let y = x.transpose(&[1, 0]).unwrap();
let mut compiler = GraphCompiler::new();
let program = compiler
.compile_with_input_specs(&y, &[(&x, DType::F64, &[2, 2])])
.unwrap();
let bound = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 2.0, 3.0, 4.0]).unwrap();
let mut executor = GraphExecutor::new(CpuBackend::new());
let value = executor.run_value_with_inputs(&program, &[(&x, &bound)]).unwrap();
assert!(matches!(&value, TensorValue::View(_)));
assert_eq!(value.to_tensor().unwrap().as_slice::<f64>().unwrap(), &[1.0, 3.0, 2.0, 4.0]);Sourcepub fn run_with_input_reads<'a>(
&mut self,
program: &'a GraphProgram,
bindings: &[(&TracedTensor, TensorRead<'a>)],
) -> Result<Tensor>
pub fn run_with_input_reads<'a>( &mut self, program: &'a GraphProgram, bindings: &[(&TracedTensor, TensorRead<'a>)], ) -> Result<Tensor>
Run a one-output program with explicit borrowed runtime placeholder bindings.
Unlike run_with_inputs, caller-owned input
tensors are read through TensorRead and are not cloned into executor
slots.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{
DType, GraphCompiler, GraphExecutor, TensorRead, TensorView, TracedTensor, TypedTensorView,
};
let x = TracedTensor::input_symbolic_shape(DType::F64, 1).unwrap();
let y = (&x + &x).unwrap();
let mut compiler = GraphCompiler::new();
let program = compiler
.compile_with_input_specs(&y, &[(&x, DType::F64, &[2])])
.unwrap();
let data = [1.0_f64, 99.0, 2.0];
let view = TypedTensorView::from_slice([2], [2], 0, &data).unwrap();
let read = TensorRead::from_view(TensorView::F64(view));
let mut executor = GraphExecutor::new(CpuBackend::new());
let out = executor.run_with_input_reads(&program, &[(&x, read)]).unwrap();
assert_eq!(out.as_slice::<f64>().unwrap(), &[2.0, 4.0]);Sourcepub fn run_value_with_input_reads<'a>(
&mut self,
program: &'a GraphProgram,
bindings: &[(&TracedTensor, TensorRead<'a>)],
) -> Result<TensorValue>
pub fn run_value_with_input_reads<'a>( &mut self, program: &'a GraphProgram, bindings: &[(&TracedTensor, TensorRead<'a>)], ) -> Result<TensorValue>
Run a one-output program with borrowed bindings and preserve lazy output views.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{
DType, GraphCompiler, GraphExecutor, TensorRead, TensorValue, TensorView, TracedTensor,
TypedTensorView,
};
let x = TracedTensor::input_symbolic_shape(DType::F64, 2).unwrap();
let y = x.transpose(&[1, 0]).unwrap();
let mut compiler = GraphCompiler::new();
let program = compiler
.compile_with_input_specs(&y, &[(&x, DType::F64, &[2, 2])])
.unwrap();
let data = [1.0_f64, 2.0, 3.0, 4.0];
let view = TypedTensorView::from_slice([2, 2], [1, 2], 0, &data).unwrap();
let read = TensorRead::from_view(TensorView::F64(view));
let mut executor = GraphExecutor::new(CpuBackend::new());
let value = executor
.run_value_with_input_reads(&program, &[(&x, read)])
.unwrap();
assert!(matches!(&value, TensorValue::View(_)));
assert_eq!(value.shape(), &[2, 2]);Sourcepub fn run_many_with_inputs(
&mut self,
program: &GraphProgram,
bindings: &[(&TracedTensor, &Tensor)],
) -> Result<Vec<Tensor>>
pub fn run_many_with_inputs( &mut self, program: &GraphProgram, bindings: &[(&TracedTensor, &Tensor)], ) -> Result<Vec<Tensor>>
Run a program with explicit runtime placeholder bindings.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{DType, GraphCompiler, GraphExecutor, Tensor, TracedTensor};
let x = TracedTensor::input_symbolic_shape(DType::F64, 1).unwrap();
let sum = (&x + &x).unwrap();
let mut compiler = GraphCompiler::new();
let program = compiler
.compile_with_input_specs(&sum, &[(&x, DType::F64, &[2])])
.unwrap();
let bound = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
let mut executor = GraphExecutor::new(CpuBackend::new());
let outputs = executor.run_many_with_inputs(&program, &[(&x, &bound)]).unwrap();
assert_eq!(outputs.len(), 1);
assert_eq!(outputs[0].as_slice::<f64>().unwrap(), &[2.0, 4.0]);Sourcepub fn run_many_values_with_inputs(
&mut self,
program: &GraphProgram,
bindings: &[(&TracedTensor, &Tensor)],
) -> Result<Vec<TensorValue>>
pub fn run_many_values_with_inputs( &mut self, program: &GraphProgram, bindings: &[(&TracedTensor, &Tensor)], ) -> Result<Vec<TensorValue>>
Run a program with explicit bindings and preserve lazy output views.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{DType, GraphCompiler, GraphExecutor, Tensor, TensorValue, TracedTensor};
let x = TracedTensor::input_symbolic_shape(DType::F64, 2).unwrap();
let y = x.transpose(&[1, 0]).unwrap();
let mut compiler = GraphCompiler::new();
let program = compiler
.compile_with_input_specs(&y, &[(&x, DType::F64, &[2, 2])])
.unwrap();
let bound = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 2.0, 3.0, 4.0]).unwrap();
let mut executor = GraphExecutor::new(CpuBackend::new());
let outputs = executor
.run_many_values_with_inputs(&program, &[(&x, &bound)])
.unwrap();
assert_eq!(outputs.len(), 1);
assert!(matches!(&outputs[0], TensorValue::View(_)));Sourcepub fn run_many_with_input_reads<'a>(
&mut self,
program: &'a GraphProgram,
bindings: &[(&TracedTensor, TensorRead<'a>)],
) -> Result<Vec<Tensor>>
pub fn run_many_with_input_reads<'a>( &mut self, program: &'a GraphProgram, bindings: &[(&TracedTensor, TensorRead<'a>)], ) -> Result<Vec<Tensor>>
Run a program with explicit borrowed runtime placeholder bindings.
Bindings override program defaults and are validated against the input specs captured in the compiled program. Bound tensors are borrowed by the executor for this call instead of cloned into input slots.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{
DType, GraphCompiler, GraphExecutor, TensorRead, TensorView, TracedTensor, TypedTensorView,
};
let x = TracedTensor::input_symbolic_shape(DType::F64, 1).unwrap();
let y = (&x + &x).unwrap();
let mut compiler = GraphCompiler::new();
let program = compiler
.compile_with_input_specs(&y, &[(&x, DType::F64, &[2])])
.unwrap();
let data = [1.0_f64, 99.0, 2.0];
let view = TypedTensorView::from_slice([2], [2], 0, &data).unwrap();
let read = TensorRead::from_view(TensorView::F64(view));
let mut executor = GraphExecutor::new(CpuBackend::new());
let outputs = executor.run_many_with_input_reads(&program, &[(&x, read)]).unwrap();
assert_eq!(outputs[0].as_slice::<f64>().unwrap(), &[2.0, 4.0]);Sourcepub fn run_many_values_with_input_reads<'a>(
&mut self,
program: &'a GraphProgram,
bindings: &[(&TracedTensor, TensorRead<'a>)],
) -> Result<Vec<TensorValue>>
pub fn run_many_values_with_input_reads<'a>( &mut self, program: &'a GraphProgram, bindings: &[(&TracedTensor, TensorRead<'a>)], ) -> Result<Vec<TensorValue>>
Run a program with borrowed bindings and preserve lazy output views.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{
DType, GraphCompiler, GraphExecutor, TensorRead, TensorValue, TensorView, TracedTensor,
TypedTensorView,
};
let x = TracedTensor::input_symbolic_shape(DType::F64, 2).unwrap();
let y = x.transpose(&[1, 0]).unwrap();
let mut compiler = GraphCompiler::new();
let program = compiler
.compile_with_input_specs(&y, &[(&x, DType::F64, &[2, 2])])
.unwrap();
let data = [1.0_f64, 2.0, 3.0, 4.0];
let view = TypedTensorView::from_slice([2, 2], [1, 2], 0, &data).unwrap();
let read = TensorRead::from_view(TensorView::F64(view));
let mut executor = GraphExecutor::new(CpuBackend::new());
let outputs = executor
.run_many_values_with_input_reads(&program, &[(&x, read)])
.unwrap();
assert_eq!(outputs.len(), 1);
assert!(matches!(&outputs[0], TensorValue::View(_)));Sourcepub fn eval_exec_ir(
&mut self,
program: &ExecProgram,
inputs: Vec<Tensor>,
) -> Result<Vec<Tensor>>
pub fn eval_exec_ir( &mut self, program: &ExecProgram, inputs: Vec<Tensor>, ) -> Result<Vec<Tensor>>
Evaluate an execution program through this executor’s backend state.
This lower-level entry point is intended for code that already owns an execution program and concrete ordered input tensors.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{GraphCompiler, GraphExecutor, TracedTensor};
let x = TracedTensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
let mut compiler = GraphCompiler::new();
let program = compiler.compile(&x.neg()).unwrap();
let mut executor = GraphExecutor::new(CpuBackend::new());
let out = executor.run(&program).unwrap();
assert_eq!(out.as_slice::<f64>().unwrap(), &[-2.0]);Sourcepub fn eval_exec_ir_values(
&mut self,
program: &ExecProgram,
inputs: Vec<Tensor>,
) -> Result<Vec<TensorValue>>
pub fn eval_exec_ir_values( &mut self, program: &ExecProgram, inputs: Vec<Tensor>, ) -> Result<Vec<TensorValue>>
Evaluate an execution program and preserve lazy owned output views.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{GraphCompiler, GraphExecutor, TensorValue, TracedTensor};
let x = TracedTensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 2.0, 3.0, 4.0]).unwrap();
let y = x.transpose(&[1, 0]).unwrap();
let mut compiler = GraphCompiler::new();
let program = compiler.compile(&y).unwrap();
let mut executor = GraphExecutor::new(CpuBackend::new());
let value = executor.run_value(&program).unwrap();
assert!(matches!(&value, TensorValue::View(_)));
assert_eq!(value.shape(), &[2, 2]);Sourcepub fn eval_exec_ir_non_consuming(
&mut self,
program: &ExecProgram,
inputs: &[Tensor],
) -> Result<Vec<Tensor>>
pub fn eval_exec_ir_non_consuming( &mut self, program: &ExecProgram, inputs: &[Tensor], ) -> Result<Vec<Tensor>>
Evaluate an execution program without consuming caller-owned inputs.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{GraphCompiler, GraphExecutor, TracedTensor};
let x = TracedTensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
let mut compiler = GraphCompiler::new();
let program = compiler.compile(&x.neg()).unwrap();
let mut executor = GraphExecutor::new(CpuBackend::new());
let out = executor.run(&program).unwrap();
assert_eq!(out.shape(), &[1]);Sourcepub fn eval_exec_ir_non_consuming_values(
&mut self,
program: &ExecProgram,
inputs: &[Tensor],
) -> Result<Vec<TensorValue>>
pub fn eval_exec_ir_non_consuming_values( &mut self, program: &ExecProgram, inputs: &[Tensor], ) -> Result<Vec<TensorValue>>
Evaluate an execution program without consuming inputs and preserve lazy outputs.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{GraphCompiler, GraphExecutor, TensorValue, TracedTensor};
let x = TracedTensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 2.0, 3.0, 4.0]).unwrap();
let y = x.transpose(&[1, 0]).unwrap();
let mut compiler = GraphCompiler::new();
let program = compiler.compile(&y).unwrap();
let mut executor = GraphExecutor::new(CpuBackend::new());
let value = executor.run_value(&program).unwrap();
assert!(matches!(&value, TensorValue::View(_)));
assert_eq!(value.to_tensor().unwrap().shape(), &[2, 2]);Sourcepub fn clear_backend_cache(&mut self)
pub fn clear_backend_cache(&mut self)
Clear backend-specific runtime analysis cache entries.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{GraphExecutor};
let mut executor = GraphExecutor::new(CpuBackend::new());
executor.clear_backend_cache();
assert_eq!(executor.cache_stats().backend.entries, 0);Sourcepub fn clear_extension_caches(&mut self)
pub fn clear_extension_caches(&mut self)
Clear generic extension runtime cache entries.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{GraphExecutor};
let mut executor = GraphExecutor::new(CpuBackend::new());
executor.clear_extension_caches();
assert_eq!(executor.cache_stats().extensions.entries, 0);Sourcepub fn clear_caches(&mut self)
pub fn clear_caches(&mut self)
Clear every executor-owned runtime cache.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{GraphExecutor};
let mut executor = GraphExecutor::new(CpuBackend::new());
executor.clear_caches();
assert_eq!(executor.cache_stats().backend.entries, 0);Sourcepub fn cache_stats(&self) -> GraphExecutorCacheStats
pub fn cache_stats(&self) -> GraphExecutorCacheStats
Return executor runtime cache-entry and retained-byte stats.
§Examples
use tenferro_cpu::CpuBackend;
use tenferro_runtime::{GraphExecutor};
let executor = GraphExecutor::new(CpuBackend::new());
let stats = executor.cache_stats();
assert_eq!(stats.extensions.entries, 0);