views:

259

answers:

9

Hi All: Look at the following code:


StringBuilder row = TemplateManager.GetRow("xyz"); // no control over this method 
StringBuilder rows = new StringBuilder();

foreach(Record r in records)
{
    StringBuilder thisRow = new StringBuilder(row.ToString());
    thisRow.Replace("%firstName%", r.FirstName)
       .Replace("%lastName%", r.LastName)
          // all other replacement goes here
       .Replace("%LastModifiedDate%", r.LastModifiedDate);

    //finally append row to rows
    rows.Append(thisRow);
}

Currently 3 StringBuilders and row.ToString() is inside a loop. Is there any room for further optimization here?

Thanks a bunch. :-)

+5  A: 

You could take row.ToString() out of the loop; row never changes inside the loop.

Carl Manaster
+12  A: 

Is this a bottleneck in your application? Have you profiled it and know it needs optimization?

Don't optimize unless you need to. Pre-mature optimization leads to less maintainable code that may or may not even be optimized.

Ben S
I wish I could upvote 50 times.
pst
Of course this does not answer the question.
ChaosPandion
Chaos is right..No help on the answer
Luke101
@Luke101 - Not that I am against the answer.
ChaosPandion
I agree with your advice, but the question wasn't "should I optimize," it was "can I optimize." And in this case, the obvious optimization doesn't lead to worse code.
Charles
It's as if this line comes straight out of the bible - 'Premature optimization'. The statement has good intent but it is overused. You don't want your optimizations to reduce maintainability, and you definitely don't want to waste of time trying to get that last 1% efficiency, but there is plainly nothing wrong with trying to write efficient code. In this case, putting static / unchanging methods outside of loops is a very simple optimization. It's ridiculous to suggest that it makes code 'less maintainable'.
Kirk Broadhurst
I agree if this is one of existing code. But this is going to be a new project and this piece of code is going to be repeated many times. So I want to make sure is there anything I could do initially than later. Thanks.
Sha Le
+2  A: 

I'd argue that row should be a string, not a StringBuilder.
More of a design comment than an optimization.

Kobi
It's an optimisation to avoid calling `.ToString()` on the StringBuilder in each iteration.
Kirk Broadhurst
+1  A: 

Not a big deal if the row is small, but you could try combining those three replaces into a single replace action.

Edit: You could do this by walking through each character in the StringBuilder using a loop. Since it appears that you are replacing based on %name%, you could walk until you see a % and capture the text until you come across another %. If the captured text matches one of your replacement keys, substitute its replacement value and ignore the text until you come across the next % and repeat the capture process. Otherwise, consider try the capture process from that last % you encountered. That is roughly what you would want to do. Of course, any text that doesn't match a key would have to be appended to the output StringBuilder.

Zach Johnson
How would you combine 3 replaces in to single replace?
Sha Le
@Sha Le: I edited my answer to show how.
Zach Johnson
If the length of the row text is large (> 1k?) or there are a lot (> 5?) of fields to replace, doing all the replacements in one pass will beat having to scan the row text multiple times.Doing all the replacements in one pass will also eliminate the risk of double-replacement: if r.FirstName contains "%lastName%", then the second pass to replace lastname may replace parts of firstname that weren't intended to be seen as template macros.
dthorpe
+2  A: 

StringBuilders generally only provide great improvements when concatenating strings into very large strings, not very large quantities of small ones. If your rows are small you might consider clocking this code against normal strings.

i.e.

string row = TemplateManager.GetRow("xyz").ToString();
foreach (Record r in records) 
    rows.Append(row
        .Replace("%firstName%", r.FirstName)
        .Replace("%lastName", r.LastName)
        // ....
        );

Or just use XSLT.

tsilb
:) I doubt that xslt would be a performance improvement, but I agree that a simple string replace is probably the best, since there are only 3 string mods. It does depend on how many replaces there are, though.
Andrew Backer
+8  A: 

I thought it might be quicker to replace thisRow with a string, but I did some tests and StringBuilder won.

String.Replace() creates a new string, which is expensive. StringBuilder.Replace() modifies the string in its buffer, which is less expensive. In my test I used a string of 500 chars and performed 6 Replace()'s. StringBuilder was about 5% faster, including creating a new one each iteration.

However, one thing you should do is move the row.ToString() outside the loop. Calling it for each record is wasting cycles by creating a new string.

String row = TemplateManager.GetRow("xyz").ToString();
StringBuilder rows = new StringBuilder(); 

foreach(Record r in records) 
{ 
    StringBuilder thisRow = new StringBuilder(row);  
    thisRow.Replace("%firstName%", r.FirstName) 
                   .Replace("%lastName%", r.LastName) 
                   .Replace("%LastModifiedDate%", r.LastModifiedDate); 
    rows.Append(thisRow);
}    
Charles
Another improvement you could make is move the `sb` from the loop and call `sb.Remove(0, sb.Length);` then append the format again `sb.Append(row);` this keeps object creation down.
ChaosPandion
@Chaos: That would actually take longer - object creation is cheap.
Charles
@Charles - Now if I took the time to measure properly I wouldn't look like a n00b! :)
ChaosPandion
You can remove even more from within the loop - no need to do any searching for %firstName% ... in the invariant string row within that loop, it can all be precalculated.
Hightechrider
A: 
StringBuilder row = TemplateManager.GetRow("xyz"); // no control over this method 
StringBuilder rows = new StringBuilder();

foreach(Record r in records)
{
    row.Replace("%firstName%", r.FirstName)
       .Replace("%lastName%", r.LastName)
          // all other replacement goes here
       .Replace("%LastModifiedDate%", r.LastModifiedDate);

    //finally append row to rows
    rows.Append(row);
}
Rosdi
This won't work. StringBuilder.Replace() modifies the original StringBuilder. This would just add the first record repeatedly.
Charles
A: 

Depending on what "Records" is and how many there are, you might look at using for, instead of foreach.

I've found Jon Skeet's benchmarking analysis of for vs foreach interesting.

McAden
A: 

Like Ben said, don't do it!

But if you must, the obvious optimization is to move the calculation of all the indexes for where to copy and where to substitute out of the loop entirely. 'rows' is constant so the positions of each substitutable parameter within it are constant.

If you do that the loop becomes just a bunch of SubString() and concatenation operations and it runs ~3x faster for the case with just one of each in the template in that order.

Or, for an even easier (but still questionable) optimization that can easily handle repeats (but not {#} values in the template string!) ...

string format = row.Replace("%firstname%,"{0}").Replace("%...

for ...

   string thisRow = string.format(format, r.FirstName, r.LastName, r.LastModifiedDate);

   ...
Hightechrider
Or maybe you construct a RegEx from the format and compile it outside the loop ... oh wait ... then you'd have two problems :-)
Hightechrider