c++ - 为什么将排序的键插入 std::set 比插入打乱的键快得多?

标签 c++ stl red-black-tree stdset cache-locality

我无意中惊讶地发现,将排序后的键插入std::set 比插入打乱后的键快得多。这有点违反直觉,因为红黑树(我验证了 std::set 在我的系统上实现为红黑树)作为自平衡二叉搜索树,需要做许多重新平衡操作以插入一系列排序的键,因此插入排序的键应该比插入打乱的键花费更多的时间。

但事实是,插入排序键比插入随机键快 15 倍!这是我的测试代码和一些结果:

#include <algorithm>
#include <chrono>
#include <iostream>
#include <random>
#include <set>
#include <vector>
using namespace std;

int64_t insertion_time(const vector<int> &keys) {    
        auto start = chrono::system_clock::now();
        set<int>(keys.begin(), keys.end());
        auto stop = chrono::system_clock::now();
        auto elapsed = chrono::duration_cast<chrono::milliseconds>(stop - start);
        return elapsed.count(); 
}

int main() {
    size_t test_size;
    cout << "test size: ";
    cin >> test_size;
    vector<int> keys(test_size);
    for (int i = 0; i < test_size; ++i) {
        keys[i] = i;
    }
    
    // whether shuffled case or sorted case took first was irrelevant and results were similar
    auto rng = std::default_random_engine {};
    shuffle(keys.begin(), keys.end(), rng);
    cout << "shuffled: " << insertion_time(keys) << endl;

    sort(keys.begin(), keys.end());
    cout << "sorted: " << insertion_time(keys) << endl;

    return 0;
}
// i7 8700, 32 GB RAM, WIN10 2004, g++ -O3 main.cpp
// An interesting observation is that the difference becomes larger as test_size being larger.
// Similar results showed up for my handwritten red-black tree and other
// machines( or other compilers, operating systems etc)

C:\Users\Leon\Desktop\testSetInsertion>a
test size: 1000000
shuffled: 585
sorted: 96

C:\Users\Leon\Desktop\testSetInsertion>a
test size: 3000000
shuffled: 2480
sorted: 296

C:\Users\Leon\Desktop\testSetInsertion>a
test size: 5000000
shuffled: 4805
sorted: 484

C:\Users\Leon\Desktop\testSetInsertion>a
test size: 10000000
shuffled: 11537
sorted: 977

C:\Users\Leon\Desktop\testSetInsertion>a
test size: 30000000
shuffled: 46239
sorted: 3076

有人解释一下吗?我猜想这与缓存位置有关,因为在插入排序键时,重新平衡通常涉及最近插入的那些节点。但以上只是我的猜测,我对缓存局部性知之甚少。

最佳答案

如果你看https://en.cppreference.com/w/cpp/container/set/set

你可以看到:

Complexity
[..]
2) N log(N) where N = std::distance(first, last) in general, linear in N if the range is already sorted by value_comp().

我们可以使用insert在循环中以 end() 作为提示,它是具有正确提示的摊销常量。

关于c++ - 为什么将排序的键插入 std::set 比插入打乱的键快得多?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66332142/

相关文章:

c++ - 按字母顺序排序

C++静态分配双端队列实现

c++ - 如何循环 std::regex_search 的结果?

c++ - 检查一棵树是否满足红黑树的black-height属性

C#引用麻烦

java - 类似 STL 的 Java 红黑树/TreeSet/Map 和带有非快速失败/安全迭代器的链表

c++ - Hook ishellfolder enumobjects

c++ - 如何使用 CGAL 简化 3d 网格的特定区域

c++ - c++ 'pass by reference if possible' 可以吗?

c++ - 为什么我不能删除 vector 的最后一个元素