std :: unordered_multiset的用例

时间:2015-10-18 01:03:50

标签: c++11 stl hashset unordered-multiset

我想知道为什么会使用std::unordered_multiset。我的猜测是它与插入/擦除后的迭代器的失效或非失效有关,但也许它更深一些?非常相似的问题在这里:https://p.ota.to/blog/2013/4/pagination-with-cursors-in-the-app-engine-datastore/,但它更多的是关于地图的讨论。

1 个答案:

答案 0 :(得分:4)

关于您的问题,unordered_multiset容器最重要的功能是:

  • 它们是关联容器,因此与(未排序的)向量相比,它们允许快速的数据查找和检索。
  • 它们通常比多集合更快,无论是插入还是查找,有时甚至是删除(参见例如this benchmark)。

因此,unordered_multiset的典型用例是当您需要快速查找而不关心数据是否无序时:

  • 如果您根本不进行任何数据查找,矢量可能是更好的解决方案;
  • 如果您需要对数据进行排序,则应该使用多重集。

请注意,在其他情况下,无法使用或不应使用无序容器。

  • 首先,如果散列是非常昂贵的,那么无序容器实际上可能比有序容器慢。
  • 其次,无序容器的最坏情况插入复杂度是线性的,而不是像有序容器那样的对数。在实践中,这意味着插入几乎总是非常快,除了容器大小超过某个容量阈值并且整个容器需要重新散列时。因此,如果您对插入时间有严格的时间要求或者重新拍摄非常慢,则可能无法使用无序容器。
  • 第三,可能存在无序数字的内存消耗高得无法接受的情况(但有时可以通过微调容器参数来解决)。

关于有序容器应该优先于无序容器的用例,您可能需要阅读this answer。有关容器选择的一般准则,您可能需要阅读How can I efficiently select a Standard Library container in C++11?

修改

考虑到无序多重集和向量通常可以做非常相似的事情,总是使用向量不是更好吗?矢量是否自动优于无序多重集?

以下转载的是非常简单的基准测试结果(本文末尾提供完整代码):

  • 我们创建一个容器,它可以是无序多重集,原始向量或有序向量;
  • 我们交替插入一些随机元素,然后计算一些随机密钥;
  • 插入+计数操作重复10万次;
  • 测量并显示这些100 000次插入+计数操作所需的总持续时间。

以下是整数容器的结果:

|---------------------------------------------|----------------|
| Environment              |     Windows 7    | CygWin 64 bits |
| Compiler                 | VS Express 2013  |   gcc 4.9.3    |
|---------------------------------------------|----------------|
| unordered_multiset<int>  |    0.75 s        |     0.8 s      |
| vector<int>, unsorted    |    7.9  s        |    11.0 s      |
| vector<int>, sorted      |    1.0  s        |     0.6 s      | 
|---------------------------------------------|----------------|

在上面的示例中,无序多集对Windows基准稍微好一些,而对于CygWin基准,排序向量稍微好一点。对于多目标开发,这里没有明显的选择。

以下是使用字符串容器进行类似测试的结果:

|-----------------------------------------------|----------------|
| Environment                |     Windows 7    | CygWin 64 bits |
| Compiler                   | VS Express 2013  |   gcc 4.9.3    |
|-----------------------------------------------|----------------|
| unordered_multiset<string> |      1  s        |       1 s      |
| vector<string>, unsorted   |     30  s        |      65 s      |
| vector<string>, sorted     |    130  s        |      32 s      | 
|-----------------------------------------------|----------------|

在这个例子中,无序多重集合远远超过了向量。

这里的确切数字并不重要,因为它们特定于执行这些基准测试的特定条件(硬件,操作系统,编译器,编译器选项等)。重要的是,向量有时胜过无序的多重集合,但有时它们却没有。确定无序多点或向量是否应该用于给定应用程序的唯一方法是尽可能逼真地进行基准测试。

