F# NativePtr.stackalloc Неожиданное переполнение стека

Я продолжаю тестирование производительности F# и пытаюсь заставить работать основанные на стеке массивы. Дополнительную информацию смотрите здесь: F# NativePtr.stackalloc в Struct Constructor.

Насколько я понимаю, каждый вызов функции должен получить свой собственный кадр в стеке. Затем эта память освобождается при возврате путем перемещения указателя стека назад. Однако нижеприведенное вызывает ошибку переполнения стека - не уверен, почему, поскольку stackalloc выполняется внутри функции.

Интересно, что это происходит только в режиме выпуска, а не в режиме отладки.

Я считаю, что стандартный размер стека в dotnet составляет 1 МБ, и я не скорректировал свой. Я ожидаю, что выделение 8192 дюймов (32768 байт) не приведет к разрыву стека.

#nowarn "9"

module File1 =

    open Microsoft.FSharp.NativeInterop
    open System
    open System.Diagnostics    

    let test () =
        let stackAlloc x =
            let mutable ints:nativeptr<int> = NativePtr.stackalloc x
            ()

        let size = 8192            
        let reps = 10000
        let clock = Stopwatch()
        clock.Start()
        for i = 1 to reps do            
            stackAlloc size
        let elapsed = clock.Elapsed.TotalMilliseconds
        let description = "NativePtr.stackalloc"
        Console.WriteLine("{0} ({1} ints, {2} reps): {3:#,##0.####}ms", description, size, reps, elapsed)

    [<EntryPoint>]
    let main argv = 
        printfn "%A" argv
        test ()
        Console.ReadKey() |> ignore
        0

ОБНОВЛЕНИЕ После декомпиляции с ILSpy, как предложил Федор Сойкин, мы видим, что встраивание имело место во время оптимизации. Вроде круто, и вроде страшно!

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

[CompilationMapping(SourceConstructFlags.Module)]
public static class File1
{
    public unsafe static void test()
    {
        Stopwatch clock = new Stopwatch();
        clock.Start();
        for (int i = 1; i < 10001; i++)
        {
            IntPtr intPtr = stackalloc byte[8192 * sizeof(int)];
        }
        double elapsed = clock.Elapsed.TotalMilliseconds;
        Console.WriteLine("{0} ({1} ints, {2} reps): {3:#,##0.####}ms", "NativePtr.stackalloc", 8192, 10000, elapsed);
    }

    [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);
        File1.File1.test();
        ConsoleKeyInfo consoleKeyInfo = Console.ReadKey();
        return 0;
    }
}

В дополнение к этому, следующие могут представлять интерес:

http://www.hanselman.com/blog/ReleaseISNOTDebug64bitOptimizationsAndCMethodInliningInReleaseBuildCallStacks.aspx

Также оптимизацию можно настроить с помощью атрибутов:

https://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.methodimploptions(v=vs.110).aspx?cs-save-lang=1&cs-lang=fsharp

1 ответ

Решение

Это произойдет, если ваш stackAlloc функция была встроена, в результате чего stackalloc происходит внутри testрама. Это также объясняет, почему это происходит только в Release: встраивание - это своего рода оптимизация, которая будет выполняться гораздо менее агрессивно в Debug, чем в Release.

Чтобы подтвердить это, я бы попробовал взглянуть на ваш полученный код с помощью ILSpy.

Зачем вам в первую очередь использовать выделенные в стеке массивы? Это похоже на то, о чем нас предупреждал Дональд Кнут.:-)

Другие вопросы по тегам