簡體   English   中英

F#NativePtr.stackalloc慢於C#stackalloc - 包含反編譯代碼

[英]F# NativePtr.stackalloc slower then C# stackalloc - Decompiled Code Included

繼續我的F#性能測試。 有關更多背景,請參閱此處:

結構構造函數中的f#NativePtr.stackalloc

F#NativePtr.stackalloc意外的堆棧溢出

現在我已經在F#中使用堆棧數組了。 但是,由於某種原因,等效的C#快約50倍。 我在下面列出了ILSpy反編譯版本,看起來只有1行真的不同(在stackAlloc里面)。

這里發生了什么? 未經檢查的算術真的對這個巨大的差異負責嗎? 不知道我怎么測試這個?

https://msdn.microsoft.com/en-us/library/a569z7k8.aspx

F#代碼

#nowarn "9"

open Microsoft.FSharp.NativeInterop
open System
open System.Diagnostics    
open System.Runtime.CompilerServices        

[<MethodImpl(MethodImplOptions.NoInlining)>]
let stackAlloc x =
    let mutable ints:nativeptr<byte> = NativePtr.stackalloc x
    ()   

[<EntryPoint>]
let main argv = 
    printfn "%A" argv

    let size = 8192            
    let reps = 10000

    stackAlloc size // JIT
    let clock = Stopwatch()
    clock.Start()
    for i = 1 to reps do            
        stackAlloc size
    clock.Stop()

    let elapsed = clock.Elapsed.TotalMilliseconds
    let description = "F# NativePtr.stackalloc"
    Console.WriteLine("{0} ({1} bytes, {2} reps): {3:#,##0.####}ms", description, size, reps, elapsed)

    Console.ReadKey() |> ignore
    0

C#代碼

using System;
using System.Diagnostics;

namespace CSharpLanguageFeatures
{
    class CSharpStackArray
    {
        static void Main(string[] args)
        {
            int size = 8192;
            int reps = 10000;

            stackAlloc(size); // JIT
            Stopwatch clock = new Stopwatch();
            clock.Start();
            for (int i = 0; i < reps; i++)
            {
                stackAlloc(size);
            }
            clock.Stop();

            string elapsed = clock.Elapsed.TotalMilliseconds.ToString("#,##0.####");
            string description = "C# stackalloc";
            Console.WriteLine("{0} ({1} bytes, {2} reps): {3:#,##0.####}ms", description, size, reps, elapsed);
            Console.ReadKey();
        }

        public unsafe static void stackAlloc(int arraySize)
        {
            byte* pArr = stackalloc byte[arraySize];
        }
    }
}

F#版本已反編譯

using Microsoft.FSharp.Core;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;

[CompilationMapping(SourceConstructFlags.Module)]
public static class FSharpStackArray
{
    [MethodImpl(MethodImplOptions.NoInlining)]
    public unsafe static void stackAlloc(int x)
    {
        IntPtr ints = stackalloc byte[x * sizeof(byte)];
    }

    [EntryPoint]
    public static int main(string[] argv)
    {
        PrintfFormat<FSharpFunc<string[], Unit>, TextWriter, Unit, Unit> format = new PrintfFormat<FSharpFunc<string[], Unit>, TextWriter, Unit, Unit, string[]>("%A");
        PrintfModule.PrintFormatLineToTextWriter<FSharpFunc<string[], Unit>>(Console.Out, format).Invoke(argv);
        FSharpStackArray.stackAlloc(8192);
        Stopwatch clock = new Stopwatch();
        clock.Start();
        for (int i = 1; i < 10001; i++)
        {
            FSharpStackArray.stackAlloc(8192);
        }
        clock.Stop();
        double elapsed = clock.Elapsed.TotalMilliseconds;
        Console.WriteLine("{0} ({1} bytes, {2} reps): {3:#,##0.####}ms", "F# NativePtr.stackalloc", 8192, 10000, elapsed);
        ConsoleKeyInfo consoleKeyInfo = Console.ReadKey();
        return 0;
    }
}

C#版本已反編譯

using System;
using System.Diagnostics;

namespace CSharpLanguageFeatures
{
    internal class CSharpStackArray
    {
        private static void Main(string[] args)
        {
            int size = 8192;
            int reps = 10000;
            CSharpStackArray.stackAlloc(size);
            Stopwatch clock = new Stopwatch();
            clock.Start();
            for (int i = 0; i < reps; i++)
            {
                CSharpStackArray.stackAlloc(size);
            }
            clock.Stop();
            string elapsed = clock.Elapsed.TotalMilliseconds.ToString("#,##0.####");
            string description = "C# stackalloc";
            Console.WriteLine("{0} ({1} bytes, {2} reps): {3:#,##0.####}ms", new object[]
            {
                description,
                size,
                reps,
                elapsed
            });
            Console.ReadKey();
        }

        public unsafe static void stackAlloc(int arraySize)
        {
            IntPtr arg_06_0 = stackalloc byte[checked(unchecked((UIntPtr)arraySize) * 1)];
        }
    }
}

F#版本IL - 字節分配

.method public static 
    void stackAlloc (
        int32 x
    ) cil managed noinlining 
{
    // Method begins at RVA 0x2050
    // Code size 13 (0xd)
    .maxstack 4
    .locals init (
        [0] native int ints
    )

    IL_0000: nop
    IL_0001: ldarg.0
    IL_0002: sizeof [mscorlib]System.Byte
    IL_0008: mul
    IL_0009: localloc
    IL_000b: stloc.0
    IL_000c: ret
} // end of method FSharpStackArray::stackAlloc

