只是为了澄清我也认为标题有点傻。我们都知道语言的大多数内置函数都写得很好而且速度很快(有些甚至是汇编编写的)。虽然可能对我的情况仍有一些建议。我有一个小项目,展示了搜索引擎的工作。在索引阶段,我有一个过滤方法来从关键字中过滤掉不必要的东西。就在这里:
bool Indexer::filter(string &keyword)
{
// Remove all characters defined in isGarbage method
keyword.resize(std::remove_if(keyword.begin(), keyword.end(), isGarbage) - keyword.begin());
// Transform all characters to lower case
std::transform(keyword.begin(), keyword.end(), keyword.begin(), ::tolower);
// After filtering, if the keyword is empty or it is contained in stop words list, mark as invalid keyword
if (keyword.size() == 0 || stopwords_.find(keyword) != stopwords_.end())
return false;
return true;
}
首先,这些函数(alls是STL容器或标准函数的成员函数)应该是快速的,并且在索引阶段不需要花费很多时间。但在与Valgrind进行分析之后,这个filter
的包容性成本是荒谬的高:33.4%。此过滤器有三个标准函数占用该百分比的大部分时间:std::remove_if
占6.53%,std::set::find
占15.07%,std::transform
占7.71%。
因此,如果有任何我可以做(或更改)以减少此过滤器的指令时间成本(如使用并行化或类似的东西),请给我你的建议。提前致谢。
更新:感谢你的所有建议。所以简而言之,我总结一下我需要做的是:
1)通过构造我自己的循环将tolower
和remove_if
合并为一个。
2)使用unordered_set
代替set
以获得更快的find
方法。
因此,我选择Mark_B
作为正确答案。
答案 0 :(得分:2)
首先,您确定在编译时启用了优化和内联吗?
假设是这种情况,我首先尝试编写自己的变换器,将垃圾和低层套管组合成一个步骤,以防止第二次迭代关键字。
如果没有使用评论中建议的unordered_set
之类的其他容器,则无法对此查找做很多事情。
您的应用程序是否有可能真正进行过滤只是CPU操作中的一部分?
答案 1 :(得分:2)
如果使用boost过滤器迭代器,则可以将remove_if
和transform
合并为一个,例如(未经测试):
keyword.erase(std::transform(boost::make_filter_iterator(!boost::bind(isGarbage), keyword.begin(), keyword.end()),
boost::make_filter_iterator(!boost::bind(isGarbage), keyword.end(), keyword.end()),
keyword.begin(),
::tolower), keyword.end());
这假设您希望修改字符串的副作用仍然在外部可见,否则通过const
引用而只需使用count_if
和谓词来完成所有操作。您可以为停止词列表构建分层数据结构(基本上是树),使“就地”匹配成为可能,例如,如果您的停用词是SELECT, SELECTION, SELECTED
,您可以构建树:
|- (other/empty accept) \- S-E-L-E-C-T- (empty, fail) |- (other, accept) |- I-O-N (fail) \- E-D (fail)
您可以在转换和过滤的同时遍历这样的树结构,而无需对字符串本身进行任何修改。实际上,您需要将多字符运行压缩到树中的单个节点(可能)。
您可以通过以下方式轻松构建此类数据结构:
#include <iostream>
#include <map>
#include <memory>
class keywords {
struct node {
node() : end(false) {}
std::map<char, std::unique_ptr<node>> children;
bool end;
} root;
void add(const std::string::const_iterator& stop, const std::string::const_iterator c, node& n) {
if (!n.children[*c])
n.children[*c] = std::unique_ptr<node>(new node);
if (stop == c+1) {
n.children[*c]->end = true;
return;
}
add(stop, c+1, *n.children[*c]);
}
public:
void add(const std::string& str) {
add(str.end(), str.begin(), root);
}
bool match(const std::string& str) const {
const node *current = &root;
std::string::size_type pos = 0;
while(current && pos < str.size()) {
const std::map<char,std::unique_ptr<node>>::const_iterator it = current->children.find(str[pos++]);
current = it != current->children.end() ? it->second.get() : nullptr;
}
if (!current) {
return false;
}
return current->end;
}
};
int main() {
keywords list;
list.add("SELECT");
list.add("SELECTION");
list.add("SELECTED");
std::cout << list.match("TEST") << std::endl;
std::cout << list.match("SELECT") << std::endl;
std::cout << list.match("SELECTOR") << std::endl;
std::cout << list.match("SELECTED") << std::endl;
std::cout << list.match("SELECTION") << std::endl;
}
这就像你希望并给予的那样有效:
0 1 0 1 1
然后只需要修改match()
来适当调用转换和过滤函数,例如:
const char c = str[pos++];
if (filter(c)) {
const std::map<char,std::unique_ptr<node>>::const_iterator it = current->children.find(transform(c));
}
您可以对此进行一些优化(紧凑的长单字符串运行)并使其更通用,但它显示了如何在一次通过中就地执行所有操作,并且这是加速您显示的功能的最可能的候选者
(当然是基准变化)
答案 2 :(得分:1)
您可以通过单个传递字符串来更快地完成此操作,忽略垃圾字符。像这样的东西(伪代码):
std::string normalizedKeyword;
normalizedKeyword.reserve(keyword.size())
for (auto p = keyword.begin(); p != keyword.end(); ++p)
{
char ch = *p;
if (!isGarbage(ch))
normalizedKeyword.append(tolower(ch));
}
// then search for normalizedKeyword in stopwords
这应该可以消除std::remove_if
的开销,尽管存在内存分配以及将字符复制到normalizedKeyword
的一些新开销。
答案 3 :(得分:1)
如果对isGarbage()的调用不需要同步,那么并行化应该是第一个要考虑的优化(当然,过滤一个关键字是一个足够大的任务,否则并行化应该高一级)。以下是如何完成的 - 一次通过原始数据,使用线程构建模块进行多线程:
bool isGarbage(char c) {
return c == 'a';
}
struct RemoveGarbageAndLowerCase {
std::string result;
const std::string& keyword;
RemoveGarbageAndLowerCase(const std::string& keyword_) : keyword(keyword_) {}
RemoveGarbageAndLowerCase(RemoveGarbageAndLowerCase& r, tbb::split) : keyword(r.keyword) {}
void operator()(const tbb::blocked_range<size_t> &r) {
for(size_t i = r.begin(); i != r.end(); ++i) {
if(!isGarbage(keyword[i])) {
result.push_back(tolower(keyword[i]));
}
}
}
void join(RemoveGarbageAndLowerCase &rhs) {
result.insert(result.end(), rhs.result.begin(), rhs.result.end());
}
};
void filter_garbage(std::string &keyword) {
RemoveGarbageAndLowerCase res(keyword);
tbb::parallel_reduce(tbb::blocked_range<size_t>(0, keyword.size()), res);
keyword = res.result;
}
int main() {
std::string keyword = "ThIas_iS:saome-aTYpe_Ofa=MoDElaKEYwoRDastrang";
filter_garbage(keyword);
std::cout << keyword << std::endl;
return 0;
}
当然,最终的代码可以通过避免数据复制来进一步改进,但样本的目标是证明它是一个容易解决的问题。
答案 4 :(得分:0)
这里的问题不是标准功能,而是你对它们的使用。当你显然只需要做一个时,你就会对你的字符串进行多次传递。
您需要做的事情可能无法通过算法直接完成,您需要通过提升或滚动自己的帮助。
您还应该仔细考虑是否确实需要调整字符串大小。是的,你可能会节省一些空间,但这会让你付出代价。单独删除它可能会占用您的操作费用。
答案 5 :(得分:0)
这是一种将垃圾清除和下部套管组合成一个步骤的方法。它不适用于UTF-8等多字节编码,但原始代码也没有。我认为0
和1
都是垃圾值。
bool Indexer::filter(string &keyword)
{
static char replacements[256] = {1}; // initialize with an invalid char
if (replacements[0] == 1)
{
for (int i = 0; i < 256; ++i)
replacements[i] = isGarbage(i) ? 0 : ::tolower(i);
}
string::iterator tail = keyword.begin();
for (string::iterator it = keyword.begin(); it != keyword.end(); ++it)
{
unsigned int index = (unsigned int) *it & 0xff;
if (replacements[index])
*tail++ = replacements[index];
}
keyword.resize(tail - keyword.begin());
// After filtering, if the keyword is empty or it is contained in stop words list, mark as invalid keyword
if (keyword.size() == 0 || stopwords_.find(keyword) != stopwords_.end())
return false;
return true;
}
您的时间安排的最大部分是std::set::find
,所以我也会尝试std::unordered_set
看看它是否有所改善。
答案 6 :(得分:-1)
我会用较低级别的C函数实现它,这样的事情可能(不检查这个编译),在适当的位置进行替换而不调整关键字的大小。
static const char GARBAGE[256] = { 1, 1, 1, 1, 1, ...., 0, 0, 0, 0, 1, 1, ... }
;
然后对于pos
中偏移量const char *str
中的每个字符,您只需检查if (GARBAGE[str[pos]] == 1)
;
这或多或少是无序集所做的,但指令却少得多。 stopwords
如果不是,则应该是无序的。
现在过滤功能(我假设ascii / utf8和null终止字符串):
bool Indexer::filter(char *keyword)
{
char *head = pos;
char *tail = pos;
while (*head != '\0') {
//copy non garbage chars from head to tail, lowercasing them while at it
if (!GARBAGE[*head]) {
*tail = tolower(*head);
++tail; //we only advance tail if no garbag
}
//head always advances
++head;
}
*tail = '\0';
// After filtering, if the keyword is empty or it is contained in stop words list, mark as invalid keyword
if (tail == keyword || stopwords_.find(keyword) != stopwords_.end())
return false;
return true;
}