我正在编写一个使用边界体积层次结构的小型光线跟踪器来加速光线跟踪。 长话短说,我有一个二叉树,我可能需要访问多个叶子。
当前我有一个节点,左右两个子节点,然后在travel()期间如果有一些条件,在这个例子中是intersect(),则访问子节点:
class BoundingBoxNode{
BoundingBoxNode* left, *right;
void travel(param &p);
inline bool intersect(param &p){...};
};
void BoundingBoxNode::travel(param &p){
if(this->intersect(p)){
if(left)
left->travel(p);
if(right)
right->travel(p);
}
}
这种方法使用递归方法调用,但是,我需要尽可能地优化这段代码......而根据IA-32的优化参考手册,16以上的函数调用可能非常昂贵,所以我想使用while循环而不是递归调用来执行此操作。 但我不希望进行动态堆分配,因为这些分配很昂贵。所以我在想,也许我可以使用这样一个事实:每次while循环开始在堆栈上都会处于相同的位置。 在下面非常丑陋的黑客中,我依靠alloca()来总是分配相同的地址:
class BoundingBoxNode{
BoundingBoxNode* left, right;
inline void travel(param &p){
int stack_size = 0;
BoundingBoxNode* current = this;
while(stack_size >= 0){
BoundingBoxNode* stack = alloca(stack_size * 4 + 2*4);
if(current->intersect(p)){
if(current->left){
stack[stack_size] = current->left;
stack_size++;
}
if(current->right){
stack[stack_size] = current->right;
stack_size++;
}
}
stack_size--;
current = stack[stack_size];
}
};
inline bool intersect(param &p){...};
};
然而令人惊讶的是,这种方法似乎确实失败了:) 但是只要堆栈小于4或5就可以工作......我也非常有信心这种方法是可行的,我只是觉得我需要一些帮助才能正确实现它。
那么如何从C ++手动操作堆栈,是否可以使用一些编译器扩展...或者我必须这样做是汇编程序,如果是这样,我如何编写汇编程序而不是用两者编译GCC和ICC。
我希望有人可以帮助我......我不需要一个完美的解决方案,只需要一个黑客,如果它能够正常运行它就足够了:)
关于Jonas Finnemann Jensen
答案 0 :(得分:4)
所以,你有一个你想要转换为循环的递归函数。你正确地知道你的函数不是尾调用,所以你必须用堆栈来实现它。
现在,您为什么担心分配“临时空间”堆栈的次数?这不是每次遍历一次吗? - 如果没有,则将临时区域传递给遍历函数本身,这样就可以分配一次,然后重新用于每次遍历。
如果堆栈足够小以适应缓存,那么它将保持热度并且它不在真正的C ++堆栈上并不重要。
一旦你完成了所有的配置文件,它会两种方式,看看它是否有所不同 - 保持更快的版本。
答案 1 :(得分:2)
无法调整堆栈分配的大小。
在您的示例中,除了调用堆栈本身之外,您需要分配哪些数据并不是很明显。您基本上可以将当前路径保存在预先分配到最大深度的矢量中。循环变得丑陋,但这就是生活......
如果您需要许多可以作为整体发布的小分配(在算法完成后),请使用连续池进行分配。
如果知道所需内存的上边界,则分配只是一个指针增量:
class CPool
{
std::vector<char> m_data;
size_t m_head;
public:
CPool(size_t size) : m_data(size()), m_head(0) {}
void * Alloc(size_t size)
{
if (m_data.size() - head < size)
throw bad_alloc();
void * result = &(m_data[m_head]);
m_head += size;
return result;
}
void Free(void * p) {} // free is free ;)
};
如果您没有总大小的上限,请使用“绳索上的池” - 即当大块内存耗尽时,获取一个新内存,并将这些块放在列表中。
答案 2 :(得分:1)
您不需要 堆栈,只需要一个堆栈。如果我查看您的代码,您可以使用std::stack<BoundingBoxNode* >
。
答案 3 :(得分:0)
C ++标准没有提供操作堆栈的方法 - 它甚至不需要堆栈。您是否实际使用动态分配来衡量代码的性能?
答案 4 :(得分:0)
由于alloca分配是累积的,我建议你先做一个alloca来存储“this”指针,从而成为堆栈的“基础”,跟踪堆栈可容纳的元素数量,并仅分配所需的大小:
inline void travel(param &p){
BoundingBoxNode* stack = alloca(sizeof(BoundingBoxNode*)*3);
int stack_size = 3, stack_idx = 0;
stack[stk_idx] = this;
do {
BoundingBoxNode* current = stack[stk_idx];
if( current->intersect(p)){
int need = current->left ? ( current->right ? 2 : 1 ) : 0;
if ( stack-size - stk_idx < need ) {
// Let us simplify and always allocate enough for two
alloca(sizeof(BoundingBoxNode*)*2);
stack_size += 2;
}
if(current->left){
stack[stack_idx++] = current->left;
}
if(current->right){
stack[stack_idx++] = current->right;
}
}
stack_idx--;
} while(stack_idx > 0)
};
答案 5 :(得分:0)
它适用于小堆栈大小的事实可能是巧合。您必须维护多个堆栈并在它们之间进行复制。你永远不能保证对alloca的连续调用将返回相同的地址。
最佳方法可能是堆栈的固定大小,有一个断言来捕获溢出。或者您可以根据构造中的树深度确定最大堆栈大小,并动态分配将用于每次遍历的堆栈...假设您至少没有在多个线程上遍历。
答案 6 :(得分:0)
从你的问题来看,似乎还有很多东西需要学习。
最重要的是要学习:如果没有先测量运行时执行情况并分析结果以确定性能瓶颈的确切位置,就不要假设性能。
函数'alloca'从堆栈中分配一块内存,堆栈大小增加(通过移动堆栈指针)。每次调用'alloca'都会创建一个新的内存块,直到你的堆栈空间不足,它不会重新使用以前分配的内存,当你分配另一块内存时,'stack'指向的数据会丢失。将它分配给'堆栈'。这是内存泄漏。在这种情况下,当函数退出时会自动释放内存,因此它不是严重泄漏,但是,您丢失了数据。
我会单独留下“IA-32优化参考手册”。它假设您确切知道CPU的工作原理。让编译器担心优化它会为你正在做的事做得足够好 - 编译器编写者希望知道内部的引用。对于现代PC,性能的常见瓶颈通常是内存带宽。
我认为'16深度'函数调用昂贵与CPU如何管理其堆栈有关,并且仅作为指导。 CPU将堆栈的顶部保留在板载高速缓存中,当高速缓存已满时,堆栈的底部被分页到RAM,这是性能开始降低的地方。具有大量参数的函数不会像没有参数的函数那样深入嵌套。它不仅仅是函数调用,还有本地变量和使用alloca分配的内存。实际上,使用alloca可能会受到性能影响,因为CPU将被设计为针对常见用例优化其堆栈 - 一些参数和一些局部变量。偏离常见情况,性能下降。
尝试使用std :: stack,如上面建议的MSalters。让它工作。
答案 7 :(得分:0)
使用C ++数据结构。毕竟你正在使用C ++。一个std :: vector&lt;&gt;可以预先分配成块的摊销成本几乎为零。它也是安全的(因为你已经注意到使用普通堆栈不是。特别是当你使用线程时)
不,这不贵。它和堆栈分配一样快。
的std ::列表&LT;&GT;是的,这将是昂贵的。但那是因为你无法预先分配。的std ::矢量&lt;&GT;默认情况下是块分配。