以下代码遍历许多数据行,每行计算一些分数,然后根据该分数对行进行排序:
unsigned count = 0;
score_pair* scores = new score_pair[num_rows];
while ((row = data.next_row())) {
float score = calc_score(data.next_feature())
scores[count].score = score;
scores[count].doc_id = row->docid;
count++;
}
assert(count <= num_rows);
qsort(scores, count, sizeof(score_pair), score_cmp);
不幸的是,有许多重复的行具有相同的docid但得分不同。现在我想保留任何docid的最后得分。 docids是unsigned int,但通常很大(=&gt;没有查找数组) - 使用HashMap查找docid的最后一次计数可能太慢了(数百万行,只需要几秒钟而不是几分钟...... )。
好的,我修改了我的代码以使用std:map:
map<int, int> docid_lookup;
unsigned count = 0;
score_pair* scores = new score_pair[num_rows];
while ((row = data.next_row())) {
float score = calc_score(data.next_feature())
map<int, int>::iterator iter;
iter = docid_lookup.find(row->docid);
if (iter != docid_lookup.end()) {
scores[iter->second].score = score;
scores[iter->second].doc_id = row->docid;
} else {
scores[count].score = score;
scores[count].doc_id = row->docid;
docid_lookup[row->docid] = count;
count++;
}
}
它的工作原理和性能的打击并不像我预期的那么糟糕 - 现在它运行一分钟而不是16秒,所以它大约是3倍。内存使用率也从1Gb上升到4Gb。
答案 0 :(得分:3)
我要尝试的第一件事就是map
或unordered_map
:如果性能比没有任何独特化处理的情况慢60倍,我会感到惊讶。如果表现不可接受,另一种选择是这样的:
// get the computed data into a vector
std::vector<score_pair>::size_type count = 0;
std::vector<score_pair> scores;
scores.reserve(num_rows);
while ((row = data.next_row())) {
float score = calc_score(data.next_feature())
scores.push_back(score_pair(score, row->docid));
}
assert(scores.size() <= num_rows);
// remove duplicate doc_ids
std::reverse(scores.begin(), scores.end());
std::stable_sort(scores.begin(), scores.end(), docid_cmp);
scores.erase(
std::unique(scores.begin(), scores.end(), docid_eq),
scores.end()
);
// order by score
std::sort(scores.begin(), scores.end(), score_cmp);
请注意,使用reverse和stable_sort是因为您需要每个doc_id的最后得分,但std :: unique保留第一个得分。如果你想获得第一个分数,你可以使用stable_sort,如果你不关心什么分数,你可以使用sort。
处理此问题的最佳方法可能是将反向迭代器传递给std :: unique,而不是单独的反向操作。但是我没有信心我可以在没有测试的情况下正确编写,错误可能会让人感到困惑,所以你得到了未经优化的代码...
编辑:只是为了与您的代码进行比较,以下是我如何使用地图:
std::map<int, float> scoremap;
while ((row = data.next_row())) {
scoremap[row->docid] = calc_score(data.next_feature());
}
std::vector<score_pair> scores(scoremap.begin(), scoremap.end());
std::sort(scores.begin(), scores.end(), score_cmp);
请注意,score_pair需要一个构造函数取std::pair<int,float>
,这使得它不是POD。如果这是不可接受的,请使用std :: transform,并使用函数进行转换。
最后,如果存在大量重复(例如,每个doc_id平均有2个或更多条目),并且如果calc_score不重要,那么我将查看是否可以迭代{{1}的行以相反的顺序。如果是,那么它将加快data
/ map
方法,因为当您获得doc_id的命中时,您不需要计算该行的分数,只需删除它并移动上。
答案 1 :(得分:2)
我要去std :: docids地图。如果您可以创建适当的散列函数,则最好使用散列映射。但我想这太难了。并且没有 - std :: map不会太慢。访问是O(log n),几乎和O(1)一样好。 O(1)是数组访问时间(和Hashmap btw)。
顺便说一下,如果std :: map太慢,qsort O(n log n)也太慢了。并且,使用std :: map并迭代它的内容,你可以保存你的qsort。
评论的一些补充(bybyone):
简短 - 如果你问一般性问题,你会得到一般答案。这意味着 - 至少对我来说,看一下O-Notation的时间复杂度。仍然你是对的,根据不同的因素,std :: map可能太慢而qsort仍然足够快 - 在qsort的最坏情况下它也可能是另一种方式,它具有n ^ 2的复杂性。 / p>
答案 2 :(得分:1)
除非我误解了这个问题,否则解决方案可以大大简化。至少据我所知,你有几百万个docid(类型为unsigned int),对于每个唯一的docid,你想要存储一个'得分'(这是一个浮点数)。如果输入中同一个docid多次出现,您希望保留最后一个得分。如果这是正确的,代码可以简化为:
std::map<unsigned, float> scores;
while ((row = data.next_row()))
scores[row->docid] = calc_score(data.next_feature());
这可能比原始版本慢一些,因为它分配了很多单独的块而不是一块大的内存块。鉴于你的声明,docid中有很多重复,我希望这可以节省相当多的内存,因为它只存储每个唯一docid的数据而不是原始数据中的每一行。
如果你想优化它,你几乎肯定会这样做 - 因为它使用了很多小块,为此目的设计的自定义分配器可能会有所帮助。一种可能性是看看Andrei Alexandrescu的Loki图书馆中的小块分配器。从那以后他就这个问题做了更多的工作,但是Loki中的那个可能足以完成手头的任务 - 它几乎肯定会节省相当多的内存并且运行得更快。
答案 3 :(得分:1)
如果你的C ++实现有,并且大多数都有,请尝试hash_map
而不是std::map
(有时在std::hash_map
下可用)。
如果查找本身是您的计算瓶颈,那么这可能比std::map
的二叉树显着加速。
答案 4 :(得分:0)
为什么不首先按doc id排序,计算得分,然后对于重复项的任何子集使用最高得分?
重新阅读问题;我建议对如何读入分数进行修改。请记住,C ++不是我的母语,所以这不太可编译。
unsigned count = 0;
pair<int, score_pair>* scores = new pair<int, score_pair>[num_rows];
while ((row = data.next_row())) {
float score = calc_score(data.next_feature())
scores[count].second.score = score;
scores[count].second.doc_id = row->docid;
scores[count].first = count;
count++;
}
assert(count <= num_rows);
qsort(scores, count, sizeof(score_pair), pair_docid_cmp);
//getting number of unique scores
int scoreCount = 0;
for(int i=1; i<num_rows; i++)
if(scores[i-1].second.docId != scores[i].second.docId) scoreCount++;
score_pair* actualScores=new score_pair[scoreCount];
int at=-1;
int lastId = -1;
for(int i=0; i<num_rows; i++)
{
//if in first entry of new doc id; has the last read time by pair_docid_cmp
if(lastId!=scores[i].second.docId)
actualScores[++at]=scores[i].second;
}
qsort(actualScores, count, sizeof(score_pair), score_cmp);
pair_docid_cmp首先在docid上进行比较;将相同的文档分组在一起,然后按逆序读取;这样读取的最后一项是具有相同docid的项目子列表中的第一项。应该只有~5 / 2x内存使用量,并且〜执行速度加倍。