Generational GC on Playground and New Async/Await: Weekly Motoko Updates

Motoko Weekly Update Part 2!

Hey Motoko Devs,

The languages team is back with a second weekly update to keep you guys in sync with what we’re working on for Motoko. You can check out our previous post here.

As a recap, last time we talked about the new record extension syntax, extended buffer class, and teased the generational GC that will be coming in the next release of dfx.

Generational Garbage Collector now Available on the Motoko Playground!

The new generational garbage collector (GC) has been deployed to the Motoko Playground (thanks to @chenyan)! If you’re curious to try out the new system, this is a great way to try it out before the official release in the next version of dfx.

As a reminder, generational garbage collection allows more efficient management and cleaning of your Motoko program’s heap space. This helps, primarily, with reducing cycle costs for your canisters and avoiding the cycle limit per message on particularly heavy computation.

Our internal testing shows that this new garbage collector is more performant than the existing two collection strategies in most situations, but @luc-blaeser (the team member responsible for this new feature) is interested in real world metrics. If you’re interested in benchmarking and stress-testing Motoko, throw this new collection strategy at your canisters and send us any metrics you find interesting :slight_smile:

Teaser: Efficient Async Await

We are adding a new feature to Motoko: async* and await*!

This new type and operator lets you abstract out asynchronous code without having to send an inter-canister message (and thus suspend execution and go through the scheduler)!

This feature is not yet shipped with dfx, but is available on the playground and the most recent release of moc.

Example:

Let’s say you have a function that divides two numbers, doubles the sum, and returns the answer. Let’s also assume that your canister publicly exposes this function for other canisters to call, and so it has an asynchronous type.

public func foo(number1 : Nat, number2 : Nat) : async Nat {
  if (number2 == 0) {
    throw Error.reject "Cannot divide by zero";
  };
  (number1 / number2) * 2
};

Now, for the sake of convoluted examples, let’s say you want to pull out the division and the error checking to a helper function. Something unfortunate happens.

func divide(number1 : Nat, number2 : Nat) : async Nat {
  if (number2 == 0) {
    throw Error.reject "Cannot divide by zero";
  };
  number1 / number2
};

public func foo(number1 : Nat, number2 : Nat) : async Nat {
  (await divide(number1, number2)) * 2
};

Even though the divide function is a local helper, it needs to be declared with an async type to throw the reject. This means that when it is called and the result is awaited on, the canister actually pauses execution and sends a message (to itself…) to invoke this call. Not very efficient, especially if you have a lot of async calls like this.

But it’s okay. Just add a * to everything. The compiler will take care of the rest.

func divide(number1 : Nat, number2 : Nat) : async* Nat {
  if (number2 == 0) {
    throw Error.reject "Cannot divide by zero";
  };
  number1 / number2
};

public func foo(number1 : Nat, number2 : Nat) : async Nat {
  (await* divide(number1, number2)) * 2
};

Now, the helper is called locally, and you take no performance hit for pulling out code from async functions to helpers!

Note: There is a bit more detail to this feature than what we covered so far. In a nutshell, this feature avoids context switches at the expense of less commit points; your code may behave differently with traps and concurrent messages. Please be mindful of this tradeoff and difference between this feature and the async / await that you are used to. As we gather feedback on this new feature from users, we may make changes in future versions of Motoko. If you want to see the full discussion, you can check it out here.

See you next time!

We’ll be back with more features and updates :slight_smile:

– DFINITY Languages team.

21 Likes

Excited GIFs - Find & Share on GIPHY

These features are huge for the scalability of Motoko - both in terms of improvements to heap storage (GC), and message load (output queues). So nice that they came together in the same release.

1 Like

Any further explanation about Generational GC?

One thing that is not different, is it if you call await star you probably need to assume that a consensus check could’ve happened and recheck your variables after whatever return you get. Obviously, if the internal code doesn’t do any awaits, this won’t occur, but especially in instances where you don’t know if an await will happen or not, make sure you reverify any loaded state after the await star.

Once the feature is released in a dfx release, I will post a more detailed explanation going into details.

1 Like

dfx 0.12.2-beta.0 contains Motoko 0.7.4. Does that include these changes already?

I’d love to see some more documentation and examples on the pitfalls of async/await* when it comes out eventually - seems like there’s a few edge cases to consider to insure the intended application behavior is implemented.

5 Likes

This is awesome! Thanks to the team :pray:

It does! I am planning on waiting for the full release

1 Like

Not sure if I understand. Is what you say specific to async*? I mean isn’t it true that for any internal function that you call, that if you don’t know what happens internally then there could be an await happening inside?

Not if the function isn’t tagged with an async return type. You can’t put an await in there unless you have an async…the compiler yells at you.

https://m7sm4-2iaaa-aaaab-qabra-cai.raw.ic0.app/?tag=2474467952

1 Like

Ok, right. So then the difference is only that it can be more efficient (avoid some self-calls) but otherwise, as you say, one has to code like it was an await (non-starred).

1 Like

Are there any examples available showing how using this would definitely cause an issue?

    public func foo() : async* Nat {
      await anotherCanister.get();
    };

    public func bar() : async Nat {
      await* foo();
    };

Does the * make sense in this example if there is a call to another canister? (when calling bar)

3 Likes

Not had my morning coffee yet, but yes, this makes sense and means ‘bar()’ only does one suspension at the ‘await’ inside ‘foo()’.

2 Likes