5

I have a simple scenario. I need to join two C-strings together into a std::string. I have decided to do this in one of two ways:

Solution 1

void ProcessEvent(char const* pName) {
   std::string fullName;
   fullName.reserve(50); // Ensure minimal reallocations for small event names (50 is an arbitrary limit).
   fullName += "com.domain.events.";
   fullName += pName;

   // Use fullName as needed
}

Solution 2

void ProcessEvent(char const* pName) {
   std::ostringstream ss;
   ss << "com.domain.events." << pName;

   std::string fullName{ss.str()};
   // Use fullName as needed
}

I like solution 2 better because the code is more natural. Solution 1 seems like a response to a measurable bottleneck from performance testing. However, Solution 1 exists for 2 reasons:

  1. It's a light optimization to reduce allocations. Event management in this application is used quite frequently so there might be benefits (but no measurements have been taken).
  2. I've heard criticism regarding STL streams WRT performance. Some have recommended to only use stringstream when doing heavy string building, especially those involving number conversions and/or usage of manipulators.

Is it a premature pessimization to prefer solution 2 for its simplicity? Or is it a premature optimization to choose solution 1? I'm wondering if I'm too overly concerned about STL streams.

5
  • I would suggest do some profiling to see if this is actually a problem worth concerning. If this is really a bottleneck, std::string would not the most optimal solution either. Commented Oct 20, 2014 at 20:43
  • 1
    TBH Solution 1 seems more natural to me (less the reserve() for this situation. I usually only think of using std::stringstreams when I need to convert numbers or user defined types. Although you should not prematurely optimize, I think you should still be mindful of efficiency. Commented Oct 20, 2014 at 20:44
  • 5
    Solution 3: std::string fullName = std::string{"com.domain.events."} + std::string{pName}; -- There are many ways to skin this cat. Keep things simple when you can. Commented Oct 20, 2014 at 20:48
  • You may also get some interesting opinions at codereview.stackexchange.com . Commented Oct 20, 2014 at 21:03
  • 1
    Or, in C++14, std::string fullName = "com.domain.events."s + pName;. About as simple as you can get. Commented Oct 20, 2014 at 22:29

2 Answers 2

11

Let's measure it

A quick test with the following functions:

void func1(const char* text) {
    std::string s;
    s.reserve(50);
    s += "com.domain.event.";
    s += text;
}

void func2(const char* text) {
    std::ostringstream oss;
    oss << "com.domain.event." << text;
    std::string s = oss.str();
}

Running each 100 000 times in a loop gives the following results on average on my computer (using gcc-4.9.1):

func1 : 37 milliseconds

func2 : 87 milliseconds

That is, func1 is more than twice as fast.

That being said, I would recommend using the clearest most readable syntax until you really need the performance. Implement a testable program first, then optimize if its too slow.

Edit:

As suggested by @Ken P:

void func3(const char* text) {
    std::string s = "com.domain.event" + std::string{text};
}

func3 : 27 milliseconds

The simplest solution is often the fastest.

Sign up to request clarification or add additional context in comments.

2 Comments

Wish I could give you more than one +1 for doing actual testing on your own machine and also for the nice solution 3 that I should have thought of! Thanks !
Glad to be of help, although the credit for solution 3 should go to @Ken P :)
0

You didn't mention the 3rd alternative of not pre-allocating anything at all in the string and just let the optimizer do what it's best at.

Given these two functions, func1 and func3:

void func1(const char* text) {
    std::string s;
    s.reserve(50);
    s += "com.domain.event.";
    s += text;
    std::cout << s;
}

void func3(const char* text) {
    std::string s;
    s += "com.domain.event.";
    s += text;
    std::cout << s;
}

It can be seen in the example that the gcc assembly for func1 just for reserving space for 50 characters will add an additional 3 instructions compared to when no pre-allocation is done in func3. One of the calls is a string append call, which in turn will give some overhead:

leaq    16(%rsp), %rdi
movl    $50, %esi
call    std::basic_string<char>::append(char const*, unsigned long)

Looking at the code alone doesn't guarantee that func3 is faster than func1 though, just because it has fewer instructions. Cache and other things also contributes to the actual performance, which can only be properly assessed by measuring, as others pointed out.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.