Прежде чем начать, проверьте, не тратится ли значительное время на эту функцию. Сделайте это путем измерения либо с помощью профилировщика, либо иным образом. Зная, что вы называете это миллион раз, это очень хорошо, но если окажется, что ваша программа по-прежнему тратит только 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
). Проблема в том, что при этом выделяется и обнуляется гораздо больше памяти, чем обычно требуется (322 байта для двойника IEEE). Моя интуиция подсказывает, что это будет медленнее, чем первый вызов 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
. «Множество» не обязательно должно быть целым файлом, это может быть одна строка или «абзац» за раз, но все, что позволяет избежать распределения памяти миллионов, является потенциальным повышением производительности.
person
Steve Jessop
schedule
01.03.2013