Language-ext: Performance question

Created on 24 Dec 2019  路  3Comments  路  Source: louthy/language-ext

Does language-ext add some performance overhead?
Does language-ext generate the same IL code, same as if I write with standart c# language constructs?

Most helpful comment

Everything Tom (@colethecoder) says is the correct way to think about it. And to expand on Tyson's (@bender2k14) point, Kent is saying: don't prematurely optimise.

I wrote a wiki page about performance which you might find interesting. It's not exhaustive on the issues you might face, but it should give you a flavour. Also, you'll see from it that (as an ex-games engine programmer) I'm very aware of potential performance problems and do whatever I can to make this library quick.

Users of this library will get continual performance improvements as I optimise various parts of it (because it's something I care a lot about). The collection types are a big part of this, because this is where performance is often lost. Something like Seq<A> isn't that far from the .NET List<A> in many cases, which for an immutable data-type is amazing.

Does language-ext generate the same IL code, same as if I write with standart c# language constructs?

This bit confused me slightly. And I'm wondering if you're referring to the generated IL for the Record<TYPE> type. It is the same IL and just as fast as regular C#. The other way of creating [Record] or [Union] types, using the code-gen library, creates the exact same C# you'd write by hand and can be seen in the generated files (Press F12 on the type to see the backing classes). It is as fast as writing the code manually and importantly is guaranteed to be correct :)

The great thing about writing functionally (if you do it correctly by concentrating on writing pure functions and composing them into new pure functions) is that when you find anything, that's not performing well enough, it's trivial to swap it out for something more imperative and performant - as long as the containing function honours the same pure contract.

For example, a classic way to implement fold is to use recursion:
```c#
static S Fold(Seq items, S state, Func f) =>
items.IsEmpty
? state
: Fold(items.Tail, f(state, items.Head), f);

This could be replaced with:
```c#
    static S Fold<S, A>(Seq<A> items, S state, Func<S, A, S> f)
    {
        foreach(var item in items)
        {
            state = f(state, item);
        }
        return state;
    }

The function is still pure, but the internals of it have been swapped out and made imperative to get the maximum performance out of it. This is the best approach and actually makes performance tweaking trivial later because there's no external dependencies to worry about.

All 3 comments

The short answer is yes, there is some performance overhead in using a Functional C# approach whether you use Language-Ext or not. Using LINQ and lambda expressions for example will cause a few extra allocations.

The collections in Language-Ext are pretty performant, @louthy made a lot of tweaks to these this year to close the gap between the collections in the BCL and Lang-Ext's.

Recently @wdolek added some benchmarks to the project so you can run them if you are interested in comparisons in some places.

The more pertinent question is: Does it matter? In most cases the performance difference will be negligible and the benefit to a functional approach vastly out weighs it. If you are working on something particularly performance critical then you should benchmark it and understand the trade off you are making. High performance code is often a PITA to maintain, part of the reason to write in the functional style is to improve maintainability.

I work on a large web application with a lot of background services. We use Language-Ext in the majority of our code. Occasionally we run into something that needs an imperative approach to squeeze out the performance (or avoid blowing the stack because of the lack of tail recursion). When we do this we tend to use BenchmarkDotNet to measure the gains we are getting.

Make it work, make it right, make it fast.
-- Kent Beck

Everything Tom (@colethecoder) says is the correct way to think about it. And to expand on Tyson's (@bender2k14) point, Kent is saying: don't prematurely optimise.

I wrote a wiki page about performance which you might find interesting. It's not exhaustive on the issues you might face, but it should give you a flavour. Also, you'll see from it that (as an ex-games engine programmer) I'm very aware of potential performance problems and do whatever I can to make this library quick.

Users of this library will get continual performance improvements as I optimise various parts of it (because it's something I care a lot about). The collection types are a big part of this, because this is where performance is often lost. Something like Seq<A> isn't that far from the .NET List<A> in many cases, which for an immutable data-type is amazing.

Does language-ext generate the same IL code, same as if I write with standart c# language constructs?

This bit confused me slightly. And I'm wondering if you're referring to the generated IL for the Record<TYPE> type. It is the same IL and just as fast as regular C#. The other way of creating [Record] or [Union] types, using the code-gen library, creates the exact same C# you'd write by hand and can be seen in the generated files (Press F12 on the type to see the backing classes). It is as fast as writing the code manually and importantly is guaranteed to be correct :)

The great thing about writing functionally (if you do it correctly by concentrating on writing pure functions and composing them into new pure functions) is that when you find anything, that's not performing well enough, it's trivial to swap it out for something more imperative and performant - as long as the containing function honours the same pure contract.

For example, a classic way to implement fold is to use recursion:
```c#
static S Fold(Seq items, S state, Func f) =>
items.IsEmpty
? state
: Fold(items.Tail, f(state, items.Head), f);

This could be replaced with:
```c#
    static S Fold<S, A>(Seq<A> items, S state, Func<S, A, S> f)
    {
        foreach(var item in items)
        {
            state = f(state, item);
        }
        return state;
    }

The function is still pure, but the internals of it have been swapped out and made imperative to get the maximum performance out of it. This is the best approach and actually makes performance tweaking trivial later because there's no external dependencies to worry about.

Was this page helpful?
0 / 5 - 0 ratings