dfinity / motoko

Simple high-level language for writing Internet Computer canisters
Apache License 2.0
516 stars 97 forks source link

library: performance counters and how to expose them #3294

Closed crusso closed 2 years ago

crusso commented 2 years ago

3288 exposes the raw ic0.performence_counter as a prim.

ic0.performance_counter(type) is reset to zero on every context switch (IC entry point) which means you cannot correctly use it, e.g. across awaits, without more work possibly compiler support. What's worse, the counter isn't updated within basic blocks though any reasonable stretch of Motoko code is probably unlikely to observe that.

Instead of, or in addition to, exposing the raw prim in base, shall we just/also expose a higher-order function, perhaps:

measure: (counter_type: Nat32) -> (<A,B>((fun : A -> B) -> ( (arg:A) -> (B, Nat64)))

eg.

let (i, diff) = measure(0)(fib)(1000)

Since (local) functions can't await, this might prevent some confusion due to the IC semantics of performance_counter.

My one concern is that Motoko functions aren't unary-2-unary, so some plumbing may be required even in common cases (e.g. for binary functions).

The other option might be:

measure: (counter_type: Nat32) -> (thunk:() -> ()) -> Nat64

So users are forced to thunk their computations and just get a measurement, with no result.

I'm once again regretting that we went for n-ary function types in Motoko (and that I pushed for them)

chenyan-dfinity commented 2 years ago

Thunk is general enough, e.g., f(x,y) --> func () { ignore f(x,y) }

matthewhammer commented 2 years ago

What does the argument counter_type: Nat32 do? (Looked at the files changed in https://github.com/dfinity/motoko/pull/3288 but I didn't see it documented there.)

Update: Found this helpful link in another discussion: https://internetcomputer.org/docs/current/references/ic-interface-spec/#system-api-performance-counter

For what it's worth, either API design (the one with functional plumbing, or the imperative one that @chenyan-dfinity gave) seems fine to me.

crusso commented 2 years ago

Landed here: https://github.com/dfinity/motoko-base/pull/381:

 /// Given computation, `comp`, counts the number of actual and (for IC system calls) notional WebAssembly
  /// instructions performed during the execution of `comp()`.
  ///
  /// More precisely, returns the difference between the state of the IC instruction counter (_performance counter_ `0`) before and after executing `comp()`
  /// (see [Performance Counter](https://internetcomputer.org/docs/current/references/ic-interface-spec#system-api-performance-counter)).
  ///
  /// NB: `countInstructions(comp)` will _not_ account for any deferred garbage collection costs incurred by `comp()`.
  public func countInstructions(comp : () -> ()) : Nat64 {
    let pre = Prim.performanceCounter(0);
    comp();
    let post = Prim.performanceCounter(0);
    post - pre
  }