C++ 不使用新运算符细分四叉树
在我看到的每个四叉树实现中,细分方法总是使用C++ 不使用新运算符细分四叉树,c++,memory-management,quadtree,C++,Memory Management,Quadtree,在我看到的每个四叉树实现中,细分方法总是使用new操作符来创建子单元 有没有办法避免这种情况? 因为我每帧都重新创建四叉树以方便更新,但是使用new和delete大约每帧200~300次会破坏我的性能 这是我的实现: void UQuadtree::subdivide(Quad * Node) { float HalfExtent = Node->Extent/2; FVector2D Center = Node->Center; Node->NW =
new
操作符来创建子单元
有没有办法避免这种情况?
因为我每帧都重新创建四叉树以方便更新,但是使用new
和delete
大约每帧200~300次会破坏我的性能
这是我的实现:
void UQuadtree::subdivide(Quad * Node)
{
float HalfExtent = Node->Extent/2;
FVector2D Center = Node->Center;
Node->NW = new Quad(FVector2D(Center.X + HalfExtent, Center.Y - HalfExtent), HalfExtent);
Node->NE = new Quad(FVector2D(Center.X + HalfExtent, Center.Y + HalfExtent), HalfExtent);
Node->SW = new Quad(FVector2D(Center.X - HalfExtent, Center.Y - HalfExtent), HalfExtent);
Node->SE = new Quad(FVector2D(Center.X - HalfExtent, Center.Y + HalfExtent), HalfExtent);
}
bool UQuadtree::insert(FVector2D* point, Quad * Node)
{
if (!ConstructBox2D(Node->Center, Node->Extent).IsInside(*point))
{
return false;
}
if (Node->Points.Num() < Capacity) {
Node->Points.Add(point);
return true;
}
if (Node->NW == nullptr) {
subdivide(Node);
}
if (insert(point, Node->NW)) { return true; }
if (insert(point, Node->NE)) { return true; }
if (insert(point, Node->SW)) { return true; }
if (insert(point, Node->SE)) { return true; }
return false;
}
(顺便说一句,我是在UE4中实现的)。我想演示一个非常简单的内存池。(在我的评论中,我推荐了一个向量列表,这就是我想在下面详细阐述的内容。) 首先,我提出了一些用于简化概念的约束条件:
模板类PoolT
开始:
#include <iomanip>
#include <iostream>
#include <vector>
#include <list>
template <typename ELEMENT, size_t N = 16>
class PoolT {
private:
typedef std::list<std::vector<ELEMENT> > Data;
Data _data;
typename Data::iterator _iterEnd;
size_t _n;
size_t _size, _capacity;
public:
PoolT():
_data(), _iterEnd(_data.end()), _n(N),
_size(0), _capacity(0)
{
std::cout << " PoolT<ELEMENT>::PoolT()\n";
}
~PoolT() = default;
PoolT(const PoolT&) = delete;
PoolT& operator=(const PoolT&) = delete;
ELEMENT& getNew()
{
if (_n >= N && _iterEnd != _data.end()) {
_n = 0; ++_iterEnd;
std::cout << " PoolT<ELEMENT>::getNew(): switching to next chunk.\n";
}
if (_iterEnd == _data.end()) {
std::cout << " PoolT<ELEMENT>::getNew(): Chunks exhausted. Allocating new chunk of size " << N << ".\n";
_iterEnd = _data.insert(_iterEnd, std::vector<ELEMENT>(N));
_capacity += N;
_n = 0;
}
std::cout << " PoolT<ELEMENT>::getNew(): returning ELEMENT " << _n << " of current chunk.\n";
return (*_iterEnd)[++_size, _n++];
}
void reset()
{
_size = _n = 0; _iterEnd = _data.begin();
}
size_t size() const { return _size; }
size_t capacity() const { return _capacity; }
};
返回的元素
可能以前使用过。因此,之后应将其重置为初始状态。为了简单起见,我只做了一个函数Node::clear()
,它将实例重置为初始状态
我还禁用了节点
的复制构造函数和复制赋值操作符。在我的示例中,节点
实例通过指针相互引用。因此,重新分配它们的存储将产生致命的后果。(这会使节点指针悬空。)内存池PoolT
的构建就是考虑到这一点的。(对于std::vector
中的意外重新分配,至少需要其中一个(复制构造函数或赋值运算符)。因此,在这种情况下,我会得到一个编译器错误。)
节点的内存池
:
typedef PoolT<Node> NodePool;
typedef PoolT NodePool;
和一个小测试套件,用于显示实际情况:
Node* fill(NodePool &nodePool, int depth)
{
Node *pNode = &nodePool.getNew();
pNode->clear();
if (--depth > 0) {
pNode->pNW = fill(nodePool, depth);
pNode->pNE = fill(nodePool, depth);
pNode->pSW = fill(nodePool, depth);
pNode->pSE = fill(nodePool, depth);
}
return pNode;
}
void print(std::ostream &out, const Node *pNode, int depth = 0)
{
out << (const void*)pNode << '\n';
if (!pNode) return;
++depth;
if (pNode->pNW) {
out << std::setw(2 * depth) << "" << "pNW: "; print(out, pNode->pNW, depth);
}
if (pNode->pNE) {
out << std::setw(2 * depth) << "" << "pNE: "; print(out, pNode->pNE, depth);
}
if (pNode->pSW) {
out << std::setw(2 * depth) << "" << "pSW: "; print(out, pNode->pSW, depth);
}
if (pNode->pSE) {
out << std::setw(2 * depth) << "" << "pSE: "; print(out, pNode->pSE, depth);
}
}
#define DEBUG(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__
int main()
{
DEBUG(NodePool nodePool);
std::cout
<< "nodePool.capacity(): " << nodePool.capacity() << ", "
<< "nodePool.size(): " << nodePool.size() << '\n';
DEBUG(Node *pRoot = nullptr);
DEBUG(pRoot = fill(nodePool, 2));
DEBUG(std::cout << "pRoot: "; print(std::cout, pRoot));
std::cout
<< "nodePool.capacity(): " << nodePool.capacity() << ", "
<< "nodePool.size(): " << nodePool.size() << '\n';
DEBUG(pRoot = nullptr);
DEBUG(nodePool.reset());
std::cout
<< "nodePool.capacity(): " << nodePool.capacity() << ", "
<< "nodePool.size(): " << nodePool.size() << '\n';
DEBUG(pRoot = fill(nodePool, 3));
DEBUG(std::cout << "pRoot: "; print(std::cout, pRoot));
std::cout
<< "nodePool.capacity(): " << nodePool.capacity() << ", "
<< "nodePool.size(): " << nodePool.size() << '\n';
return 0;
}
Node*填充(NodePool&NodePool,int-depth)
{
Node*pNode=&nodepol.getNew();
pNode->clear();
如果(--深度>0){
pNode->pNW=填充(节点池,深度);
pNode->pNE=填充(节点波尔,深度);
pNode->pSW=填充(节点波尔,深度);
pNode->pSE=填充(节点波尔,深度);
}
返回pNode;
}
无效打印(std::ostream&out,常量节点*pNode,int深度=0)
{
请给出具体的实现。否则您将无法得到任何具体的答案。不过,您可以考虑重载new
,以创建内存池。“new
和delete
大约每帧200~300次会扼杀我的性能”,您是否真的对其进行了基准测试?您没有证据表明new
是罪魁祸首,并且作为一个有效的假设,它是相当可疑的。您需要分析您的代码并确定实际的瓶颈。在任何情况下,不在每一帧重新创建整个树可能是一个好主意。为了防止单独的分配,您可以查看尚未使用的预分配节点块。当对节点进行细分时,可以将其从该池中取出。(如果进行反向细分,则可以将其退回-这就是我对@wilx的理解。)如果预先分配的节点被完全消耗,并且需要更多的节点,那么必须分配一个新的块。这至少会减少单个分配的数量,甚至可能通过更好的缓存位置来提高性能……内存池在最简单的情况下可以是一个节点向量列表+稍微调整以管理未使用和已使用的节点请注意,虽然向量通过给它一个初始大小提供了方便的分配,但调整它的大小可能会使它的所有内容无效。(因此,我提到了一个向量列表。)“+有点篡改以管理未使用的节点与已使用的节点”如果要返回单个节点,可能会变得更复杂。否则,某种计数器可能足以识别向量何时耗尽(需要一个新的)这真是令人兴奋!非常感谢你花时间做这个演示,我想它会成为一个有用的资源。我对C++来说是很新的,我用它的时间只有2个月,所以我在理解新概念之前需要花上一段时间。但是这很清楚。我需要再读一遍,然后自己去做,所以我可以。n消化它。我今天读了关于重载的新的,使用它似乎很酷。Idk什么是就地构造,但我稍后会读到。如果你有其他类似的酷东西要引导我,我很高兴读到它!@MaximeParata欢迎你。我插入了一些关于删除副本构造的附加说明等。并找到了用于就地构造的链接。我试图自己实现该池,但我没有得到GetNew()
函数的返回语句。(*\u ItrEnd)[++size,n++];
的确切含义是什么?通常运算符[x,y]用于2D数组,对吗?但在这里,即使我们有一种2D数组,运算符[]不应该用列表来工作。我知道它实际上是返回了什么在代码> *yTrime[n+] [/COD]。但是整个语句我不清楚。你能告诉我它应该如何工作吗?@ MaxePARATA号。C++中没有2D下标操作符。<代码>(*yTyrEnter)
引用列表中的当前元素。这是一个向量
检索它的\n
第个元素,而\n
随后递增。结果是元素&
,这就是返回的内容。++\u大小
是一个预期的副作用,我使用序列运算符,
将其放入下标表达式中。++\u大小
的结果是disc但是效果(大小的增加)发生在返回[\n]
的结果之前,这正是我想要的。但是,++\u size;return(*u iterEnd)[\n++];
也可以工作。好吧,我现在更了解它了。它非常简洁,我甚至不知道这是可能的。
typedef PoolT<Node> NodePool;
Node* fill(NodePool &nodePool, int depth)
{
Node *pNode = &nodePool.getNew();
pNode->clear();
if (--depth > 0) {
pNode->pNW = fill(nodePool, depth);
pNode->pNE = fill(nodePool, depth);
pNode->pSW = fill(nodePool, depth);
pNode->pSE = fill(nodePool, depth);
}
return pNode;
}
void print(std::ostream &out, const Node *pNode, int depth = 0)
{
out << (const void*)pNode << '\n';
if (!pNode) return;
++depth;
if (pNode->pNW) {
out << std::setw(2 * depth) << "" << "pNW: "; print(out, pNode->pNW, depth);
}
if (pNode->pNE) {
out << std::setw(2 * depth) << "" << "pNE: "; print(out, pNode->pNE, depth);
}
if (pNode->pSW) {
out << std::setw(2 * depth) << "" << "pSW: "; print(out, pNode->pSW, depth);
}
if (pNode->pSE) {
out << std::setw(2 * depth) << "" << "pSE: "; print(out, pNode->pSE, depth);
}
}
#define DEBUG(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__
int main()
{
DEBUG(NodePool nodePool);
std::cout
<< "nodePool.capacity(): " << nodePool.capacity() << ", "
<< "nodePool.size(): " << nodePool.size() << '\n';
DEBUG(Node *pRoot = nullptr);
DEBUG(pRoot = fill(nodePool, 2));
DEBUG(std::cout << "pRoot: "; print(std::cout, pRoot));
std::cout
<< "nodePool.capacity(): " << nodePool.capacity() << ", "
<< "nodePool.size(): " << nodePool.size() << '\n';
DEBUG(pRoot = nullptr);
DEBUG(nodePool.reset());
std::cout
<< "nodePool.capacity(): " << nodePool.capacity() << ", "
<< "nodePool.size(): " << nodePool.size() << '\n';
DEBUG(pRoot = fill(nodePool, 3));
DEBUG(std::cout << "pRoot: "; print(std::cout, pRoot));
std::cout
<< "nodePool.capacity(): " << nodePool.capacity() << ", "
<< "nodePool.size(): " << nodePool.size() << '\n';
return 0;
}