Enum fusion?

You mean it’s useless because the Erlang compiler will generate the same BEAM code for both examples?

I would be interested in (real-world) examples where the Erlang compiler is unable to do that optimization. If we could improve the Erlang compiler to generate better code for nested cases it would benefit the entire BEAM community.

In Erlang/OTP 26, it sufficient to enable the maybe feature in the compiler. There is no longer any need to enable it in the runtime system too for it to work. We realized that we had to do that so that we could use maybe in OTP code.

5 Likes

My compiler analyzes the body of fn and checks if it can call any impure function. Right now, this analyzer is primitive, because I lack graph coloring algorithm (or tree coloring, to be precise).

This kind of optimization removes vanilla hot-swap, but other hot-swaps will still be possible, I just need infrastructure for this.

1 Like

Hi all!

It’s been a while since I shared Enumancer as an early proof of concept of “Enum fusion”, and I’ve been thinking about this topic since, especially regarding how to make it practical to use.

I am glad to announce the release of a new library, Iter, which should be closer to the original idea described in this thread. I hope it can offer a compelling, fast and easy-to-use alternative to the Enum and Stream modules.

iex> 1..5 |> Iter.filter(& rem(&1, 2) == 1) |> Iter.map(& &1 ** 2) |> Iter.reverse()
[25, 9, 1]

Unlike the same pipeline written with Enum, it won’t build any intermediate list, therefore saving memory and CPU cycles.

You can think of Iter as compile-time streams, or as comprehensions on steroids. It should be highly efficient compared to the same pipeline written with Stream, since it does most of the work at compile time without any runtime overhead. And while it actually works very similarly to for/1 under the hood and basically emits the same code, it offers a much more flexible, composable and extensive API.

As previously pointed out by Jose, there is one limitation: breaking the pipeline will remove the opportunity for Iter to merge steps, in which case the performance will simply be the same as good old Enum. This seems like a reasonable trade-off though.

I’m looking forward to hearing your thoughts!

PS: even if you don’t plan to use Iter, you might still be interested in this Enum cheatsheet :slightly_smiling_face:

7 Likes

If you’re interested, we can collaborate on enum optimizations in optimizing compiler I am writing.

Some optimizations are just not possible with regular approach and they require proper call stack analysis for purity and safeness of some calls. For example,

list
|> Enum.map(f1)
|> Enum.map(f2)

Can be joined into one Enum.map only when f1 is pure.

Plus some Stream calls can be translated to Enum calls, and Enum calls can be translated to :lists calls, which can then be inlined.

PM me if you’re interested

1 Like