地图何时变得比两个向量更好?

时间:2011-10-24 17:54:12

标签: c++ performance map vector

地图会对其所有元素进行二元搜索,这些元素具有对数复杂度 - 这意味着对于足够小的对象集合,地图的性能会比具有线性搜索的两个向量差。

对象(关键字)池应该有多大才能使地图开始比两个向量表现更好?

编辑:问题的一个更通用的版本:对象池应该有多大,以便二进制搜索比线性搜索更好地执行?

我使用字符串作为键,值是指针,但我的特定用例可能无关紧要。我更好奇地了解如何正确使用这两个工具。

3 个答案:

答案 0 :(得分:14)

如果你原谅我这么说,大多数答案听起来像是各种各样的说法:“我不知道”,没有实际承认他们不知道。虽然我普遍同意他们给出的建议,但似乎没有人试图直接解决你提出的问题:什么是收支平衡点。

公平地说,当我读到这个问题时,我也不知道。这是我们所知道的基本知识之一:对于足够小的集合,线性搜索可能会更快,而对于足够大的集合,二进制搜索无疑会更快。然而,我从来没有太多理由去调查关于盈亏平衡点的真实情况。然而,你的问题让我很好奇,所以我决定写一些代码来至少得到一些想法。

这段代码肯定是非常快速破解(大量重复,目前仅支持一种类型的密钥等),但至少它可能会让人知道会发生什么:

#include <set>
#include <vector>
#include <string>
#include <time.h>
#include <iostream>
#include <algorithm>

int main() {   

    static const int reps = 100000;

    std::vector<int> data_vector;
    std::set<int> data_set;
    std::vector<int> search_keys;

    for (int size=10; size<100; size += 10) {
        data_vector.clear();
        data_set.clear();
        search_keys.clear();

        int num_keys = size / 10;   
        for (int i=0; i<size; i++) {
            int key = rand();

            if (i % num_keys == 0)
                search_keys.push_back(key);

            data_set.insert(key);
            data_vector.push_back(key);
        }

        // Search for a few keys that probably aren't present.
        for (int i=0; i<10; i++)
            search_keys.push_back(rand());

        long total_linear =0, total_binary = 0;

        clock_t start_linear = clock();
        for (int k=0; k<reps; k++) {
            for (int j=0; j<search_keys.size(); j++) {
                std::vector<int>::iterator pos = std::find(data_vector.begin(), data_vector.end(), search_keys[j]);
                if (pos != data_vector.end())
                    total_linear += *pos;
            }
        }
        clock_t stop_linear = clock();                      

        clock_t start_binary = clock();
        for (int k=0; k<reps; k++) {
            for (int j=0; j<search_keys.size(); j++) {
                std::set<int>::iterator pos = data_set.find(search_keys[j]);
                if (pos != data_set.end())
                    total_binary += *pos;
            }
        }
        clock_t stop_binary = clock();

        std::cout << "\nignore: " << total_linear << " ignore also: " << total_binary << "\n";

        std::cout << "For size = " << size << "\n";
        std::cout << "\tLinear search time = " << stop_linear - start_linear << "\n";
        std::cout << "\tBinary search time = " << stop_binary - start_binary << "\n";
    }
    return 0;
}

以下是我在我的机器上运行的结果:

ignore: 669830816 ignore also: 669830816
For size = 10
        Linear search time = 37
        Binary search time = 45

ignore: 966398112 ignore also: 966398112
For size = 20
        Linear search time = 60
        Binary search time = 47

ignore: 389263520 ignore also: 389263520
For size = 30
        Linear search time = 83
        Binary search time = 63

ignore: -1561901888 ignore also: -1561901888
For size = 40
        Linear search time = 106
        Binary search time = 65

ignore: -1741869184 ignore also: -1741869184
For size = 50
        Linear search time = 127
        Binary search time = 69

ignore: 1130798112 ignore also: 1130798112
For size = 60
        Linear search time = 155
        Binary search time = 75

ignore: -1949669184 ignore also: -1949669184
For size = 70
        Linear search time = 173
        Binary search time = 83

ignore: -1991069184 ignore also: -1991069184
For size = 80
        Linear search time = 195
        Binary search time = 90

ignore: 1750998112 ignore also: 1750998112
For size = 90
        Linear search time = 217
        Binary search time = 79

显然,这不是唯一可能的测试(甚至接近可能的最佳测试),但在我看来,即使是一点硬数据也比没有更好。

编辑:我会注意到记录,我认为没有理由使用两个向量(或对向量)的代码不能像使用集合或映射的代码一样干净。显然你想把它的代码放到它自己的一个小类中,但我完全没有理由认为它不能正确地 与外部世界相同的接口{{ 1}}。事实上,我可能只是将其称为“tiny_map”(或该订单上的某些内容)。

OO编程的基本要点之一(在通用编程中仍然如此,至少在某种程度上)是将接口与实现分开。在这种情况下,您所说的纯粹是一个不需要影响接口的实现细节。事实上,如果我正在编写一个标准库,我很想把它作为一个“小地图优化”,类似于常见的小字符串优化。基本上,只需在地图对象本身中直接分配一个包含map的10个(或大约)对象的数组,并在地图较小时使用它们,如果它变得足够大,则将数据移动到树中。证明这一点。唯一真正的问题是人们是否经常使用微小的地图来证明这项工作的合理性。

答案 1 :(得分:5)

map的代码比两个vector的代码更清晰;那应该是你最关心的问题。只有当您确定map的性能在您自己的应用程序中存在问题时,您才应该担心这种差异,并且此时您应该自己对其进行基准测试。

答案 2 :(得分:3)

地图永远不会等于排序矢量搜索速度,因为矢量内存更好,内存访问更直接。

然而,这只是故事的一半。一旦开始修改容器,节点基容器(不需要在其元素周围移动以容纳中间的插入),就可以获得理论上的优势。与搜索一样,更好的缓存局部性仍然为少量元素提供了矢量边缘。

确切的测量很难提供,并且取决于具体的优化,上下文使用和机器,因此我建议简单地按功能进行。地图有一个按键搜索的自然界面,所以使用它会让你的生活更轻松。

如果您真的发现自己正在测量并且需要真正从性能中挤出性能,那么您可能需要更专业的数据结构。查找B-Trees。