Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/backbone.js/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
.net Mono下的堆栈大小_.net_Recursion_F#_Mono - Fatal编程技术网

.net Mono下的堆栈大小

.net Mono下的堆栈大小,.net,recursion,f#,mono,.net,Recursion,F#,Mono,我已经编写了一个很小的递归F#代码,看看在.NET/Mono下,我可以在堆栈中容纳多少级别的递归。只要它是2的精确幂,它就会打印递归深度,所以我会在因子2内找到最大深度 我使用System.Threading.thread(ThreadStart,int)在具有定义堆栈空间的线程中启动代码。在.Net下,每级递归大约需要100字节,我可以在2G堆栈上获得大约1600万级。Mono下的内存使用情况大致相似,但我只能获得大约3万个级别。将传递给Threadpass的堆栈大小值增加约600000不会增

我已经编写了一个很小的递归F#代码,看看在.NET/Mono下,我可以在堆栈中容纳多少级别的递归。只要它是2的精确幂,它就会打印递归深度,所以我会在因子2内找到最大深度

我使用
System.Threading.thread(ThreadStart,int)
在具有定义堆栈空间的线程中启动代码。在.Net下,每级递归大约需要100字节,我可以在2G堆栈上获得大约1600万级。Mono下的内存使用情况大致相似,但我只能获得大约3万个级别。将传递给
Thread
pass的堆栈大小值增加约
600000
不会增加递归深度

ulimit
报告堆栈大小限制为1G

一个明显的解释是,如果Mono太大,它将不遵守
Thread
的第二个参数。有人知道如何说服Mono分配一个大堆栈吗

代码很琐碎,但下面是以防万一,以防有人关心:

let rec f i =
    if popcount i = 1 then // population count is one on exact powers of 2
        printf "Got up to %d\n" i
        stdout.Flush ()
    if i = 1000000000 then 0 else 1 + f (i+1)
选项1:更改单声道堆栈大小 一个明显的解释是,如果Mono太大,它将不遵守
Thread
的第二个参数。有人知道如何说服Mono分配一个大堆栈吗

您认为Mono将限制堆栈大小是正确的,即使您传入了一个较大的值。例如,在我的Cent OS 64位测试机上,Mono将分配的最大堆栈大小为2 MB。显示了创建单线程时发生的情况:

公共线程(ThreadStart,int-maxStackSize) { if(start==null) 抛出新的ArgumentNullException(“开始”); threadstart=start; Internal.stack_size=CheckStackSize(maxStackSize); } 静态int CheckStackSize(int maxStackSize) { 如果(maxStackSize<0) 抛出新ArgumentOutOfRangeException(“小于零”、“maxStackSize”); if(maxStackSize<131072)//确保堆栈至少有128k大 返回131072; int page_size=Environment.GetPageSize(); if((maxStackSize%page\u size)!=0)//四舍五入到页面大小的可整除部分 maxStackSize=(maxStackSize/(页面大小-1))*页面大小; int default_stack_size=(IntPtr.size/4)*1024*1024;//来自wthreads.c 如果(maxStackSize>默认堆栈大小) 返回默认的堆栈大小; 返回最大堆栈大小; } 上面的代码对堆栈大小进行了严格限制

理论上,您可以更改上述一个或两个函数(粗体行)中的代码,以便分配更大的堆栈大小。一旦您这样做了,您就必须构建Mono运行时,然后运行您的函数,看看更改是否会产生影响

我应该强调的是,我对Mono的了解还不够,无法理解分配更大的堆栈是否有助于您的具体情况。我只会在万不得已的情况下这样做(如果我的其他答案都不起作用的话)。

选项2:F#Tail calls 一种选择是重写方法,以便使用递归。从先前的(维基百科)链接:

尾部递归函数是递归的一种特殊情况,其中在方法中执行的最后一条指令是递归调用。F#和许多其他函数语言可以优化尾部递归函数;由于递归调用后没有执行额外的工作,因此函数不需要记住它来自何处,因此没有理由在堆栈上分配额外的内存

F#通过告诉CLR在执行目标函数之前删除当前堆栈帧来优化尾部递归函数。因此,尾部递归函数可以无限递归,而不消耗堆栈空间

有一个警告-Mono不完全支持尾部递归调用-您应该首先在.Net运行时中进行测试,然后查看代码是否将在Mono中运行

下面是使用尾部递归调用重新编写的示例函数-它在.NET(使用Visual Studio 2010)和Mono(Mono版本3.2.0,F#3.0)中都可以工作:

输出:

Got up to 10000
Got up to 20000
Got up to 30000
...
Got up to 999980000
Got up to 999990000
Got up to 1000000000
result 999999999
Got up to 1000000000
result 2
Got up to 10000
Got up to 20000
Got up to 30000
...
Got up to 999980000
Got up to 999990000
Got up to 1000000000
result 999999999
Got up to 1000000000
result 2
对于输入值9999999 8:

let result = f 999999998
printf "result %d\n" result
let result = f 999999998
printf "result %d\n" result
输出:

Got up to 10000
Got up to 20000
Got up to 30000
...
Got up to 999980000
Got up to 999990000
Got up to 1000000000
result 999999999
Got up to 1000000000
result 2
Got up to 10000
Got up to 20000
Got up to 30000
...
Got up to 999980000
Got up to 999990000
Got up to 1000000000
result 999999999
Got up to 1000000000
result 2
上面的代码使用两个变量来跟踪进度:

acc
-存储计算结果的累加器

计数器
-只是递归调用的数量

为什么示例代码不是尾部递归的?

改写维基百科文章的一节,我们可以重写这行代码:

作为

尾部递归的定义就是在递归调用之后不能有额外的操作。正如我们在代码的重写版本中所看到的,生成结果需要额外的操作

资源

  • 在MSDN
  • 在维基百科
  • -在Mono中不起作用的尾部调用示例
  • 在Mono bug跟踪器中
选项3:使其迭代 一种选择是重写方法,使其不是递归的,而是迭代的。也就是说,您可以更改方法,使其成为计算结果的循环:

let f i =
    let mutable acc = 0
    for counter in i .. 1000000000 do
        // display progress every 10k iterations
        let j = counter % 10000
        if j = 0 then
            printf "Got up to %d\n" counter
            stdout.Flush ()

        if counter < 1000000000 then
            acc <- acc + 1
    acc
输出:

Got up to 10000
Got up to 20000
Got up to 30000
...
Got up to 999980000
Got up to 999990000
Got up to 1000000000
result 999999999
Got up to 1000000000
result 2
Got up to 10000
Got up to 20000
Got up to 30000
...
Got up to 999980000
Got up to 999990000
Got up to 1000000000
result 999999999
Got up to 1000000000
result 2
对于输入值9999999 8:

let result = f 999999998
printf "result %d\n" result
let result = f 999999998
printf "result %d\n" result
输出:

Got up to 10000
Got up to 20000
Got up to 30000
...
Got up to 999980000
Got up to 999990000
Got up to 1000000000
result 999999999
Got up to 1000000000
result 2
Got up to 10000
Got up to 20000
Got up to 30000
...
Got up to 999980000
Got up to 999990000
Got up to 1000000000
result 999999999
Got up to 1000000000
result 2
上面的代码使用两个变量来跟踪进度:

acc
-存储计算结果的累加器

计数器
-只是迭代调用的数量(循环的数量)


由于我们使用循环来计算结果,因此没有分配任何额外的堆栈。

此代码绕过了mono限制。它对于dfs在竞争性编程中非常有用

        public static void IncreaseStack(ThreadStart action, int stackSize = 16000000)
        {
            var thread = new Thread(action, stackSize);
#if __MonoCS__
        const BindingFlags bf = BindingFlags.NonPublic | BindingFlags.Instance;
        var it = typeof(Thread).GetField("internal_thread", bf).GetValue(thread);
        it.GetType().GetField("stack_size", bf).SetValue(it, stackSize);
#endif
            thread.Start();
            thread.Join();
        }

非常感谢。然而,问题是(正如你所观察到的)Mono并不总是在应该的时候进行尾部调用。我的代码已经是尾部递归的,并且在.net下运行良好。但是Mono在同一个代码上很快就耗尽了堆栈