少量项目的哈希映射

时间:2016-03-28 14:51:32

标签: c++ hashmap

我有一个场景,我需要0-10000个对象,这些对象与0-10000的数字明显相关。这些对象应该有效存储并具有快速查找时间。但是,管理它们的某个容器会相当稀疏。

例如,我可能有100个对象,相应的关联数字为0-99,另外150个对象,相应的关联数字为650-799。

我首先想到的是一些哈希映射:哈希函数非常容易计算。裸矢量需要二进制搜索来查找元素,这似乎比表查找慢。我也可以使用带指针的数组,但我会指针开销。

现在,如果我想使用hashmap,哪一个?我最近读过unorderd_map会有一些开销。但另一个要求是地图也应该具有内存效率。

所以我想知道哪个是最适合我的容器。我想使用一些std容器或增强容器,但如果它们可以在LGPL或其他免费的闭源和商业可用许可证下使用,我也可以使用其他第三方容器。

再次要求:

  • 我需要一个(关联)容器,它将数字(int)映射到一个对象
  • 查询时间应尽可能快
  • 容器最多有10000个元素,因此容器对于少量条目也应该具有性能和内存效率
  • 在阅读评论后添加):容器应该快速复制。容器在其他一些类中使用,我们称之为HoldingClass,我有数千个实例。所有这些实例都会不时地复制,这应该尽可能快。这也是内存开销应该很低的地方,因为副本存储在我的应用程序中
  • 在阅读评论后添加):除了复制整个容器外,从容器中插入和删除是不常见的,而不是性能关键

那么,对于这个具体案例来说,似乎是最好的容器?

5 个答案:

答案 0 :(得分:2)

我不知道你在哪里读到std :: unordered_map会效率低下"对于这个用例,但是在你描述的索引上没有碰撞的情况下,它比std :: unordered_map做得更好。特别是,由于您很好地了解索引的结构,因此您需要查看std::unordered_map::reserve,它允许您在将项目放入unordered_map之前决定哈希表应使用多少个桶。

如果在第一次插入之前在unordered_map上调用std :: unordered_map :: reserve(10001),我希望unordered_map上的所有查找和插入时间都为O(1)。您可以使用较小的数字调用std :: unordered_map :: reserve来权衡内存大小的插入复杂性(从而缩短复制时间)。

但是,我必须说这个问题强烈反映了过早的优化。您如何知道必须优化此数据结构?你更关心内存还是CPU?这个选择对实际程序性能有多大差异?你有一个简单的unordered_map或向量实现基准吗?如果您在任何桶或红黑容器中处理< 10000项目,那么该容器上的几乎所有操作都将相当快。

虽然我们正在研究它:为什么不使用一个简单的C风格的10000指针数组到你的对象?即使在64位计算机上,它的大小也只有78 KB,而且你无法在速度上击败它。

对于理解您自己的数据和理解STL中的基础算法没有任何替代品。特别是,不要试图替换不直接与您的用例说话的专家的建议,不要过早优化,不要害怕深入STL内部

答案 1 :(得分:1)

我想到的是,如果你有0-99索引范围的相同对象 - 可能是某种散列函数,然后是关联容器。例如

UserSchema

然后映射到将0索引映射到实际对象。

然而 - 我怀疑功能读数无法确定为函数(地址范围一直在变化)

我可以提出的建议 - 是我曾经为管理索引范围而制作的一个自定义类 - 但是 - 该类有一些限制 - 我只需要指定给定索引是“空闲”还是“已分配” 'type - 但是type可以用某种索引替换,并且它们映射到对象本身(所以用你自己的类型指针替换char类型)。

我怀疑我的班级可能不是最好的选择,所以请不要粗略地得出结论,那么其他人可能会有用。

FreeSpace.h:

int readdress( int index )
{
    if( index < 100 ) return 0;
    ...
} 

FreeSpace.cpp:

#pragma once
#include <vector>
using std::vector;

typedef __int64 int64;

typedef struct Slice_
{
    Slice_( char t, int64 p, int64 s ) : type(t), pos(p), size(s) { }

    int64 size;
    int64 pos;
    char  type;        //'f' - for free, 'a' - for allocated.
}Slice;


class FreeSpace
{
public:
    FreeSpace();

