C++std::映射创建耗时过长
C++ std::map creation taking too long?
更新:
我正在做一个性能非常关键的程序。我有一个未排序的结构向量。我需要在这个向量中执行许多搜索操作。所以我决定将矢量数据缓存到这样的地图中:
std::map<long, int> myMap;
for (int i = 0; i < myVector.size(); ++i)
{
const Type& theType = myVector[i];
myMap[theType.key] = i;
}
当我搜索地图时,程序其余部分的结果要快得多。然而,剩下的瓶颈是地图本身的创建(在其中插入大约1500个元素平均需要大约0.8毫秒(。我需要想办法把这次时间缩短。我只是插入一个long作为键,插入一个int作为值。我不明白为什么要花这么长时间。
我的另一个想法是创建矢量的副本(不能接触原始矢量(,并以某种方式执行比std::sort更快的排序(排序时间太长(。
编辑:
对不起大家。我的意思是说,我正在创建一个std::map,其中键是long,值是int。long值是结构的键值,int是向量中相应元素的索引。
此外,我做了更多的调试,并意识到向量根本没有排序。这完全是随机的。所以,做一些类似马厩的运动是行不通的。
另一个更新:
感谢大家的回复。我最终创建了一个成对的向量(std::vector of std::pair(long,int((。然后我按照长值对向量进行排序。我创建了一个自定义比较器,只查看这对的第一部分。然后我用下界搜索这对。以下是我的做法:
typedef std::pair<long,int> Key2VectorIndexPairT;
typedef std::vector<Key2VectorIndexPairT> Key2VectorIndexPairVectorT;
bool Key2VectorIndexPairComparator(const Key2VectorIndexPairT& pair1, const Key2VectorIndexPairT& pair2)
{
return pair1.first < pair2.first;
}
...
Key2VectorIndexPairVectorT sortedVector;
sortedVector.reserve(originalVector.capacity());
// Assume "original" vector contains unsorted elements.
for (int i = 0; i < originalVector.size(); ++i)
{
const TheStruct& theStruct = originalVector[i];
sortedVector.insert(Key2VectorIndexPairT(theStruct.key, i));
}
std::sort(sortedVector.begin(), sortedVector.end(), Key2VectorIndexPairComparator);
...
const long keyToSearchFor = 20;
const Key2VectorIndexPairVectorT::const_iterator cItorKey2VectorIndexPairVector = std::lower_bound(sortedVector.begin(), sortedVector.end(), Key2VectorIndexPairT(keyToSearchFor, 0 /* Provide dummy index value for search */), Key2VectorIndexPairComparator);
if (cItorKey2VectorIndexPairVector->first == keyToSearchFor)
{
const int vectorIndex = cItorKey2VectorIndexPairVector->second;
const TheStruct& theStruct = originalVector[vectorIndex];
// Now do whatever you want...
}
else
{
// Could not find element...
}
这给我带来了适度的性能提升。以前我计算的总时间是3.75毫秒,现在降到了2.5毫秒。
std::map和std::set都建立在一个二进制树上,因此添加项可以进行动态内存分配。如果您的映射基本上是静态的(即在开始时初始化一次,然后很少或从未添加或删除新项目(,那么您可能最好使用排序向量和std::lower_bound来使用二进制搜索查找项目。
地图需要花费大量时间,原因有两个
- 您需要为数据存储分配大量内存
- 您需要对排序执行O(n-lgn(比较
如果您只是将其作为一个批创建,那么抛出整个映射,使用自定义池分配器可能是一个好主意,例如boost的pool_alloc。自定义分配器还可以应用优化,例如在映射完全销毁之前不实际释放任何内存,等等。
由于密钥是整数,您可能还需要考虑基于基数树(在密钥的位上(编写自己的容器。这可能会显著提高性能,但由于没有STL实现,您可能需要编写自己的实现。
如果您不需要对数据进行排序,请使用哈希表,例如std::unordered_map
;这样可以避免排序数据所需的大量开销,还可以减少所需的内存分配量。
最后,根据程序的整体设计,简单地重用同一个映射可能会有所帮助,而不是一遍又一遍地重新创建它。只需根据需要删除和添加关键点,而不是构建新的矢量,然后构建新的贴图。同样,这在你的程序中可能是不可能的,但如果是的话,它肯定会对你有所帮助。
我怀疑是内存管理和树重新平衡让你在这里付出了代价。
显然,分析可以帮助您确定问题所在。
一般来说,我建议将所需的long/int数据复制到另一个向量中,因为你说它几乎已经排序了,所以在它上使用stable_sort来完成排序。然后使用lower_bound来定位排序向量中的项目。
std::find是一种线性扫描(必须是这样,因为它适用于未排序的数据(。如果您可以对数据进行排序(std::sort保证n个log(n(行为(,那么您可以使用std::binary_search来获取log(n个(搜索。但正如其他人指出的那样,复制时间可能是问题所在。
如果键又长又短,可以尝试std::hash_map
。来自MSDN的hash_map类页面:
散列比排序的主要优点是效率更高;一成功的哈希在中执行插入、删除和查找与与用于排序的容器中元素数的对数技术。
如果您正在创建一个大型映射并将大块数据复制到其中,则映射创建可能会成为性能瓶颈(从某种意义上说,这需要大量的时间(
myMap.insert(std::make_pair(theType.key, theType));
这应该会提高插入速度,但如果遇到重复键,则会导致行为发生轻微变化-使用insert
会导致删除重复键的值,而使用您的方法,会将具有重复键的最后一个元素插入到映射中。
如果您的分析结果确定复制元素的成本很高,我也会考虑避免复制数据(例如,通过存储指向它的指针(。但为此,你必须对代码进行分析,IME的猜测往往是错误的。。。
此外,作为附带说明,您可能需要考虑使用自定义比较器将数据存储在std::集中,因为您已经包含了键。然而,这并不会真正导致速度的大幅提高,因为在这种情况下构建集合可能会像将其插入地图一样昂贵。
我不是C++专家,但您的问题似乎源于复制Type
实例,而不是指向Type
实例的引用/指针。
std::map<Type> myMap; // <-- this is wrong, since std::map requires two template parameters, not one
如果向映射中添加元素,但它们不是指针,那么我相信复制构造函数会被调用,这肯定会导致大型数据结构的延迟。改为使用指针:
std::map<KeyType, ObjectType*> myMap;
此外,您的示例有点令人困惑,因为当您期望Type
类型的值时,您在映射中"插入"了int
类型的值。我认为你应该将引用指定给项目,而不是索引。
myMap[theType.key] = &myVector[i];
更新:
我越看你的例子,就越困惑。如果您使用的是std::map,那么它应该采用两种模板类型:
map<T1,T2> aMap;
那么,你真正在映射什么呢?map<Type, int>
还是其他什么?
似乎您正在使用Type.key
成员字段作为映射的键(这是一个有效的想法(,但除非键的类型与Type
相同,否则您不能将其用作映射的键。那么key
是Type
的一个实例吗??
此外,您正在将当前矢量索引映射到映射中的关键点,这表明您只需要将索引映射到矢量,以便以后可以快速访问该索引位置。这就是你想做的吗?
更新2.0:
在阅读你的答案后,你似乎在使用std::map<long,int>
,在这种情况下,没有涉及到结构的复制。此外,您不需要对向量中的对象进行局部引用。如果您只需要访问密钥,请调用myVector[i].key
进行访问。
您根据您给出的坏例子构建了一个表的副本,而不仅仅是一个引用。
为什么可以';我是否将引用存储在C++中的STL映射中?
无论你在地图中存储什么,它都依赖于你不改变矢量。仅尝试查找映射。
typedef vector<Type> Stuff;
Stuff myVector;
typedef std::map<long, *Type> LookupMap;
LookupMap myMap;
LookupMap::iterator hint = myMap.begin();
for (Stuff::iterator it = myVector.begin(); myVector.end() != it; ++it)
{
hint = myMap.insert(hint, std::make_pair(it->key, &*it));
}
或者把矢量放在地图上??
由于向量已经部分排序,您可能需要创建一个辅助数组,引用原始向量中的元素(的索引(。然后,您可以使用Timsort对辅助数组进行排序,Timsort对于部分排序的数据(例如您的数据(具有良好的性能。
我认为您还有其他问题。创建一个1500个<long, int>
对的向量,并根据长度对其进行排序,所需时间应该大大少于0.8毫秒(至少假设我们谈论的是一个相当现代的桌面/服务器类型的处理器(。
为了了解我们应该在这里看到什么,我做了一个快速的测试代码:
#include <vector>
#include <algorithm>
#include <time.h>
#include <iostream>
int main() {
const int size = 1500;
const int reps = 100;
std::vector<std::pair<long, int> > init;
std::vector<std::pair<long, int> > data;
long total = 0;
// Generate "original" array
for (int i=0; i<size; i++)
init.push_back(std::make_pair(rand(), i));
clock_t start = clock();
for (int i=0; i<reps; i++) {
// copy the original array
std::vector<std::pair<long, int> > data(init.begin(), init.end());
// sort the copy
std::sort(data.begin(), data.end());
// use data that depends on sort to prevent it being optimized away
total += data[10].first;
total += data[size-10].first;
}
clock_t stop = clock();
std::cout << "Ignore: " << total << "n";
clock_t ticks = stop - start;
double seconds = ticks / (double)CLOCKS_PER_SEC;
double ms = seconds * 1000.0;
double ms_p_iter = ms / reps;
std::cout << ms_p_iter << " ms/iteration.";
return 0;
}
在我有点"落后"(大约5年前(的机器上运行这个程序,每次迭代的次数大约为0.1毫秒。我希望在这里搜索(使用std::lower_bound
或std::upper_bound
(也比在std::map
中搜索快一些(因为向量中的数据是连续分配的,我们可以期待更好的引用位置,从而更好地使用缓存(。
感谢大家的回复。我最终创建了一个成对的向量(std::vector of std::pair(long,int((。然后我按照长值对向量进行排序。我创建了一个自定义比较器,只查看这对的第一部分。然后我用下界搜索这对。以下是我的做法:
typedef std::pair<long,int> Key2VectorIndexPairT;
typedef std::vector<Key2VectorIndexPairT> Key2VectorIndexPairVectorT;
bool Key2VectorIndexPairComparator(const Key2VectorIndexPairT& pair1, const Key2VectorIndexPairT& pair2)
{
return pair1.first < pair2.first;
}
...
Key2VectorIndexPairVectorT sortedVector;
sortedVector.reserve(originalVector.capacity());
// Assume "original" vector contains unsorted elements.
for (int i = 0; i < originalVector.size(); ++i)
{
const TheStruct& theStruct = originalVector[i];
sortedVector.insert(Key2VectorIndexPairT(theStruct.key, i));
}
std::sort(sortedVector.begin(), sortedVector.end(), Key2VectorIndexPairComparator);
...
const long keyToSearchFor = 20;
const Key2VectorIndexPairVectorT::const_iterator cItorKey2VectorIndexPairVector = std::lower_bound(sortedVector.begin(), sortedVector.end(), Key2VectorIndexPairT(keyToSearchFor, 0 /* Provide dummy index value for search */), Key2VectorIndexPairComparator);
if (cItorKey2VectorIndexPairVector->first == keyToSearchFor)
{
const int vectorIndex = cItorKey2VectorIndexPairVector->second;
const TheStruct& theStruct = originalVector[vectorIndex];
// Now do whatever you want...
}
else
{
// Could not find element...
}
这给我带来了适度的性能提升。以前我计算的总时间是3.75毫秒,现在降到了2.5毫秒。
- 如何创建对象函数指针C++映射?
- 如何在 c++ 中创建结构向量的映射
- 我如何创建一个函数(),其中一帕雷米特是映射
- 通过从矢量中获取键和与映射中的键对应的值来创建映射
- 使用结构和映射创建priority_queue
- 为C++重载函数创建SWIG类型映射
- 使用Regex解析cpp中的字符串并创建映射
- 我正在尝试创建一个C++映射,该映射在boost内存映射文件中具有向量值
- 创建以向量/列表迭代器为键的映射
- Rust 在索引时是否隐式创建映射条目,例如C++
- 通过读取文件创建映射<字符串,矢量>时如何管理内存<string>
- 如何使用迭代器值创建映射
- 如何从userland中的分段内存创建映射
- 在C++中为数百万个对象创建映射
- 在 Android NDK 上创建映射/直接 OpenGL ES 纹理
- 如何在 vim 中创建映射以自动执行 .h c++ 文件的 ifdef 命令
- 创建映射元素时,请避免使用值副本
- 有效地创建映射与单一条目不同
- 在c++中从字典文件创建映射的映射
- 增强如何为类型选择创建映射