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;
}
}
В дополнение к этому, следующие могут представлять интерес:
Также оптимизацию можно настроить с помощью атрибутов:
1 ответ
Это произойдет, если ваш stackAlloc
функция была встроена, в результате чего stackalloc происходит внутри test
рама. Это также объясняет, почему это происходит только в Release: встраивание - это своего рода оптимизация, которая будет выполняться гораздо менее агрессивно в Debug, чем в Release.
Чтобы подтвердить это, я бы попробовал взглянуть на ваш полученный код с помощью ILSpy.
Зачем вам в первую очередь использовать выделенные в стеке массивы? Это похоже на то, о чем нас предупреждал Дональд Кнут.:-)