我分析了我的一个程序,发现热点是levenshtein_distance
递归调用的。我决定尝试优化它。
lvh_distance levenshtein_distance( const std::string & s1, const std::string & s2 )
{
const size_t len1 = s1.size(), len2 = s2.size();
std::vector<unsigned int> col( len2+1 ), prevCol( len2+1 );
const size_t prevColSize = prevCol.size();
for( unsigned int i = 0; i < prevColSize; i++ )
prevCol[i] = i;
for( unsigned int i = 0, j; i < len1; ++i )
{
col[0] = i+1;
const char s1i = s1[i];
for( j = 0; j < len2; ++j )
{
const auto minPrev = 1 + std::min( col[j], prevCol[1 + j] );
col[j+1] = std::min( minPrev, prevCol[j] + ( static_cast<unsigned int>( s1i != s2[j] ) ) );
}
col.swap( prevCol );
}
return prevCol[len2];
}
TL;DR:我改变了std::string
→std::array
War Story:在它上面运行 vtune 之后,我发现更新的那一行col[j+1]
是减慢一切的那一行(90% 的时间都花在它上面)。我想:好吧,也许这是一个别名问题,也许编译器无法确定字符串对象中的字符数组是无别名的,因为它们被字符串接口屏蔽,并且花费 90% 的时间检查程序的其他部分修改了它们。
所以我把我的字符串改成了静态数组,因为那里,没有动态内存,下一步本来就是restrict
用来帮助编译的。但与此同时,我决定检查是否通过这样做获得了任何性能。
lvh_distance levenshtein_distance( const std::string & s1, const std::string & s2 )
{
const size_t len1 = s1.size(), len2 = s2.size();
static constexpr unsigned MAX_STRING_SIZE = 512;
assert(len1 < MAX_STRING_SIZE && len2 < MAX_STRING_SIZE);
static std::array<unsigned int, MAX_STRING_SIZE> col, prevCol;
for( unsigned int i = 0; i < len2+1; ++i )
prevCol[i] = i;
// the rest is unchanged
}
TL;DR:现在它运行缓慢。
发生的事情是我失去了表现。很多。我的示例程序现在在 44 秒内运行,而不是在 ~ 6 秒内运行。再次使用 vtune 进行分析表明,一个函数被一遍又一遍地调用:(std::swap
对你来说,gcc 人,这是在 bits/move.h 中),而它又由std::swap_ranges
(bits/stl_algobase.h) 调用。
我想这std::min
是使用 实现的quicksort
,这解释了为什么要进行交换,但我不明白为什么在这种情况下交换需要这么多时间。
编辑:编译器选项:我正在使用带有选项“-O2 -g -DNDEBUG”的 gcc 和一堆警告说明符。