Оптимизация кода 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 С вашей хэш-функции вместо.