    void specifySlice(char type, int64 pos, int64 size);
    void _specifySlice(char type, int64& pos, int64& size, int& i);
    int64 getTotalSlicesSize( char type );
    void printMap(void);
    void Clear(void);

    void RunTest(const char* name, Slice* ptests, int nTests);
    void CheckMap(const char* name, Slice* ptests, int nTests, int* testCounters );
    bool RunSelfTest(void);

    vector<Slice> slices;
    int64 fileSize;
    int64 lastAllocEndPos;
    bool bDoDebug;
}; //class FreeSpace

但是这个类依赖于MFC,必要时可以切断它。 (例如,用std :: string替换CStringA。

这个类很可能具有最低的内存消耗,因为它以索引范围运行。

这里最可能缺少的是从索引到实际对象的映射(或在我的情况下键入) - 但是这个函数可以通过切片上的for循环轻松实现。 不知怎的,这样:

#include <afx.h>
#include <afxstr.h>                 //CString
#include <Windows.h>
#include "FreeSpace.h"
#include <assert.h>



FreeSpace::FreeSpace() :
    bDoDebug ( false )
{

}


void FreeSpace::specifySlice( char type, int64 ipos, int64 isize )
{
    Slice* nextSlice;
    Slice* prevSlice;
    int i;

    if( bDoDebug )
        printf("- %s storage from pos %lld, by size %lld\n", (type == 'f') ? "Freeing" : "Allocating", ipos, isize);

    _specifySlice(type, ipos, isize, i);

    ipos = slices[i].pos;
    isize = slices[i].size;
    assert( type == slices[i].type );

    // Checks if slices can be recombined.
    if( i + 1 != slices.size() )    nextSlice = &slices[i+1];
    else                            nextSlice = 0;

    if( i != 0 )    prevSlice = &slices[i-1];
    else            prevSlice = 0;

    // Can we recombine with previous allocation
    if( prevSlice != 0 && prevSlice->pos + prevSlice->size == ipos && prevSlice->type == type )
    {
        // Can combine with previous allocation.
        slices.erase(slices.begin() + i);
        prevSlice->size += isize;
        i--;
    }

    // ---[# 1 #][# 2 #]-------
    // Can we recombine with next allocation
    if( nextSlice != 0 && nextSlice->pos == ipos + isize && nextSlice->type == type )
    {
        // Can combine with next allocation.
        slices[i].size += nextSlice->size;
        slices.erase(slices.begin() + i + 1);
    }
}


void FreeSpace::_specifySlice( char type, int64& ipos, int64& isize, int& i )
{
    int64 last = ipos + isize;
    Slice* nextSlice;
    Slice* prevSlice;

    for( i = 0; i < slices.size(); i++)
    {
        Slice& slice = slices[i];

        if( i + 1 != slices.size() )    nextSlice = &slices[i+1];
        else                            nextSlice = 0;

        if( i != 0 )    prevSlice = &slices[i-1];
        else            prevSlice = 0;


        // ---[######]----------------
        //    ^------^
        // Our allocation starts from same place as current allocation
        if( ipos == slice.pos )
        {
            // Our allocation is the same size.
            if( isize == slice.size )
            {
                if( type == slice.type )    // Nothing changed                                              test1
                    return;

                // Type changed.
                slice.type = type;                                                                      //  test1
                return;
            }

            // ---[######]----------------
            //    ^----------^
            if( last > slice.pos + slice.size )
            {
                slices.erase(slices.begin() + i );  // Covered by our allocation -                          test2
                i--;
                continue;
            }

            // ---[##############]--------
            //    ^----------^
            if( type == slice.type )
                return;                                                                                 //  test3

            slice.size = slice.pos + slice.size - last;                                                 //  test3
            slice.pos = last;
            break;  // Insert our slice
        }

        // ----------------------[#######]----
        //        ^----------^
        // Our allocation comes before this allocation
        if( last <= slice.pos )  // And will not reach this allocation.
        {
            // ------------------[#######]----
            //        ^----------^
            if( last == slice.pos )
                break;                                                                                  //  test13

            // ------------------[#######]----                                                              test5
            //        ^------^
            if( slice.pos > last )
                break;                                                                                  //  test16
        }

        // ---------------[#######]----
        //        ^----------^
        // Our allocation comes before this allocation
        if( ipos < slice.pos )
        {
            // ---------------[#######]----
            //        ^----------^
            if( last < slice.pos + slice.size )
            {
                if( type != slice.type )
                {
                    // Shrink current allocation.                                                           test7
                    slice.size = slice.pos + slice.size - last;
                    slice.pos = last;
                    break;  // Insert our slice
                } else {
                    // Glow current allocation.                                                             test8
                    slice.size = slice.pos + slice.size - last + isize;
                    slice.pos = ipos;
                    return;
                } //if-else
            } //if

            // ---------------[#######]----
            //        ^---------------^
            if( last >= slice.pos + slice.size )
            {
                // ---------------[#######]----
                //        ^---------------^
                if( last == slice.pos + slice.size )
                {
                    slices.erase( slices.begin() + i ); // Covered by our allocation                    -   test6
                    break;  // Insert our slice
                }

                // ---------------[#######]----                                                         -   test9, test10
                //        ^------------------^
                slices.erase( slices.begin() + i ); // Covered by our allocation.
                i--;
                continue;
            } //if
        } //if


        // ---[######]----------------
        //           ^----^
        // Our allocation passed completely previous allocation
        if( ipos >= slice.pos + slice.size )
            // Slice is not affected by our allocation, continue search next slice.
            continue;                                                                                   //  test1

        // ---[#######]--------------------
        //        ^----------^
        // Our allocation passed partially previous allocation
        if( ipos > slice.pos )
        {
            // ---[############]-----------
            //        ^--------^
            // Our allocation ends with current allocation in same place
            if( last == slice.pos + slice.size )
            {
                if( type == slice.type )
                    return;     // Nothing changed.                                                         test12

                slice.size = ipos - slice.pos;                                                          //  test4
                i++;
                break;  // Insert our slice
            } //if

            // ---[############]-----------
            //        ^-----^
            // Our allocation is completely within current allocation
            if( last < slice.pos + slice.size )
            {
                if( type == slice.type )
                    return;     // Same kind, nothing new.                                              -   test11

                // We have some chopped one slice into two                                              -   test1
                slices.insert(slices.begin() + i, Slice(slice.type,slice.pos, ipos - slice.pos ));
                i++;
                Slice& slice = slices[i];
                slice.size = slice.pos + slice.size - last;
                slice.pos = last;
                break;  // Insert our slice
            } //if


            // ---[############]-----------
            //        ^---------------^
            // Our allocation goes beyond current allocation
            if( type == slice.type )
            {
                isize += (ipos - slice.pos);                                                            //  test7
                ipos = slice.pos;               // Will recombine our slice into bigger slice.
                slices.erase(slices.begin() + i);
                i--;
                continue;
            }

            // Different type.
            slice.size = (ipos - slice.pos);    // Make current slice smaller one, continue scan            test6
            continue;
        }
    } //for

    // Simply add slice at that area range.
    slices.insert( slices.begin() + i, Slice(type, ipos, isize) );
} //addSlice


int64 FreeSpace::getTotalSlicesSize( char type )
{
    int64 total = 0;

    for( int i = 0; i < slices.size(); i++)
    {
        Slice& slice = slices[i];
        if( slice.type == type )
            total += slice.size;
    }

    return total;
}


void FreeSpace::printMap(void)
{
    int64 totsize = 0;
    printf("Type   Start addr   End addr     Size\n");

    for( int i = 0; i < slices.size(); i++)
    {
        Slice& slice = slices[i];

        totsize += slice.size;

        printf("%c      %08lld     %08lld     %08lld", slice.type, slice.pos, slice.pos + slice.size - 1, slice.size);

        if( i+1 == slices.size() )
            printf("    (%lld)", totsize );

        printf("\n");
    }

}

void FreeSpace::Clear(void)
{
    slices.clear();
}

void FreeSpace::RunTest(const char* name, Slice* ptests, int nTests)
{
    Clear();

    if( bDoDebug )
        printf("****************** %s ******************\n", name );

    for( int i = 0 ; i < nTests; i++ )
    {
        specifySlice(ptests[i].type, ptests[i].pos, ptests[i].size);
        if( bDoDebug )
            printMap();
    }
}

void FreeSpace::CheckMap(const char* name, Slice* ptests, int nTests, int* testCounters )
{
    bool bPassed = true;

    if( slices.size() != nTests )
        bPassed = false;
    else
        for( int i = 0 ; i < nTests; i++ )
        {
            if( 
                ptests[i].pos != slices[i].pos ||
                ptests[i].size != slices[i].size ||
                ptests[i].type != slices[i].type )
            {
                bPassed = false;
                break;
            }
        }

    testCounters[ bPassed ? 0: 1 ]++;

    if( bPassed ) 
        return;

    CStringA found;
    CStringA expected;

    for( int i = 0 ; i < slices.size(); i++ )
    {
        found.AppendFormat("Slice('%c', %lld, %lld)", slices[i].type, slices[i].pos, slices[i].size );

        if( i+1 != slices.size() )
            found += ",";
    }

    for( int i = 0 ; i < nTests; i++ )
    {
        expected.AppendFormat("Slice('%c', %lld, %lld)", ptests[i].type, ptests[i].pos, ptests[i].size );

        if( i+1 != slices.size() )
            expected += ",";
    }

    if( bDoDebug )
    {
        printf("Test '%s' failed - expected:\n", name);
        printf("     %s\n", expected.GetBuffer() );
        printf("found:\n" );
        printf("     %s\n", found.GetBuffer() );
    }
}


#define RUNTEST( testid, ... ) \
    Slice testid[] = {  ##__VA_ARGS__ }; \
    RunTest( #testid, testid, sizeof(testid) / sizeof(testid[0]) );

#define CHECKTEST( testid, ... ) \
    Slice chk##testid[] = {  ##__VA_ARGS__ }; \
    CheckMap( #testid, chk##testid, sizeof(chk##testid) / sizeof(chk##testid[0]), testCounters );


//
//  Runs class self test, returns false if fails.
//
bool FreeSpace::RunSelfTest(void)
{
    int nTestPassed = 0;
    int testCounters[3] = { 0, 0, 0 };
/*
    Slice test1[] = {
        Slice('f', 0, 10000),
        Slice('a', 0, 10000),
        Slice('f', 900, 100)
    };
    RunTest( "test1", test1, sizeof(test1) / sizeof(test1[0]) );
*/

    RUNTEST( test1, Slice('f', 0, 10000), Slice('a', 1000, 1000),   Slice('a', 1000, 1000), Slice('f', 1000, 1000) );
    CHECKTEST(test1, Slice('f', 0, 10000));

    RUNTEST( test2, Slice('f', 0, 10000), Slice('a', 1000, 1000),   Slice('f', 1000, 2000) );
    CHECKTEST(test2, Slice('f', 0, 10000));

    RUNTEST( test3, Slice('f', 0, 10000), Slice('a', 1000, 1000), Slice('a', 1000, 100),   Slice('f', 1000, 500) );
    CHECKTEST( test3, Slice('f', 0, 1500),  Slice('a', 1500, 500),   Slice('f', 2000, 8000) );

    RUNTEST( test4, Slice('f', 0, 10000), Slice('a', 1000, 1000),   Slice('a', 500, 500) );
    CHECKTEST(test4, Slice('f', 0, 500),Slice('a', 500, 1500),Slice('f', 2000, 8000));

    RUNTEST( test5, Slice('f', 0, 10000), Slice('a', 1000, 1000),   Slice('a', 500, 100) );
    CHECKTEST(test5, Slice('f', 0, 500),Slice('a', 500, 100),Slice('f', 600, 400),Slice('a', 1000, 1000),Slice('f', 2000, 8000) );

    RUNTEST( test6, Slice('f', 0, 10000), Slice('a', 1000, 1000),   Slice('a', 500, 1500) );
    CHECKTEST(test6, Slice('f', 0, 500),Slice('a', 500, 1500),Slice('f', 2000, 8000));

    RUNTEST( test7, Slice('f', 0, 10000), Slice('a', 1000, 2000),   Slice('f', 500, 1000) );
    CHECKTEST(test7, Slice('f', 0, 1500),Slice('a', 1500, 1500),Slice('f', 3000, 7000) );

    RUNTEST( test8, Slice('f', 0, 10000), Slice('a', 1000, 2000),   Slice('a', 500, 1000) );
    CHECKTEST(test8, Slice('f', 0, 500),Slice('a', 500, 2500),Slice('f', 3000, 7000) );

    RUNTEST( test9, Slice('f', 0, 10000), Slice('a', 1000, 2000),   Slice('a', 500, 4000) );
    CHECKTEST(test9, Slice('f', 0, 500),Slice('a', 500, 4000),Slice('f', 4500, 5500) );

    RUNTEST( test10, Slice('f', 0, 10000), Slice('a', 1000, 2000),   Slice('f', 500, 4000) );
    CHECKTEST(test10, Slice('f', 0, 10000) );

    RUNTEST( test11, Slice('f', 0, 10000), Slice('f', 1000, 1000) );
    CHECKTEST(test11, Slice('f', 0, 10000) );

    RUNTEST( test12, Slice('f', 0, 10000), Slice('f', 9000, 1000) );
    CHECKTEST(test12, Slice('f', 0, 10000) );

    RUNTEST( test13, Slice('f', 1000, 1000), Slice('f', 500, 500) );
    CHECKTEST(test13, Slice('f', 500, 1500) );

    RUNTEST( test14, Slice('f', 1000, 1000), Slice('a', 500, 500) );
    CHECKTEST(test14, Slice('a', 500, 500),Slice('f', 1000, 1000) );

    RUNTEST( test15, Slice('f', 1000, 1000), Slice('f', 100, 100) );
    CHECKTEST(test15, Slice('f', 100, 100),Slice('f', 1000, 1000) );

    RUNTEST( test16, Slice('f', 1000, 1000), Slice('a', 100, 100) );
    CHECKTEST(test16, Slice('a', 100, 100),Slice('f', 1000, 1000) );


    if( bDoDebug )
    {
        if( testCounters[1] == 0 )
            printf("\n     Success: All %d tests passed\n\n", testCounters[0] );
        else
            printf("\n     %d test(s) passed, %d test(s) FAILED\n\n", testCounters[0], testCounters[1] );
    }

    return testCounters[1] == 0;
} //RunSelfTest

答案 2 :(得分:1)

boost::flat_map是一个关联容器,使用向量基本实现。这使得它对缓存友好(基于节点的实现会降低std::unordered_map)的性能。

但是您应该在某些实际数据上同时衡量flat_mapunordered_map,以了解哪些数据更具效果。

答案 3 :(得分:0)

表示指数和值的两个向量的草图:

#include <algorithm>
#include <vector>

template <typename KeyType, typename ValueType>
class vector_map
{
    private:
    typedef std::vector<KeyType> index_vector;
    typedef std::vector<ValueType> value_vector;

    public:
    typedef KeyType key_type;
    typedef ValueType value_type;
    typedef typename value_vector::size_type size_type;
    typedef typename value_vector::iterator iterator;
    typedef typename value_vector::const_iterator const_iterator;

    vector_map() {}
    vector_map(size_type capacity)
    {
        indices.reserve(capacity);
        values.reserve(capacity);
    }

    iterator begin() { return values.begin(); }
    iterator end() { return values.end(); }
    iterator insert(const key_type key, const value_type& value) {
        iterator pos = std::lower_bound(indices.begin(), indices.end(), key);
        size_type i = pos - indices.begin();
        indices.insert(pos, key);
        return values.insert(values.begin() + i, value);
    }

    iterator erase(const key_type key) {
        iterator pos = std::lower_bound(indices.begin(), indices.end(), key);
        if(pos != indices.end() && ! (*pos < key)) {
            size_type i = pos - indices.begin();
            indices.erase(pos);
            return values.erase(values.begin() + i);
        }
        return values.end();
    }

    iterator erase(const_iterator position) {
        if(position == end())
            return end();
        else {
            size_type i = position - values.begin();
            indices.erase(indices.begin() + i);
            return values.erase(values.begin() + i);
        }
    }

    iterator find(const key_type key) {
        iterator pos = std::lower_bound(indices.begin(), indices.end(), key);
        if(pos != indices.end() && ! (*pos < key)) {
            size_type i = pos - indices.begin();
            return values.begin() + i;
        }
        return values.end();
    }

    private:
    index_vector indices;
    value_vector values;
};

您也可以考虑使用自定义分配器std::unordered_map。 无论如何,您应该测量(配置)不同的解决方案并相应地选择。

答案 4 :(得分:0)

应该使用

std::map<int, MyClass>代替std::unordered_map<int, MyClass>

理由:

• The lookup complexity for map is 
  

对数大小

http://www.cplusplus.com/reference/map/map/at/

• The lookup complexity for unordered map is 
  

平均情况:不变。最坏情况:容器尺寸为线性

http://www.cplusplus.com/reference/unordered_map/unordered_map/at/