-
Notifications
You must be signed in to change notification settings - Fork 789
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
Use for..in..do instead of List.iter to prevent function allocations #8175
Conversation
What about changing the compiler to rewrite this? |
I want to do that, but it may not be straight forward as we think. It would be quite an interesting optimization. For |
The easiest thing would be to inline I'm generally not comfortable inlining functions in FSharp.Core in order to obtain performance due to potentially exposing private internals that should not be exposed. |
Well, we can't quite inline |
Does the implementation of List.iter use a for loop?
If this is not possible we could try to use similar techniques like in my
seq.map fusion PR.
Will Smith <notifications@github.com> schrieb am So., 12. Jan. 2020, 14:40:
… Well, we can't quite inline List.iter - since it requires recursion.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#8175?email_source=notifications&email_token=AAAOANC2MX2YXJ67Z2HSMDLQ5MMVVA5CNFSM4KFXTPFKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEIW2ICI#issuecomment-573416457>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAOANGCAVU5VRZVD7ZHMADQ5MMVVANCNFSM4KFXTPFA>
.
|
It doesn't use a for loop, but I think we could re-implement it using a for loop though. |
I tried inlining internal static void CheckTypesDeep(cenv cenv, FSharpFunc<Tast.TType, Unit> f_0, FSharpOption<FSharpFunc<bool, FSharpFunc<Tast.EntityRef, Unit>>> f_1, FSharpOption<FSharpFunc<Tuple<Tast.EntityRef, FSharpList<Tast.TType>>, Unit>> f_2, FSharpOption<FSharpFunc<Tast.TraitConstraintSln, Unit>> f_3, FSharpOption<FSharpFunc<Tuple<env, Tast.Typar>, Unit>> f_4, TcGlobals.TcGlobals g, env env, FSharpList<Tast.TType> tys)
{
FSharpFunc<Tast.TType, Unit> fSharpFunc = new CheckTypesDeep@382(cenv, f_0, f_1, f_2, f_3, f_4, g, env);
FSharpList<Tast.TType> fSharpList = tys;
for (FSharpList<Tast.TType> tailOrNull = fSharpList.TailOrNull; tailOrNull != null; tailOrNull = fSharpList.TailOrNull)
{
Tast.TType headOrDefault = fSharpList.HeadOrDefault;
fSharpFunc.Invoke(headOrDefault);
fSharpList = tailOrNull;
}
} Why is it still allocating the function? I have no idea. It's probably an optimization bug. Below is just using this PR: internal static void CheckTypesDeep(cenv cenv, FSharpFunc<Tast.TType, Unit> f_0, FSharpOption<FSharpFunc<bool, FSharpFunc<Tast.EntityRef, Unit>>> f_1, FSharpOption<FSharpFunc<Tuple<Tast.EntityRef, FSharpList<Tast.TType>>, Unit>> f_2, FSharpOption<FSharpFunc<Tast.TraitConstraintSln, Unit>> f_3, FSharpOption<FSharpFunc<Tuple<env, Tast.Typar>, Unit>> f_4, TcGlobals.TcGlobals g, env env, FSharpList<Tast.TType> tys)
{
FSharpList<Tast.TType> fSharpList = tys;
for (FSharpList<Tast.TType> tailOrNull = fSharpList.TailOrNull; tailOrNull != null; tailOrNull = fSharpList.TailOrNull)
{
Tast.TType ty = fSharpList.HeadOrDefault;
CheckTypeDeep(cenv, f_0, f_1, f_2, f_3, f_4, g, env, isInner: true, ty);
fSharpList = tailOrNull;
}
} -- Here are some examples to test out: open System
open System.Runtime.CompilerServices
let inline iter action (list: 'T list) =
for x in list do action x
//[<MethodImpl(MethodImplOptions.NoInlining)>]
let checkIt (a: int) (b: int) (c: int) (d: int) (e: int) (x: string) = if x = "test" then printf "test"
let check1 a b c d e (x: string list) =
x |> iter (checkIt a b c d e)
let check2 a b c d e (x: string list) =
for xx in x do checkIt a b c d e xx
public static void check1(int a, int b, int c, int d, int e, FSharpList<string> x)
{
FSharpFunc<string, Unit> fSharpFunc = new check1@11();
FSharpList<string> fSharpList = x;
for (FSharpList<string> tailOrNull = fSharpList.TailOrNull; tailOrNull != null; tailOrNull = fSharpList.TailOrNull)
{
string headOrDefault = fSharpList.HeadOrDefault;
fSharpFunc.Invoke(headOrDefault);
fSharpList = tailOrNull;
}
} public static void check2(int a, int b, int c, int d, int e, FSharpList<string> x)
{
FSharpList<string> fSharpList = x;
for (FSharpList<string> tailOrNull = fSharpList.TailOrNull; tailOrNull != null; tailOrNull = fSharpList.TailOrNull)
{
string headOrDefault = fSharpList.HeadOrDefault;
if (string.Equals(headOrDefault, "test"))
{
PrintfFormat<Unit, TextWriter, Unit, Unit> format = new PrintfFormat<Unit, TextWriter, Unit, Unit, Unit>("test");
PrintfModule.PrintFormatToTextWriter(Console.Out, format);
}
fSharpList = tailOrNull;
}
} -- let checkIt (a: int) (b: int) (c: int) (d: int) (x: string) = if x = "test" then printf "test"
let check1 a b c d (x: string list) =
x |> iter (checkIt a b c d)
let check2 a b c d (x: string list) =
for xx in x do checkIt a b c d xx public static void check1(int a, int b, int c, int d, FSharpList<string> x)
{
FSharpList<string> fSharpList = x;
for (FSharpList<string> tailOrNull = fSharpList.TailOrNull; tailOrNull != null; tailOrNull = fSharpList.TailOrNull)
{
string headOrDefault = fSharpList.HeadOrDefault;
checkIt(a, b, c, d, headOrDefault);
fSharpList = tailOrNull;
}
} public static void check2(int a, int b, int c, int d, FSharpList<string> x)
{
FSharpList<string> fSharpList = x;
for (FSharpList<string> tailOrNull = fSharpList.TailOrNull; tailOrNull != null; tailOrNull = fSharpList.TailOrNull)
{
string headOrDefault = fSharpList.HeadOrDefault;
checkIt(a, b, c, d, headOrDefault);
fSharpList = tailOrNull;
}
} |
In regards to using the inlined version of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2.5% allocations in a sample is a little too much. Great change!
In these specific cases for post inference checks, using
List.iter
is causing a lot of function allocations. Can be resolved usingfor..in..do
, which is optimized forlist
meaning that it does not use the enumerator.