在 C++ 中将调用堆栈扩展到磁盘?

Posted

技术标签:

【中文标题】在 C++ 中将调用堆栈扩展到磁盘?【英文标题】:Extend call stack to disk in C++? 【发布时间】:2016-06-30 17:27:41 【问题描述】:

当涉及到大规模递归方法调用时,必须通过修改适当的编译器参数来扩展调用堆栈大小以避免堆栈溢出。

让我们考虑编写一个可移植的应用程序,它的布局足够简单,用户只需要具备最少的技术知识,因此手动配置虚拟内存是不可能的。

运行大规模递归方法(显然是在幕后)可能会导致超出调用堆栈限制,尤其是在运行应用程序的机器内存有限的情况下。

闲聊就够了:在 C++ 中,如果内存(几乎)已满,是否可以手动将调用堆栈扩展到磁盘?

【问题讨论】:

不,这是不可能的。不递归地重写。 把递归变成迭代,问题解决了。 不,您也不能将调用堆栈扩展到“云端”。 您不必忍受固定大小的调用堆栈。请参阅***.com/a/1053159/120163 你肯定不想推送到磁盘,那里的访问时间从 nS 到毫秒,这会让你减慢 1000 倍。 gcc 不是已经支持 linux 上的碎片化堆栈了吗?在这种情况下,解决方案就是使用现代版本的 gcc。 【参考方案1】:

这几乎是不可能的。

使用协程库。这样,您就可以从堆中分配自己的堆栈。重构你的代码以跟踪它在调用堆栈中的深度,当它变得危险的深度时,创建一个新的协同线程并切换到它。当堆内存用完时,冻结旧的共线程并释放它们的内存。当然,你最好确保将它们解冻到同一个地址——所以我建议你自己将它们的堆栈分配到你可以控制的自己的竞技场之外。事实上,为协线程堆栈重用同一块内存并一次将它们换入和换出一个可能更容易。

将算法重写为非递归当然更容易。

这可能是它工作的一个例子,或者它可能只是在意外时打印正确的答案:

#include <stdio.h>
#include "libco.h"

//byuu's libco has been modified to use a provided stack; it's a simple mod, but needs to be done per platform
//x86.c:
////if(handle = (cothread_t)malloc(size)) 
//handle = (cothread_t)stack;

//here we're going to have a stack on disk and have one recursion's stack in RAM at a time
//I think it may be impossible to do this without a main thread controlling the coroutines, but I'm not sure.

#define STACKSIZE (32*1024)
char stack[STACKSIZE];

FILE* fpInfiniteStack;
cothread_t co_mothership;

#define RECURSING 0
#define EXITING 1
int disposition;

volatile int recurse_level;

int call_in_cothread( int (*entrypoint)(int), int arg);

int fibo_b(int n);
int fibo(int n)

    if(n==0)
        return 0;
    else if(n==1) 
        return 1;
    else 
        int a = call_in_cothread(fibo,n-1);
        int b = call_in_cothread(fibo_b,n-2);
        return a+b;
    

int fibo_b(int n)  printf("fibo_b\n"); return fibo(n);  //just to make sure we can call more than one function

long filesize;
void freeze()

    fwrite(stack,1,STACKSIZE,fpInfiniteStack);
    fflush(fpInfiniteStack);
    filesize += STACKSIZE;

void unfreeze()

    fseek(fpInfiniteStack,filesize-STACKSIZE,SEEK_SET);
    int read = fread(stack,1,STACKSIZE,fpInfiniteStack);
    filesize -= STACKSIZE;
    fseek(fpInfiniteStack,filesize,SEEK_SET);


struct 

    int (*proc)(int);
    int arg;
 thunk, todo;

void cothunk()

    thunk.arg = thunk.proc(thunk.arg);
    disposition = EXITING;
    co_switch(co_mothership);


int call_in_cothread(int (*proc)(int), int arg)

    if(co_active() != co_mothership)
    
        todo.proc = proc;
        todo.arg = arg;

        disposition = RECURSING;
        co_switch(co_mothership);
        //we land here after unfreezing. the work we wanted to do has already been done.
        return thunk.arg;
    

NEXT_RECURSE:
    thunk.proc = proc;
    thunk.arg = arg;
    cothread_t co = co_create(stack,STACKSIZE,cothunk);
    recurse_level++;
NEXT_EXIT:
    co_switch(co);

    if(disposition == RECURSING)
    
        freeze();
        proc = todo.proc;
        arg = todo.arg;
        goto NEXT_RECURSE;
    
    else
    
        recurse_level--;
        unfreeze();
        if(recurse_level==0)
            return thunk.arg; //return from initial level of recurstion
        goto NEXT_EXIT;
    

    return -666; //this should not be possible


int main(int argc, char**argv)

    fpInfiniteStack = fopen("infinite.stack","w+b");
    co_mothership = co_active();
    printf("result: %d\n",call_in_cothread(fibo,10));

现在您只需要检测系统中有多少内存,有多少可用,调用堆栈有多大,以及调用堆栈何时耗尽,因此您知道何时部署无限堆栈。对于一个系统来说,这不是一件容易的事,更不用说便携了。最好了解堆栈的实际用途而不是与之抗争。

【讨论】:

很好的例子,很有帮助! :)【参考方案2】:

这是可行的。 您需要一些汇编来操作堆栈指针,因为没有直接从 C++ 访问它的标准化方法(据我所知)。到达那里后,您可以指向您的内存页面并负责交换内存和换出内存。已经有图书馆在为你做这件事。

另一方面,如果系统提供商认为分页内存或其他虚拟内存技术在平台上不起作用/不值得,他们可能有一个很好的理由(很可能它会非常慢)。尝试让您的解决方案在没有递归的情况下工作,或者更改它以使递归适合可用的内容。即使是效率较低的实现最终也会比磁盘分页递归更快。

【讨论】:

以上是关于在 C++ 中将调用堆栈扩展到磁盘?的主要内容,如果未能解决你的问题,请参考以下文章

CentOS7 扩展磁盘容量(虚拟机及物理机同理)

PHP扩展调用C++静态库

[ZT]C++ 扩展和嵌入 Python

js-bson:使用纯JS版本加载c++ bson扩展失败

Window server2012R2 在线扩展卷

使用扩展参数调用 Delphi 函数时出现 C++ 错误