Оптимизация кода C++ (который использует UnorderedMap и Vector)
Я пытаюсь оптимизировать некоторую часть кода на C++, которая занимает много времени (следующая часть кода занимает около 19 секунд для X объема данных, и я пытаюсь завершить весь процесс менее чем за 5 секунд для того же объема данных на основе некоторых тестов, которые у меня есть). У меня есть функция "добавить", которую я написал и скопировал код здесь. Я постараюсь объяснить как можно больше, что, по моему мнению, необходимо для понимания кода. Пожалуйста, дайте мне знать, если я пропустил что-то.
следующая функция add вызывается X раз для 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;
}
}
}
7 ответов
Вы делаете много бесполезных операций... если я правильно понимаю, упрощенная форма может быть просто:
void HashTable::add(const PointObject& vector) {
hashTableMap[hash(vector)].push_back(vector);
}
это работает, потому что
- карта при доступе с помощью
operator[]
создаст инициализированное по умолчанию значение, если оно еще не присутствует на карте - значение (an
std::vector
) возвращается по ссылке, поэтому вы можете напрямуюpush_back
входящая точка к нему. Этоstd::vector
будет либо недавно вставленным, либо ранее существующий, если ключ уже был на карте.
Отметим также, что в зависимости от размера PointObject
и другие факторы, возможно, было бы более эффективно пройти vector
по значению, а не const PointObject&
. Это своего рода микро-оптимизация, которая, однако, требует, чтобы профилирование выполнялось разумно.
вместо hashTableMap.count(combinedHash)
и hashTableMap.find(combinedHash)
, лучше просто вставить новый элемент и проверить, что insert()
вернулся:
в версиях (1) и (2) функция возвращает объект pair, первый элемент-это итератор, указывающий либо вставленной элемент в контейнере или элементу, ключ которого эквивалентен, и значение bool, указывающее, был ли элемент успешно вставлено или не.
кроме того, не передавайте объекты по значению, где вам не нужно. Лучше передать его по указателю или по ссылке. Это:
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
не кажется подходящим здесь - вы используете int
С hash
как ключ (предположительно) - это хэш-код PointObject
, а не . По существу двойное хэширование. А также, если вам нужно PointObject
для того, чтобы вычислить ключ карты, то это не совсем ключ! Возможно!--8--> было бы лучшим выбором?
сначала определите форму хэш-функции 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;
}
код большой проблема заключается в том, что ты копирование всего вектора (и каждого элемента в этом векторе) дважды в другой части:
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
С вашей хэш-функции вместо.