我有一个场景,我需要0-10000个对象,这些对象与0-10000的数字明显相关。这些对象应该有效存储并具有快速查找时间。但是,管理它们的某个容器会相当稀疏。
例如,我可能有100个对象,相应的关联数字为0-99,另外150个对象,相应的关联数字为650-799。
我首先想到的是一些哈希映射:哈希函数非常容易计算。裸矢量需要二进制搜索来查找元素,这似乎比表查找慢。我也可以使用带指针的数组,但我会指针开销。
现在,如果我想使用hashmap,哪一个?我最近读过unorderd_map
会有一些开销。但另一个要求是地图也应该具有内存效率。
所以我想知道哪个是最适合我的容器。我想使用一些std容器或增强容器,但如果它们可以在LGPL或其他免费的闭源和商业可用许可证下使用,我也可以使用其他第三方容器。
再次要求:
int
)映射到一个对象HoldingClass
,我有数千个实例。所有这些实例都会不时地复制,这应该尽可能快。这也是内存开销应该很低的地方,因为副本存储在我的应用程序中那么,对于这个具体案例来说,似乎是最好的容器?
答案 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_map
和unordered_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/