在 C++ 中将调用堆栈扩展到磁盘?
Extend call stack to disk in C++?
当涉及到大规模递归方法调用时,必须通过修改适当的编译器参数来扩展调用堆栈大小,以避免堆栈溢出。
让我们考虑编写一个布局足够简单的便携式应用程序,以便其用户只需要具备最少的技术知识,因此手动配置虚拟内存是不可能的。
运行 大规模递归方法(显然在幕后)可能会导致超过调用堆栈限制,特别是如果 运行 应用程序所在的机器内存有限。
聊够了: 在 C++ 中,是否可以手动将调用堆栈扩展到磁盘以防内存(几乎)已满?
这可能只是勉强可能。
使用协程库。这样,您就可以从堆中分配自己的堆栈。重组您的代码以跟踪它在调用堆栈中的深度,当它变得危险时,创建一个新的 cothread 并切换到它。当您 运行 堆内存不足时,冻结旧的共线程并释放它们的内存。当然,你最好确保将它们解冻到同一个地址——所以我建议你自己将它们的堆栈分配到你自己可以控制的区域之外。事实上,为 cothread 堆栈重复使用同一块内存并一次将它们换入换出可能更容易。
将算法重写为非递归当然更容易。
这可能是它工作的一个例子,或者它可能只是偶然打印出正确答案:
#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));
}
现在您只需要检测系统中有多少内存、可用内存有多少、调用堆栈有多大以及调用堆栈何时耗尽,这样您就知道何时部署无限堆栈。对于一个系统来说,这不是一件容易的事,更不用说便携地做到这一点了。最好了解堆栈的实际用途而不是对抗它。
可行。
您需要一些程序集来操作堆栈指针,因为没有直接从 C++ 访问它的标准化方法(据我所知)。到达那里后,您可以指向您的内存页面并负责内存的换入和换出。已经有图书馆在为您做这件事。
另一方面,如果系统提供商认为分页内存或其他虚拟内存技术在平台上没有 work/be 价值,他们可能有很好的理由(很可能会非常慢) .尝试让您的解决方案在没有递归的情况下工作,或者更改它以使递归适合可用的内容。即使效率较低的实现最终也会比磁盘分页递归更快。
当涉及到大规模递归方法调用时,必须通过修改适当的编译器参数来扩展调用堆栈大小,以避免堆栈溢出。
让我们考虑编写一个布局足够简单的便携式应用程序,以便其用户只需要具备最少的技术知识,因此手动配置虚拟内存是不可能的。
运行 大规模递归方法(显然在幕后)可能会导致超过调用堆栈限制,特别是如果 运行 应用程序所在的机器内存有限。
聊够了: 在 C++ 中,是否可以手动将调用堆栈扩展到磁盘以防内存(几乎)已满?
这可能只是勉强可能。
使用协程库。这样,您就可以从堆中分配自己的堆栈。重组您的代码以跟踪它在调用堆栈中的深度,当它变得危险时,创建一个新的 cothread 并切换到它。当您 运行 堆内存不足时,冻结旧的共线程并释放它们的内存。当然,你最好确保将它们解冻到同一个地址——所以我建议你自己将它们的堆栈分配到你自己可以控制的区域之外。事实上,为 cothread 堆栈重复使用同一块内存并一次将它们换入换出可能更容易。
将算法重写为非递归当然更容易。
这可能是它工作的一个例子,或者它可能只是偶然打印出正确答案:
#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));
}
现在您只需要检测系统中有多少内存、可用内存有多少、调用堆栈有多大以及调用堆栈何时耗尽,这样您就知道何时部署无限堆栈。对于一个系统来说,这不是一件容易的事,更不用说便携地做到这一点了。最好了解堆栈的实际用途而不是对抗它。
可行。 您需要一些程序集来操作堆栈指针,因为没有直接从 C++ 访问它的标准化方法(据我所知)。到达那里后,您可以指向您的内存页面并负责内存的换入和换出。已经有图书馆在为您做这件事。
另一方面,如果系统提供商认为分页内存或其他虚拟内存技术在平台上没有 work/be 价值,他们可能有很好的理由(很可能会非常慢) .尝试让您的解决方案在没有递归的情况下工作,或者更改它以使递归适合可用的内容。即使效率较低的实现最终也会比磁盘分页递归更快。