糟糕的树增加了性能
Poor tree add performance
我现在正在编写一个树容器(只是为了理解和训练(,到目前为止,我得到了第一个非常基本的方法来向树中添加元素。
这是我知道的树代码。现在没有析构函数,没有清理,也没有元素访问。
template <class T> class set
{
public:
struct Node
{
Node(const T& val)
: left(0), right(0), value(val)
{}
Node* left;
Node* right;
T value;
};
set()
{}
template <class T> void add(const T& value)
{
if (m_Root == nullptr)
{
m_Root = new Node(value);
}
Node* next = nullptr;
Node* current = m_Root;
do
{
if (next != nullptr)
{
current = next;
}
next = value >= current->value ? current->left : current->right;
} while (next != nullptr);
value >= current->value ? current->left = new Node(value) : current->right = new Node(value);
}
private:
Node* m_Root;
};
好吧,现在我用唯一且平衡(低和高(的值测试了 std::set 的插入性能,得出的结论是性能太糟糕了。
集合插入值的速度如此之快,有什么理由可以提高我的方法的插入性能?(我知道可能有更好的树模型,但据我所知,大多数树模型之间的插入性能应该很接近(。
在 i5 4570 库存时钟下, std::set 需要 0.013s 才能添加 1000000 个 int16 值。 我的集合需要 4.5 秒才能添加相同的值。
这种巨大的差异从何而来?
更新:
好的,这是我的测试代码:
int main()
{
int n = 1000000;
test::set<test::int16> mset; //my set
std::set<test::int16> sset; //std set
std::timer timer; //simple wrapper for clock()
test::random_engine engine(0, 500000); //simple wrapper for rand() and yes, it's seeded, and yes I am aware that an int16 will overflow
std::set<test::int16> values; //Set of values to ensure unique values
bool flip = false;
for (int i = 0; n > i; ++i)
{
values.insert(flip ? engine.generate() : 0 - engine.generate());
flip = !flip; //ensure that we get high and low values and no straight line, but at least 2 paths
}
timer.start();
for (std::set<test::int16>::iterator it = values.begin(); values.end() != it; ++it)
{
mset.add(*it);
}
timer.stop();
std::cout << timer.totalTime() << "s for msetn";
timer.reset();
timer.start();
for (std::set<test::int16>::iterator it = values.begin(); values.end() != it; ++it)
{
sset.insert(*it);
}
timer.stop();
std::cout << timer.totalTime() << "s for stdn";
}
由于 dubicates 的原因,该集合不会存储每个值,但两个容器将获得一个较高的数字和相同的值,以确保具有代表性的结果。我知道测试可能更准确,但它应该给出一些可比的数字。
std::set
实现通常使用红黑树数据结构。它是一个自平衡的二叉搜索树,它的insert
操作保证在最坏的情况下(这是标准要求(O(log(n))
时间复杂度。您可以使用简单的二叉搜索树O(n)
最坏情况的插入操作。
如果插入唯一的随机值,那么如此大的差异看起来很可疑。但不要忘记,随机性不会让你的树平衡,树的高度可能比log(n)
大得多
编辑
似乎我发现了您的代码的主要问题。所有生成的值都存储在std::set
中。之后,按递增顺序将它们添加到集合中。这会将您的集合降级到链表。
两个明显的区别是:
-
std::set
中使用的红黑树(可能(重新平衡自己,为最坏情况的行为设置上限,正如DAle所说。如果这是问题所在,则在绘制 N(插入的节点数(与每次插入时间时,您应该看到它。您还可以跟踪树的深度(至少出于调试目的(,并将其绘制成针对 N 的图。
-
标准容器使用分配器,它可能比单独
new
每个节点更聪明。您可以尝试在自己的容器中使用std::allocator
,看看这是否会带来重大改进。
编辑 1如果实现了池分配器,则问题中应包含相关信息。
编辑 2现在您已经添加了测试代码,有一个明显的问题,这意味着您的集合将始终具有最差的插入性能。您预先排序了输入值!std::set
是一个有序容器,所以首先把你的值放在那里可以保证你总是以递增的值顺序插入,所以你的树(不自平衡(退化为一个昂贵的链表,你的插入总是线性的,而不是对数时间。
您可以通过将值存储在vector
中(仅使用set
检测冲突(或使用unordered_set
进行重复数据删除而无需预排序来验证这一点。
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- 数组索引的值没有增加
- OpenMP阵列性能较差
- 递归列出所有目录中的C++与Python与Ruby的性能
- 为什么我的代码在输出中增加了93天
- 大小相等但成员数量不同的结构之间的性能差异
- 有效地使用std::unordered_map来插入或增加键的值
- 为什么constexpr的性能比正常表达式差
- 在类中使用随机生成器时出现性能问题
- 在main()之外初始化std::vector会导致性能下降(多线程)
- C++ 动态数组每次添加时将大小增加 1 - 错误
- 为什么要增加导致崩溃的指针
- 海湾合作委员会 ARM 性能下降
- GCC 和 Clang 代码性能的巨大差异
- 在容量内调整矢量大小时的性能影响
- 了解算法的性能差异(如果以不同的编程语言实现)
- 糟糕的树增加了性能
- 堆栈保留大小和堆栈提交大小的增加会提高应用程序性能吗
- openmp:线程数量的增加会降低性能
- 原子fetch_add与增加性能