I'm trying to speed up a multithreaded program that prints a lot to std::cout. Most of the stuff that is printed are strings puzzled together from several variables (strings, numbers etc). Access to std::cout is protected by a mutex to prevent printouts from several threads from getting intermingled in the manner of:
{
std::lock_guard<std::mutex> lock(mutex);
std::cout << stringA << " 1 " << 5 << 'C' << std::endl;
}
Measurements showed that several threads spend a lot of time waiting for the mutex as std::cout seems to take some time for large and complex strings.
My question now is:
Can I in theory reduce lock contention by assembling the string into a std::stringstream before entering the mutex, then send the already assembled string to std::cout? As in:
{
std::stringstream ss;
ss << stringA << " 1 " << 5 << 'C' << std::endl;
std::lock_guard<std::mutex> lock(mutex);
std::cout << ss.str();
}
If yes, can this be improved further?
Can I in theory reduce lock contention by assembling the string into a std::stringstream before entering the mutex, then send the already assembled string to std::cout?
Absolutely. operator<< has to do some work to format the types passed in. Assembling the string into a std::stringstream means that you do all that work up front and just have to write out the assembled string to std::cout, meaning you spend less time under the lock.
However, note that ss.str() returns a std::string by value. This means that you are copying the string inside the critical region. It would be better to write std::cout << ss.rdbuf() and write the underlying string inside the std::stringstream directly.
Beyond that, you'll want to reduce the time spent outputting to std::cout as much as possible. If you never call any C stdio functions, you should probably call std::ios_base::sync_with_stdio(false)
Bringing this together:
// Near the beginning of your program:
std::ios_base::sync_with_stdio(false);
// ...
{
// Prefer using ostringstream if you never need to read from it
std::ostringstream ss;
// std::endl is never needed. Use '\n' instead. If you want to flush,
// explicitly write `ss << '\n' << std::flush`. For stringstreams, I believe
// it doesn't matter, but it's good to get into the habit of doing this
ss << stringA << " 1 " << 5 << 'C' << '\n';
std::lock_guard<std::mutex> lock(mutex);
std::cout << ss.rdbuf();
}
I would drop string streams altogether in favor of std::string::append and std::to_string. streams tend to drag a lot of locale-oriented stuff and make the implementation heavier than the raw string operations. I would go with this:
std::string str;
str.append(stringA).append(" 1 ").append('C').append('\n');
std::cout << str;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With