优化时间性能unordered_map c ++

时间:2017-11-23 21:44:21

标签: c++ performance sqlite time

我陷入了优化问题。我有一个巨大的数据库(大约16M条目),表示由不同用户给予不同项目评分。从这个数据库,我必须评估不同用户之间的相关性度量(即我必须实现相似性矩阵)。幸运的是,这个相关矩阵是对称的,所以我只需计算其中的一半。

让我把重点放在矩阵的第一列:总共有135k用户,所以我保留了一个用户,我找到了这个用户和所有其他用户之间的所有常用评级项目(带有for循环) 。如果我将单个用户与20k其他用户而不是135k进行比较,也会出现时间问题。

我的方法如下:首先我查询数据库以获取前20k用户的所有数据(这也需要时间索引实现,但它不会打扰我我只做了一次)并且我使用userID作为键将所有内容存储在无序地图中;然后对于这个unordered_map,我使用另一个unordered_map来存储用户给出的所有评级,这次使用itemID作为关键字。

然后,为了找到两个已经评级的项目组,我循环用户评分较少的项目,搜索另一个项目是否也评价相同的项目。我知道最快的数据结构是散列图,但是对于一个完整的列,我的算法需要30s(仅适用于20k条目),对于完整的矩阵,其转换为 WEEKS

代码如下:

void similarity_matrix(sqlite3 *db, sqlite3 *db_avg, sqlite3 *similarity, long int tot_users, long int interval) {

long int n = 1;
double sim;
string temp_s;
vector<string> insert_query;
sqlite3_stmt *stmt;

std::cout << "Starting creating similarity matrix..." << std::endl;

string query_string = "SELECT * from usersratings where usersratings.user <= 20000;";
unordered_map<int, unordered_map<int, int>> users_map = db_query(query_string.c_str(), db);
std::cout << "Query time: " << duration_ << " s." << std::endl;

unordered_map<int, int> u1_map = users_map[1];

string select_avg = "SELECT * from averages;";
unordered_map<int, double> avg_map = avg_value(select_avg.c_str(), db_avg);


for (int i = 2; i <= tot_users; i++)
{
    unordered_map<int, int> user;
    int compare_id;

    if (users_map[i].size() <= u1_map.size()) {
        user = users_map[i];
        compare_id = 1;
    }
    else {
        user = u1_map;
        compare_id = i;
    }

    int matches = 0;
    double newnum = 0;
    double newden1 = 0;
    double newden2 = 0;

    unordered_map<int, int> item_map = users_map[compare_id];
    for (unordered_map<int, int>::iterator it = user.begin(); it != user.end(); ++it)
    {
        if (item_map.size() != 0) {
            int rating = item_map[it->first];
            if (rating != 0) {
                double diff1 = (it->second - avg_map[1]);
                double diff2 = (rating - avg_map[i]);
                newnum += diff1 * diff2;
                newden1 += pow(diff1, 2);
                newden2 += pow(diff2, 2);
            }
        }

    }
    sim = newnum / (sqrt(newden1) * sqrt(newden2));
}

std::cout << "Execution time for first column: " << duration << " s." << std::endl;
std::cout << "First column finished..." << std::endl;

}

1 个答案:

答案 0 :(得分:0)

这对我来说是一个直接的潜在表现陷阱:

unordered_map<int, unordered_map<int, int>> users_map = db_query(query_string.c_str(), db);

如果每个用户的每个子地图的大小都接近用户数量,那么你就会得到二次复杂度算法,当你拥有的用户越多,这个算法的指数就越小。

unordered_map确实提供了定时搜索,但仍然是搜索。执行此操作所需的指令量将使数组索引的成本相形见绌,特别是如果每​​次尝试搜索地图时存在许多意味着内部循环的冲突。它也不一定以允许最快顺序迭代的方式表示。因此,如果您只能使用std::vector至少子列表,avg_map就像这样,那对初学者来说应该会有很多帮助:

typedef pair<int, int> ItemRating;
typedef vector<ItemRating> ItemRatings;
unordered_map<int, ItemRatings> users_map = ...;
vector<double> avg_map = ...;

即使外部users_map可能是vector,除非它是稀疏的并且并非所有索引都被使用。如果它稀疏并且用户ID的范围仍然适合合理的范围(不是天文大的整数),则可能构造两个向量 - 一个存储用户数据并且具有与数量成比例的大小。用户,而另一个与用户的有效索引范围成比例,并且如果您需要能够通过用户ID访问用户数据,则只需将索引存储到前一个向量中即可从用户ID转换为具有简单数组查找的索引

// User data array.
vector<ItemRatings> user_data(num_users);

// Array that translates sparse user ID integers to indices into the 
// above dense array. A value of -1 indicates that a user ID is not used.
// To fetch user data for a particular user ID, we do: 
// const ItemRatings& ratings = user_data[user_id_to_index[user_id]];
vector<int> user_id_to_index(biggest_user_index+1, -1);

对于外循环的每次迭代,你也不必要地复制那些unordered_maps。虽然我不认为这是最大瓶颈的来源,但这有助于避免深度复制您甚至不使用引用或指针修改的数据结构:

// Shallow copy, don't deep copy big stuff needlessly.
const unordered_map<int, int>& user = users_map[i].size() <= u1_map.size() ?                                     
                                      users_map[i]: u1_map;
const int compare_id = users_map[i].size() <= u1_map.size() ? 1: i;
const unordered_map<int, int>& item_map = users_map[compare_id];
...

您也不需要检查内循环中item_map是否为空。该检查应该在外面悬挂。这是一项微观优化,根本不可能提供太多帮助,但仍然可以消除明显的浪费。

第一次传递后的最终代码将是这样的:

vector<ItemRatings> user_data = ..;
vector<double> avg_map = ...;

// Fill `rating_values` with the values from the first user.
vector<int> rating_values(item_range, 0);
const ItemRatings& ratings1 = user_data[0];
for (auto it = ratings1.begin(); it != ratings1.end(); ++it)
{
    const int item = it->first;
    const int rating = it->second;
    rating_values[item] += rating;
}

// For each user starting from the second user:
for (int i=1; i < tot_users; ++i)
{
    double newnum = 0;
    double newden1 = 0;
    double newden2 = 0;

    const ItemRatings& ratings2 = user_data[i];
    for (auto it = ratings2.begin(); it != ratings2.end(); ++it)
    {
        const int item = it->first;
        const int rating1 = rating_values[it->first];
        if (rating != 0) {
            const int rating2 = it->second;
            double diff1 = rating2 - avg_map[1];
            double diff2 = rating1 - avg_map[i];
            newnum += diff1 * diff2;
            newden1 += pow(diff1, 2);
            newden2 += pow(diff2, 2);
        }
    }
    sim = newnum / (sqrt(newden1) * sqrt(newden2));
}

上述代码的最大区别在于我们通过unordered_map删除了所有搜索,并将其替换为数组的简单索引访问。我们还消除了大量冗余的数据结构复制。