In one part of our application there is the requirement to format numbers according to the user's locale.
The interface essentially looks like this:
std::string format_number(double number);
The old implementation looked like this:
std::string format_number(double number) {
using namespace std;
static const locale user_loc("");
ostringstream fmtstream;
fmtstream.imbue(user_loc);
fmtstream << std::fixed;
fmtstream << number;
return fmtstream.str();
}
We have now noticed that with our compiler (MSVC 2005), we can get a approx 10-20% speed up of this function (measured in isolation) by using num_put
directly:
std::string format_number(double number) {
using namespace std;
static const locale user_loc("");
ostringstream fmtstream;
fmtstream.imbue(user_loc);
fmtstream << std::fixed;
typedef char* CharBufOutIt;
typedef num_put<char, CharBufOutIt> np_t;
np_t const& npf = use_facet<np_t>(user_loc);
char buf[127];
const CharBufOutIt begin = &buf[0];
const CharBufOutIt end = npf.put(/*out=*/begin, /*format=*/fmtstream, /*fill=*/' ', number);
return std::string(begin, end);
}
This still seems suboptimal:
- We need to construct a stream object just to imbue it with the locale and use it for the formatting flags.
- We cannot cache the stream object, as
put
will call width(0)
on the passed format stream.
Is the another "trick" to speed this up, or is this the most we can hope to get out of iostream?
And note that we cannot use sprintf here, as we need to make sure the full locale, including thousands separator, is used, and printf doesn't output the thousand separator.
You need the stream for formatting and the locale. If you needed neither of those I would've suggested using a default-constructed num_put
facet directly. The only optimization I can see here is to use std::string
iterators instead of the raw pointers:
typedef std::back_insert_iterator<std::string> iter_type;
typedef std::num_put<char, iter_type> facet_type;
const facet_type& num_put = std::use_facet<facet_type>(user_loc);
std::string buf;
num_put.put(std::back_inserter(buf), fmtstream, fmtstream.fill(), number);
In response to the comments, here is how you would implement your function using a default-constructed facet type:
template<class Facet>
class erasable_facet : public Facet
{
public:
erasable_facet() : Facet(1)
{ }
~erasable_facet() { }
};
std::string format_number(double number)
{
typedef std::back_insert_iterator<std::string> iter_type;
typedef std::num_put<char, iter_type> facet_type;
erasable_facet<facet_type> num_put;
std::ios str(0);
std::string buf;
num_put.put(std::back_inserter(buf), str, str.fill(), number);
return buf;
}
Thanks to user 0x499602D2's answer I have been able to speed this up some more (even with a locale facet).
The point is to not use a stream for the formatting flags, since the /*format=*/
parameter to put
is only accessed for it's flags and locale, so a more basic object suffices:
std::string version_3(double number) {
using namespace std;
static const locale user_loc("");
ios fmtios(NULL); // instead of a full stream
fmtios.imbue(user_loc);
fmtios.setf(ios_base::fixed);
typedef char* CharBufOutIt;
typedef num_put<char, CharBufOutIt> np_t;
np_t const& npf = use_facet<np_t>(user_loc);
char buf[127];
const CharBufOutIt begin = &buf[0];
const CharBufOutIt end = npf.put(/*out=*/begin, /*format=*/fmtios, /*fill=*/' ', value);
return std::string(begin, end);
}
I prematurely had dismissed this version because I was not aware that you can actually legally pass a nullptr as stream buffer to basic_ios
. The docs for init show you can, although it will result in badbit
being set, but as far as I'm able to tell put
doesn't care about the state bits.