我陷入了优化问题。我有一个巨大的数据库(大约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;
}
答案 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
删除了所有搜索,并将其替换为数组的简单索引访问。我们还消除了大量冗余的数据结构复制。