C#版本IL - 字節分配

.method public hidebysig static 
    void stackAlloc (
        int32 arraySize
    ) cil managed 
{
    // Method begins at RVA 0x2094
    // Code size 8 (0x8)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: conv.u
    IL_0002: ldc.i4.1
    IL_0003: mul.ovf.un
    IL_0004: localloc
    IL_0006: pop
    IL_0007: ret
} // end of method CSharpStackArray::stackAlloc   

更新了F#IL - IntPtr分配

.method public static 
    void stackAlloc (
        int32 x
    ) cil managed noinlining 
{
    // Method begins at RVA 0x2050
    // Code size 13 (0xd)
    .maxstack 4
    .locals init (
        [0] native int ints
    )

    IL_0000: nop
    IL_0001: ldarg.0
    IL_0002: sizeof [mscorlib]System.IntPtr
    IL_0008: mul
    IL_0009: localloc
    IL_000b: stloc.0
    IL_000c: ret
} // end of method FSharpStackArray::stackAlloc

更新了C#IL - IntPtr分配

.method public hidebysig static 
    void stackAlloc (
        int32 arraySize
    ) cil managed 
{
    // Method begins at RVA 0x2415
    // Code size 13 (0xd)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: conv.u
    IL_0002: sizeof [mscorlib]System.IntPtr
    IL_0008: mul.ovf.un
    IL_0009: localloc
    IL_000b: pop
    IL_000c: ret
} // end of method CSharpStackArray::stackAlloc

謝謝大家的幫助。

答案是C#編譯器沒有將指針存儲為本地。 這是因為永遠不需要分配的內存。 缺乏“sizeof”和不同的“mul”給了C#另一個微小的優勢。

F#匯編程序 - 評論差異

.method public static 
    void stackAlloc (
        int32 x
    ) cil managed noinlining 
{
    // Method begins at RVA 0x2050
    // Code size 13 (0xd)
    .maxstack 4
    .locals init ( //***** Not in C# Version *****//
        [0] native int ints
    )

    IL_0000: nop
    IL_0001: ldarg.0
    IL_0002: sizeof [mscorlib]System.Byte //***** C# just uses "1" *****//
    IL_0008: mul //***** C# uses "mul.ovf.un" *****//
    IL_0009: localloc
    IL_000b: stloc.0 //***** Not in C# Version *****//
    IL_000c: ret
} // end of method FSharpStackArray::stackAlloc

C#匯編程序 - 評論差異

.method public hidebysig static 
    void stackAlloc (
        int32 arraySize
    ) cil managed 
{
    // Method begins at RVA 0x2094
    // Code size 8 (0x8)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: conv.u
    IL_0002: ldc.i4.1 //***** F# uses sizeof [mscorlib]System.Byte *****//
    IL_0003: mul.ovf.un //***** F# uses "mul" *****//
    IL_0004: localloc
    IL_0006: pop
    IL_0007: ret
} // end of method CSharpStackArray::stackAlloc  

這個練習教會了我一些東西:

  1. 編譯器執行了很多優化。 顯然,不同語言中相同的高級代碼可能會產生完全不同的機器指令集。
  2. 在對dotnet語言進行基准測試時,您可以閱讀Intermediate Assembly以了解最新情況。 使用ILSpy進行此操作。
  3. 您可以使用ilasm.exe修改和編譯Intermediate Assembly。
  4. C#編譯器在這里刪除不必要的代碼做得更好。 設置分配的內存中的每個字節后,性能將與最初預期的非常相似。

最終的F#代碼

#nowarn "9"

open Microsoft.FSharp.NativeInterop
open System
open System.Diagnostics    
open System.Runtime.CompilerServices        

[<MethodImpl(MethodImplOptions.NoInlining)>]
let stackAlloc x =
    let mutable bytes:nativeptr<byte> = NativePtr.stackalloc x
    for i = 0 to (x - 1) do
        NativePtr.set bytes i (byte i)
    ()   

[<EntryPoint>]
let main argv = 
    printfn "%A" argv

    let size = 8192            
    let reps = 10000

    stackAlloc size // JIT
    let clock = Stopwatch()
    clock.Start()
    for i = 1 to reps do            
        stackAlloc size
    clock.Stop()

    let elapsed = clock.Elapsed.TotalMilliseconds
    let description = "F# NativePtr.stackalloc"
    Console.WriteLine("{0} ({1} bytes, {2} reps): {3:#,##0.####}ms", description, size, reps, elapsed)

    Console.ReadKey() |> ignore
    0

最終的C#代碼

using System;
using System.Diagnostics;

namespace CSharpStackArray
{
    class Program
    {
        static void Main(string[] args)
        {
            int size = 8192;
            int reps = 10000;

            stackAlloc(size); // JIT
            Stopwatch clock = new Stopwatch();
            clock.Start();
            for (int i = 0; i < reps; i++)
            {
                stackAlloc(size);
            }
            clock.Stop();

            string elapsed = clock.Elapsed.TotalMilliseconds.ToString("#,##0.####");
            string description = "C# stackalloc";
            Console.WriteLine("{0} ({1} bytes, {2} reps): {3:#,##0.####}ms", description, size, reps, elapsed);
            Console.ReadKey();
        }

        public unsafe static void stackAlloc(int arraySize)
        {
            byte* pArr = stackalloc byte[arraySize];
            for (int i = 0; i < arraySize; i++)
            {
                pArr[i] = (byte)i;
            }
        }
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM