2016-02-29 3 views
2

Продолжение моего тестирования производительности F #. Для некоторых более фоне смотрите здесь:F # NativePtr.stackalloc медленнее, чем C# stackalloc - Декомпилированный код включен

f# NativePtr.stackalloc in Struct Constructor

F# NativePtr.stackalloc Unexpected Stack Overflow

Теперь у меня есть стек массивов, работающих в F #. Однако по какой-то причине эквивалентный C# примерно в 50 раз быстрее. Я включил декомпилированные версии ILSpy ниже, и кажется, что только одна строка действительно отличается (внутри 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 # Version декомпилированные

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# Version декомпилированные

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 # Version 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# Version 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 
+2

Можете ли вы показать фактический IL обоих 'методов stackAlloc'? –

+0

Хммм, возможно, размер [mscorlib] System.Byte или nop? В петле тоже есть nop? – Researcher

+0

'sizeof' определенно играет роль, но я думаю, что гораздо более значительным является' mul'. –

ответ

2

Спасибо всем за помощь в этом.

Ответ был тот, что компилятор 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. Составители выполнить lo т оптимизации. Очевидно, идентичный код высокого уровня на разных языках может привести к совершенно различным наборам машинных инструкций.
  2. При тестировании точечных языков вы можете прочитать промежуточную сборку, чтобы действительно увидеть, что происходит. Для этого используйте ILSpy.
  3. Вы можете изменить и скомпилировать промежуточную сборку с помощью файла ilasm.exe.
  4. Компилятор C# делает здесь лучшую работу по удалению ненужного кода. После того, как вы установите каждый байт в выделенной памяти, производительность становится очень похожей, как ожидалось первоначально.

Final 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 

Final 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; 
      } 
     } 
    } 
}