下面是整数容器基准测试的代码。由于它是在飞行中开发的,因此欢迎所有更正和改进!

#include "stdafx.h"
#include <iostream>
#include <array>
#include <unordered_set>
#include <vector>
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <unordered_map>
#include <string>

using namespace std;

const unsigned N = 100000;      // Number of test iterations (= insertions + lookups)
typedef string Element;         // Type of data stored into the container to be tested
array<Element, N> testData;     // Pseudo-random input sequence
array<Element, N> lookupKeys;   // Pseudo-random lookup keys

// Test action for an unordered_multiset (insert some random data then count some random key)
struct unordered_multiset_action
{
    typedef unordered_multiset<Element> Container;
    int operator()(Container& container, unsigned k)
    {
        container.insert(testData[k]);
        return container.count(lookupKeys[k]);
    }
};

// Test action for an unsorted vector (insert some random data then count some random key)
struct unsorted_vector_action
{
    typedef vector<Element> Container;
    int operator()(Container& container, unsigned k)
    {
        container.push_back(testData[k]);               
        return count(testData.cbegin(), testData.cend(), lookupKeys[k]);
    }
};

// Test action for a sorted vector (insert some random data then count some random key)
struct sorted_vector_action
{
    typedef vector<Element> Container;
    int operator()(Container& container, unsigned k)
    {
        container.insert(upper_bound(container.begin(), container.end(), testData[k]), testData[k]);
        auto range = equal_range(container.cbegin(), container.cend(), lookupKeys[k]);
        return range.second - range.first;
    }
};

// Builds an empty container to be tested
// Then invokes N times the test action (insert some random key then count some other random key)
template<class Action>
long long container_test(Action action)
{
    using Container = typename Action::Container;
    Container container;
    long long keyCount = 0;
    for (unsigned k = 0; k<N; ++k)
        keyCount += action(container, k);
    return keyCount;
}

int main(int nargs, char *args[])
{
    using namespace chrono;

    // Parse user input to select which container should be tested
    enum SelectedContainer { UNORDERED_MULTISET, UNSORTED_VECTOR, SORTED_VECTOR };
    unordered_map<string, SelectedContainer> decoder{ { "unordered_multiset", UNORDERED_MULTISET },
                                                      { "unsorted_vector",    UNSORTED_VECTOR },
                                                      { "sorted_vector",      SORTED_VECTOR } };
    if ( nargs < 2 )
    {
        cerr << "Please provde an argument among those keywords: unordered_multiset, unsorted_vector, sorted_vector" << endl;
        return (-1);
    }
    auto it = decoder.find(args[1]);
    if (it == decoder.cend())
    {
        cerr << "Please enter one of the following arguments: unordered_multiset, unsorted_vector, sorted_vector" << endl;
        return (-1);
    }
    SelectedContainer selectedContainer = it->second;

    // Generate pseudo-random input data and input keys (no seeding for reproducibility)
    generate(testData.begin(),   testData.end(), []()   { return rand() % 256; });
    generate(lookupKeys.begin(), lookupKeys.end(), []() { return rand() % 256; });

    // Run test on container to be tested and measure elapsed time
    auto startTime = high_resolution_clock::now();
    long long keyCount;
    switch (selectedContainer)
    {
    case UNORDERED_MULTISET: 
        keyCount = container_test(unordered_multiset_action());
        break;
    case UNSORTED_VECTOR:
        keyCount = container_test(unsorted_vector_action());
        break;
    case SORTED_VECTOR:
        keyCount = container_test(sorted_vector_action());
        break;
    };

    auto endTime = high_resolution_clock::now();

    // Summarize test results
    duration<float> elaspedTime = endTime - startTime;
    cout << "Performed " << N << " insertions interleaved with " << N << " data lookups" << endl;
    cout << "Total key count = " << keyCount << endl;
    cout << "Elapsed time: " << duration_cast<milliseconds>(elaspedTime).count() << " milliseconds" << endl;
}