在开始之前,请检查是否在此功能上花费了大量时间。通过使用分析器或其他方式进行测量来做到这一点。知道你把它称为无数次是很好的,但是如果你的程序仍然只在这个函数上花费了 1% 的时间,那么你在这里所做的任何事情都不可能使你的程序性能提高 1% 以上。如果是这种情况,您的问题的答案将是“出于您的目的,否,此功能无法显着提高效率,如果您尝试,您就是在浪费时间”。
首先,避免s.substr(0, s.size()-1)
。这会复制大部分字符串,并且使您的函数不符合 NRVO 的条件,因此我认为通常您会在返回时获得一份副本。所以我要做的第一个改变是将最后一行替换为:
if(s[s.size()-1] == '.') {
s.erase(s.end()-1);
}
return s;
但是,如果性能是一个严重的问题,那么我会这样做。我不保证这是最快的,但它避免了一些不必要的分配和复制问题。任何涉及stringstream
的方法都需要从字符串流复制到结果,所以我们需要一个更底层的操作,snprintf
.
static std::string dbl2str(double d)
{
size_t len = std::snprintf(0, 0, "%.10f", d);
std::string s(len+1, 0);
// technically non-portable, see below
std::snprintf(&s[0], len+1, "%.10f", d);
// remove nul terminator
s.pop_back();
// remove trailing zeros
s.erase(s.find_last_not_of('0') + 1, std::string::npos);
// remove trailing point
if(s.back() == '.') {
s.pop_back();
}
return s;
}
第二次调用snprintf
假定std::string
使用连续存储。这在 C++11 中得到保证。在 C++03 中不能保证,但对于std::string
C++ 委员会已知的所有积极维护的实现都是如此。如果性能真的很重要,那么我认为做出这种不可移植的假设是合理的,因为直接写入字符串可以节省以后复制到字符串的操作。
s.pop_back()
是 C++11 的说法s.erase(s.end()-1)
,并且s.back()
是s[s.size()-1]
对于另一个可能的改进,您可以摆脱第一次调用snprintf
,而是将您的大小s
调整为某个值,例如std::numeric_limits<double>::max_exponent10 + 14
(基本上,-DBL_MAX
需要的长度)。问题是这分配和归零的内存比通常需要的多得多(IEEE double 为 322 字节)。我的直觉是这将比第一次调用要慢snprintf
,更不用说在调用者将字符串返回值保持一段时间的情况下浪费内存。但你总是可以测试它。
或者,std::max((int)std::log10(d), 0) + 14
计算所需大小的合理严格的上限,并且可能比snprintf
精确计算更快。
最后,可能是你可以通过改变函数接口来提高性能。例如,您可以附加到调用者传入的字符串,而不是返回一个新字符串:
void append_dbl2str(std::string &s, double d) {
size_t len = std::snprintf(0, 0, "%.10f", d);
size_t oldsize = s.size();
s.resize(oldsize + len + 1);
// technically non-portable
std::snprintf(&s[oldsize], len+1, "%.10f", d);
// remove nul terminator
s.pop_back();
// remove trailing zeros
s.erase(s.find_last_not_of('0') + 1, std::string::npos);
// remove trailing point
if(s.back() == '.') {
s.pop_back();
}
}
然后调用者可以reserve()
有足够的空间,多次调用您的函数(可能在其间添加其他字符串),然后将生成的数据块一次全部写入文件,除了reserve
. “大量”不必是整个文件,一次可以是一行或“段落”,但任何避免大量内存分配的东西都是潜在的性能提升。