Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inline List.iter/List.iteri for performance #8176

Merged
merged 2 commits into from
Jan 15, 2020

Conversation

TIHan
Copy link
Contributor

@TIHan TIHan commented Jan 12, 2020

This inlines List.iter/List.iteri for performance reasons. Normally I do not like inlining core functions in FSharp.Core because of the potential to expose internals. But, this does not show any of those downsides.

This is in reaction to this PR: #8175

It doesn't solve the main problem that the PR describes though. There is optimization behavior that is still causing a function allocation.

@auduchinok
Copy link
Member

@TIHan Could it also be applied to arrays and sequences iteration?

@TIHan
Copy link
Contributor Author

TIHan commented Jan 13, 2020

@auduchinok
Some of the common array APIs are already inlined and might not cause a function allocation, but not in all cases.

There is interesting behavior when it comes to the inlining of functions. The compiler can decide to inline a function even when it is not marked inline; there is a heuristic called lambdaInlineThreshold which is based on function size. There are caveats with that approach, especially when you get into nested functions and how the compiler decides to inline them or not.

AFAIK, a local function's implementation will be optimized first, which in itself could contain functions that could inline themselves. This affects the size of the overall function and may cause the function not to inline when called and have to create an allocation. This is the behavior of an inlined List.iter, which is why it was still allocating a function. Trying to understand how this mechanism works in the optimizer is difficult and will take time to fully grasp it. So far, my thought was to stop optimizing local functions if they are simple wrappers around other functions. They still get optimized when called because they might be inlined. However, it seems that CI is failing because it's creating IL that we don't expect, but it does not happen on my machine. This is a bit harder than I anticipated :)

src/fsharp/Optimizer.fs Outdated Show resolved Hide resolved
@TIHan TIHan changed the title [WIP] Inline List.iter/List.iteri for performance Inline List.iter/List.iteri for performance Jan 15, 2020
@TIHan
Copy link
Contributor Author

TIHan commented Jan 15, 2020

This is ready, just need a review.

Copy link
Contributor

@forki forki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if you revert your recent changes where you manually converted to for loop?

@TIHan TIHan changed the title Inline List.iter/List.iteri for performance [WIP] Inline List.iter/List.iteri for performance Jan 15, 2020
@TIHan
Copy link
Contributor Author

TIHan commented Jan 15, 2020

So, the inline List.iter/List.iteri is fine, but I am going to separate out the specific optimizer change in a different PR.

@TIHan
Copy link
Contributor Author

TIHan commented Jan 15, 2020

@forki I just built it and yea, it did not optimize it =/ - means I do not have a good test.

However, the test that is there is passing as a result of the change and failing if the change isn't in place.

Also, if I do not use partial application, it will be optimized with no allocation, even without the optimizer change.

and CheckTypesDeep cenv f g env tys = 
    tys |> List.iter (CheckTypeDeep cenv f g env true)

to this:

and CheckTypesDeep cenv f g env tys = 
    tys |> List.iter (fun ty -> CheckTypeDeep cenv f g env true ty)

@cartermp
Copy link
Contributor

@TIHan want to remove the WIP?

@TIHan TIHan changed the title [WIP] Inline List.iter/List.iteri for performance Inline List.iter/List.iteri for performance Jan 15, 2020
@@ -100,7 +100,7 @@ namespace Microsoft.FSharp.Collections
loop ([], state) (rev list)

[<CompiledName("Iterate")>]
let iter action list = Microsoft.FSharp.Primitives.Basics.List.iter action list
let inline iter action (list:'T list) = for x in list do action x
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. A bit surprised that these older, unoptimized functions for lists are still in use.

@cartermp cartermp merged commit c627f27 into dotnet:master Jan 15, 2020
@forki
Copy link
Contributor

forki commented Jan 16, 2020

Ok @TIHan so something is wrong with optimization of partial application? That should be investigated further

@TIHan
Copy link
Contributor Author

TIHan commented Jan 16, 2020

@forki Definitely should be investigated. I'm still going to look at it.

@forki
Copy link
Contributor

forki commented Jan 16, 2020

XOXO

nosami pushed a commit to xamarin/visualfsharp that referenced this pull request Feb 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants