优化C++代码(使用无序映射和矢量)
Optimization of a C++ code (that uses UnorderedMap and Vector)
我正在尝试优化C++代码的某些部分,这需要很长时间(对于 X 量的数据,以下部分代码大约需要 19 秒,我试图在不到 5 秒的时间内完成整个过程对于相同数量的数据 - 基于我拥有的一些基准(。我有一个函数"add",我已经在这里编写并复制了代码。我将尝试尽可能多地解释我认为理解代码所需的内容。如果我错过了什么,请告诉我。
对于 X 个数据条目,以下函数 add 称为 X 次。
void HashTable::add(PointObject vector) // PointObject is a user-defined object
{
int combinedHash = hash(vector); // the function "hash" takes less than 1 second for X amount of data
// hashTableMap is an unordered_map<int, std::vector<PointObject>>
if (hashTableMap.count(combinedHash) == 0)
{
// if the hashmap does not contain the combinedHash key, then
// add the key and a new vector
std::vector<PointObject> pointVectorList;
pointVectorList.push_back(vector);
hashTableMap.insert(std::make_pair(combinedHash, pointVectorList));
}
else
{
// otherwise find the key and the corresponding vector of PointObjects and add the current PointObject to the existing vector
auto it = hashTableMap.find(combinedHash);
if (it != hashTableMap.end())
{
std::vector<PointObject> pointVectorList = it->second;
pointVectorList.push_back(vector);
it->second = pointVectorList;
}
}
}
你正在做很多无用的操作...如果我理解正确,简化的形式可以简单地:
void HashTable::add(const PointObject& vector) {
hashTableMap[hash(vector)].push_back(vector);
}
这是有效的,因为
operator[]
使用地图访问地图时,
如果地图中尚不存在默认初始化值,则该地图中尚不存在该值,则该地图将创建该地图- 值(
std::vector
(通过引用返回,因此您可以直接push_back
传入点到它。此std::vector
将是新插入的密钥或先前存在的密钥(如果密钥已在映射中(。
另请注意,根据PointObject
的大小和其他因素,按值传递vector
可能比按const PointObject&
传递更有效。然而,这是一种微优化,需要明智地执行分析。
与其调用hashTableMap.count(combinedHash)
和hashTableMap.find(combinedHash)
,不如插入新元素并检查insert()
返回的内容:
在版本 (1( 和 (2( 中,该函数返回一个对对象,其 第一个元素是指向新插入的迭代器 元素或键等效的元素, 以及指示元素是否成功的布尔值 插入与否。
此外,不要按值传递对象,因为您不必这样做。最好通过指针或引用传递它。这:
std::vector<PointObject> pointVectorList = it->second;
效率低下,因为它会创建不必要的向量副本。
这个.count()
是完全没有必要的,你可以将你的函数简化为:
void HashTable::add(PointObject vector)
{
int combinedHash = hash(vector);
auto it = hashTableMap.find(combinedHash);
if (it != hashTableMap.end())
{
std::vector<PointObject> pointVectorList = it->second;
pointVectorList.push_back(vector);
it->second = pointVectorList;
}
else
{
std::vector<PointObject> pointVectorList;
pointVectorList.push_back(vector);
hashTableMap.insert(std::make_pair(combinedHash, pointVectorList));
}
}
您还在任何地方执行复制操作。复制对象非常耗时,请避免这样做。如果可能,还要使用引用和指针:
void HashTable::add(PointObject& vector)
{
int combinedHash = hash(vector);
auto it = hashTableMap.find(combinedHash);
if (it != hashTableMap.end())
{
it->second.push_back(vector);
}
else
{
std::vector<PointObject> pointVectorList;
pointVectorList.push_back(vector);
hashTableMap.insert(std::make_pair(combinedHash, pointVectorList));
}
}
这段代码可能可以进一步优化,但它需要了解hash()
,了解hashTableMap
的工作方式(顺便说一下,为什么它不是std::map
?(和一些实验。
如果hashTableMap
是一个std::map<int, std::vector<pointVectorList>>
,你可以将你的函数简化为:
void HashTable::add(PointObject& vector)
{
hashTableMap[hash(vector)].push_back(vector);
}
如果它是一个std::map<int, std::vector<pointVectorList*>>
(指针(,你甚至可以避免最后一次复制操作。
如果没有if
,请尝试在哈希表上插入一个空条目:
auto ret = hashTableMap.insert(
std::make_pair(combinedHash, std::vector<PointObject>());
将添加新的空白条目,或者检索已存在的条目。在您的情况下,您不需要检查哪种情况,您只需要获取返回的迭代器并添加新元素:
auto &pointVectorList = *ret.first;
pointVectorList.push_back(vector);
假设PointObject
很大并且复制它很昂贵,std::move
是您的朋友。您需要确保PointObject
是移动感知的(不要定义析构函数或复制运算符,或者自己提供移动构造函数和移动赋值运算符(。
void HashTable::add(PointObject vector) // PointObject is a user-defined object
{
int combinedHash = hash(vector); // the function "hash" takes less than 1 second for X amount of data
// hashTableMap is an unordered_map<int, std::vector<PointObject>>
if (hashTableMap.count(combinedHash) == 0)
{
// if the hashmap does not contain the combinedHash key, then
// add the key and a new vector
std::vector<PointObject> pointVectorList;
pointVectorList.push_back(std::move(vector));
hashTableMap.insert(std::make_pair(combinedHash, std::move(pointVectorList)));
}
else
{
// otherwise find the key and the corresponding vector of PointObjects and add the current PointObject to the existing vector
auto it = hashTableMap.find(combinedHash);
if (it != hashTableMap.end())
{
std::vector<PointObject> pointVectorList = it->second;
pointVectorList.push_back(std::move(vector));
it->second = std::move(pointVectorList);
}
}
}
使用std::unordered_map
似乎不合适 - 您使用hash
中的int
作为键(大概(是PointObject
的哈希而不是PointObject
本身。本质上是双重哈希。而且,如果您需要一个PointObject
来计算映射键,那么它根本不是真正的键!也许std::unordered_multiset
会是更好的选择?
首先定义哈希函数形式PointObject
namespace std
{
template<>
struct hash<PointObject> {
size_t operator()(const PointObject& p) const {
return ::hash(p);
}
};
}
然后像
#include <unordered_set>
using HashTable = std::unordered_multiset<PointObject>;
int main()
{
HashTable table {};
PointObject a {};
table.insert(a);
table.emplace(/* whatever */);
return 0;
}
你最大的问题是你在 else 部分复制了整个向量(以及该向量中的每个元素(两次:
std::vector<PointObject> pointVectorList = it->second; // first copy
pointVectorList.push_back(vector);
it->second = pointVectorList; // second copy
这意味着每次向现有向量添加元素时,您都会复制整个向量。
如果你使用对该向量的引用,你会做得更好:
std::vector<PointObject> &pointVectorList = it->second;
pointVectorList.push_back(vector);
//it->second = pointVectorList; // don't need this anymore.
附带说明一下,在你的unordered_map
中,你正在散列你的价值作为你的密钥。您可以改用哈希函数的unordered_set
。
- 递归无序映射
- 正在将无序映射设置为无序映射的值
- 智能指针作为无序映射键,并通过引用进行比较
- 打印无序映射的第二个元素,即集合
- 由并发无序映射查找线程调用的函数是否安全?
- 如何按值对无序哈希映射进行排序
- 将大数字(10-12 位数字)存储在无序映射中<字符串,整数>
- 使用无序映射在STL中存储键值对
- 为C++中的无序映射获取给定输入键的错误值
- 将大型对象存储在无序映射中是否效率低下
- 在无序映射的结构化绑定中推导类型
- 使用无序映射进行错误索引
- 将一个向量对放在一个无序映射与一个映射中
- 如何在c++中修改无序映射中的每个值
- C++:使用一对(cpp_int,int)整数作为无序映射中的键(其中cpp_int是boost多精度整数)
- 将 [] 索引到无序映射时出现段错误
- 以std::字符串作为关键字,按字典顺序对一个无序映射进行排序
- 无序映射存储桶的节点大小
- 在无序映射<字符串上使用 find(),向量<string>>与 C++ 中的有序映射花费相同的时间
- 以三个无符号字符为关键字的无序映射