Подтвердить что ты не робот

Оптимизация кода на С++ (который использует UnorderedMap и Vector)

Я пытаюсь оптимизировать часть кода на С++, которая занимает много времени (следующая часть кода занимает около 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;
        }
   }
}
4b9b3361

Ответ 1

Вы делаете много бесполезных операций... если я правильно понимаю, упрощенная форма может быть простой:

void HashTable::add(const PointObject& vector) {
   hashTableMap[hash(vector)].push_back(vector);    
}

Это работает, потому что

  • Карта при доступе с помощью operator[] создаст инициализированное по умолчанию значение, если оно еще не присутствует на карте
  • Значение (a std::vector) возвращается ссылкой, поэтому вы можете непосредственно push_back указать на него точку. Этот std::vector будет либо вновь вставленным, либо ранее существующим, если ключ уже был на карте.

Обратите внимание также, что в зависимости от размера PointObject и других факторов возможно более эффективно передавать vector по значению вместо const PointObject&. Это такая микро-оптимизация, что, однако, требует, чтобы профилирование выполнялось разумно.

Ответ 2

Вместо вызова hashTableMap.count(combinedHash) и hashTableMap.find(combinedHash) лучше вставить новый элемент и проверить, что insert() возвращено:

В версиях (1) и (2) функция возвращает парный объект, чья Первый элемент - это итератор, указывающий либо на вновь вставленный элемент в контейнере или элемент, ключ которого эквивалентен, и значение bool, указывающее, был ли элемент успешно вставлен или нет.

Кроме того, не передавайте объекты по значению, где вам не нужно. Лучше передать его указателем или ссылкой. Это:

std::vector<PointObject> pointVectorList = it->second;

неэффективен, так как он создаст ненужную копию вектора.

Ответ 3

Без if попробуйте вставить пустую запись в хеш-таблицу:

auto ret = hashTableMap.insert(
   std::make_pair(combinedHash, std::vector<PointObject>());

Будет добавлена ​​новая пустая запись или будет получена уже существующая запись. В вашем случае вам не нужно проверять, в чём дело, вам просто нужно взять возвращенный итератор и добавить новый элемент:

auto &pointVectorList = *ret.first;
pointVectorList.push_back(vector);

Ответ 4

Этот .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*>> (указатель), вы даже можете избежать этой последней операции копирования.

Ответ 5

Ваша самая большая проблема заключается в том, что вы копируете весь вектор (и каждый элемент этого вектора) дважды в части 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 с вашей хеш-функцией.

Ответ 6

Использование std::unordered_map здесь не представляется возможным - вы используете int from hash в качестве ключа (предположительно) хеш 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;
}

Ответ 7

Предполагая, что PointObject большой, а копии его дороги, std::move - ваш друг здесь. Вы хотите убедиться, что PointObject поддерживает перемещение (либо не определяет деструктор, либо оператор копирования, либо сам оператор move-constructor и move-assign).

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);
        }
   }
}