Jedną z rzeczy, które uwielbiam w F #, jest prawdziwe słowo kluczowe inline
. Jednak, mimo że pozwala pisać funkcje pierwszego rzędu, które wykonują to samo, co wklejone bloki kodu, rzeczy nie są tak różowe dla funkcji wyższego rzędu. RozważmyDlaczego nie można w pełni kompilować argumentów funkcji F # funkcji wyższego rzędu?
let inline add i = i+1
let inline check i = if (add i) = 0 then printfn ""
let inline iter runs f = for i = 0 to runs-1 do f i
let runs = 100000000
time(fun()->iter runs check) 1
time(fun()->for i = 0 to runs-1 do check i) 1
Wyniki są 244 ms
dla iter
i 61 ms
do kontroli manualnych. Zagłębmy się w ILSpy. Odpowiednią funkcją dla wywołania bezpośredniego jest:
internal static void [email protected](Microsoft.FSharp.Core.Unit unitVar0)
{
for (int i = 0; i < 100000000; i++)
{
if (i + 1 == 0)
{
Microsoft.FSharp.Core.PrintfFormat<Microsoft.FSharp.Core.Unit, System.IO.TextWriter, Microsoft.FSharp.Core.Unit, Microsoft.FSharp.Core.Unit> format = new Microsoft.FSharp.Core.PrintfFormat<Microsoft.FSharp.Core.Unit, System.IO.TextWriter, Microsoft.FSharp.Core.Unit, Microsoft.FSharp.Core.Unit, Microsoft.FSharp.Core.Unit>("");
Microsoft.FSharp.Core.PrintfModule.PrintFormatLineToTextWriter<Microsoft.FSharp.Core.Unit>(System.Console.Out, format);
}
}
}
Z numerem add
. Odpowiednia funkcja iter
jest
internal static void [email protected](Microsoft.FSharp.Core.Unit unitVar0)
{
for (int i = 0; i < 100000000; i++)
{
[email protected](i);
}
}
internal static void [email protected](int i)
{
if (i + 1 == 0)
{
Microsoft.FSharp.Core.PrintfFormat<Microsoft.FSharp.Core.Unit, System.IO.TextWriter, Microsoft.FSharp.Core.Unit, Microsoft.FSharp.Core.Unit> format = new Microsoft.FSharp.Core.PrintfFormat<Microsoft.FSharp.Core.Unit, System.IO.TextWriter, Microsoft.FSharp.Core.Unit, Microsoft.FSharp.Core.Unit, Microsoft.FSharp.Core.Unit>("");
Microsoft.FSharp.Core.PrintfModule.PrintFormatLineToTextWriter<Microsoft.FSharp.Core.Unit>(System.Console.Out, format);
return;
}
}
I widzimy kara wydajność pochodzi z jednego dodatkowy poziom pośredni. Jak pokazuje test wydajności, ten kierunek nie jest również usuwany przez kompilator JIT. Czy istnieje powód, dla którego funkcje wyższego rzędu nie mogą być w pełni rozwinięte? To jest problem przy pisaniu jądra obliczeniowego.
Mój czas syntezatora (choć nie bardzo istotne tutaj) jest
let inline time func n =
func() |> ignore
GC.Collect()
GC.WaitForPendingFinalizers()
let stopwatch = Stopwatch.StartNew()
for i = 0 to n-1 do func() |> ignore
stopwatch.Stop()
printfn "Took %A ms" stopwatch.Elapsed.TotalMilliseconds
Proszę potwierdzić, że uruchomiłeś ten program w trybie Release bez dołączonego debuggera. Poza tym benchmark wydaje się ważny. Można go poprawić, zwiększając pracę o współczynnik 10, aby zmniejszyć wpływ jednorazowych kosztów. – usr
@usr Tak, uruchomiłem go bez debuggera i skompilowałem w trybie Release. Nie ma wątpliwości, że różnica w wydajności jest realna, ponieważ można ją wywnioskować z kodu IL (z wyłączeniem optymalizacji JIT). – Arbil
@Abbil Połączyłem to pytanie w jednym z wątków UserVoice w F # Language Design UserVoice o inlineing analysis: https://fslang.uservoice.com/forums/245727-f-language/suggestions/6137978-better-inlining-analysis i-heurystyczne algorytmy –