views:

238

answers:

11

I have this code to concate some array elements:

StringBuilder sb = new StringBuilder();
private RatedMessage joinMessage(int step, boolean isresult) {
        sb.delete(0, sb.length());
        RatedMessage rm;
        for (int i = 0; i <= step; i++) {
            if (mStack[i] == null)
                continue;
            rm = mStack[i].getCurrentMsg();// msg is built upfront, this just returns, it's a getter method call
            if (rm == null || rm.msg.length() == 0)
                continue;
            if (sb.length() != 0) {
                sb.append(", ");
            }
            sb.append(rm.msg);
        }
        rm.msg=sb.toString();
        return rm;
    }

Important the array holds max 10 items, so it's not quite much.

My trace output tells me this method is called 18864 times, 16% of the runtime was spent in this method. Can I optimize more?

+5  A: 

First of all I won't reuse StringBuilder and always create new instance. That will be certainly faster, because it would allow GC to use young generation heap area.

Another little trick that allows to eliminate at least one if statement is to rewrite your code like this:

    String separator = "";
    for (int i = 0; i <= step; i++) {
        ...
        sb.append(separator);
        sb.append(rm.msg);
        separator = ", ";
    }
Eugene Kuleshov
Will the extra assignment be better performance-wise than the if statement? Or is it magically optimized away by the compiler?
Peter Jaric
Another way to remove that if-block without adding the assignment is to move the exit condition out of the for-loop header and into the loop itself: `if ( i <= step ) break; sb.append(separator);`
Mark Peters
A: 

If your function is supposed to concatenate array elements, why are you passing in all these crazy values and unused parameters?

private string joinMessage( string[] myArray)
{
  StringBuilder sbr = new StringBuilder();
  for(int i = 0; i < myArray.Length; i++)
  {
     if(!string.IsNullOrEmpty(myArray[i])
     {
       sbr.Append(myArray[i]);
       sbr.Append(",")
     }
  }
  return sbr.ToString();
}
Nate Noonen
I have some other minor stuff in there, see my comment on the main question.
Pentium10
I'm not arguing that you don't need to ever return a RatedMessage or do anything like that, I'm saying that if the point of the method is "Takes an array of strings and returns a comma delimeted string of all elements concatenated", anything beyond that is worthless (for the sake of this discussion)
Nate Noonen
+1  A: 

You can make the following change (showing only the differences):

    String separator = "";
    for (int i = 0; i <= step; i++) {
    // ...
        sb.append(separator).append(rm.msg);
        separator = ", ";
    }

It gets rid if an extra if 9 times at the cost of adding an empty string once. You should measure if it helps at all with the data you are using before you decide to keep this change :-)

rsp
A: 

Take a step through each element in the stack first, taking a count of the sum of all the string lengths.

Then you can use

sb.ensureCapacity(totalEndLength);

String builder works like an array list, so you might be rebuilding that array with most of your appends.

glowcoder
+2  A: 

some ideas:

1) Do you initialize the StringBuilder with the estimated max capacity? This can save the time spent in the inner array re-allocation & copying.

2) Maybe you can append a trailing comma in the loop, and avoid the condition for string length inside the loop. Instead, add a single condition at the end of the method, and remove the trailing comma if needed.

Eyal Schneider
A: 

A bit of a mini-optimization... take the test-for-comma outside of the loop.

private RatedMessage joinMessage(int step, boolean isresult) {
    sb.delete(0, sb.length());
    for (int i = 0; i <= step; i++) {
        if (mStack[i] == null)
            continue;
        rm = mStack[i].getCurrentMsg();
        if (rm == null || rm.msg.length() == 0)
            continue;
        sb.append(rm.msg).append(", ");
    }
    if (sb.length() > 2) {
        sb.delete(sb.length() - 2, 2);
    }
    return sb.toString();
}

Other suggestions would be:

  • Make sure that when the StringBuilder is constructed you set its initial length to a decent value
  • I'm not sure of the context of the rest of the code, but maybe you can pre-ensure that mStack[i] will not be null, and that mStack[i].getCurrentMessage() is not null or empty - this will allow you to take more if statements outside of the loop.
Burg
A: 

Have a separate copy of the mStack array with the string representation, by default initialized with empty String, so your loop would be:

String [] mStackCopy = new String[]{"","","","","","","","","","",};
// or mstackCopy = new String[mStack.length]; 
// for( int i = 0 ; i < mStackCopy.lenght ; i++ ) { mStack[i] = "" }

Also, create the StringBuilder with enough capacity:

StringBuilder sb = new StringBuilder( 10000 );// 10k chars or whatever makes sense.

So, when you need to create the message you would simply:

for (int i = 0; i <= step; i++) {
   sb.append( mStackCopy[i] );
}

And empty parts won't cause a problem because they are blank already:

You may even hard code it:

 sb.append( mStackCopy[0]);
 sb.append( mStackCopy[1]);
 sb.append( mStackCopy[2]);
 sb.append( mStackCopy[3]);
 sb.append( mStackCopy[4]);
 sb.append( mStackCopy[5]);
 sb.append( mStackCopy[6]);
 sb.append( mStackCopy[7]);
 sb.append( mStackCopy[8]);
 sb.append( mStackCopy[9]);

But this would cause more pain than relief in the future, guaranteed.

When you add something to your mStack:

MStack item = new MStack();
item.setCurrentMessage("Some message");

 .... 

Just make a copy of the message and append the ", " already.

  addToMStack(int position,  MStackItem item ) {
    mStack[position] = item;
    mStackCopy[position] = item.getCurrentMessage() + ", ";
}

And depending on the occurrence of nulls ( if its low ) you can catch them

  addToMStack(int position,  MStackItem item ) {
    if( item == null ) { return; }
    mStack[position] = item;
    try {
        mStackCopy[position] = item.getCurrentMessage() + ", ";
    } catch( NullPointerException npe ){}
 }

Which is horrendous

Or validate it:

  addToMStack(int position,  MStackItem item ) {
    if( item == null ) { return; }
    mStack[position] = item;
    mStackCopy[position] = item.getCurrentMessage() + ", ";
 }

I'm pretty sure your method is doing something else that you don't show us. Probably the reason is there.

Also, 16% is not that bad, if 100% is 1sec.

OscarRyz
Since I am running this on a mobile device is much slower. The above told 18864 steps took 8-10sec.
Pentium10
A: 

If your mStack is a Collection instead of an array, you can just do mStack.toString(), which will print a readable string of the array. That might be easier than writing your own.

James Kingsbery
A: 

16% runtime in this method including or excluding called methods? The getCurrentMsg() call might be a hidden problem, if it creates lots of objects.

Besides that, I suggest to take all the needed Strings out of your stack and afterwards call

StringUtils.join(myStrings, ", ")

using the Apache commons library. Try relying on tested code for such low level things instead of optimizing it yourself every other day. In the end that will give you better optimization results because you will be able to concentrate on the big picture (i.e. the overall design of your software).

Bananeweizen
slapping myself for not having noticed the comment besides that call. Nevertheless the StringUtils recommendation remains. :)
Bananeweizen
it's inclusive, exclusive is 12% something
Pentium10
A: 

Sometimes there's just nothing else to optimize. I think this is one of such cases. You can try to shave off an instruction or two maybe, but you won't get it much faster in principle.

I think the only thing left to optimize is to consider why you are calling it 18864 times and whether some of those calls can be avoided altogether. Perhaps some are not needed, or perhaps you can cache the result in some cases.

Vilx-
it's part of a state machine generation process, when a state reaches a Stop state, it generates a result. It's already reduced some way from 2M values only 18k times is called.
Pentium10
A: 

Use StringBuilder + StringUtils from Apache Commons Lang. Looping through a String with a separator and chomping is what StringUtils is all about!

private RatedMessage joinMessage(int step, boolean isresult) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i <= step; i++) {
            WhateverTypeIsFromMStackVariable stackVariable = mStack[i];
            String message = getMessage(stackVariable);
            if(StringUtils.isNotEmpty(message)) {
                builder.append(message).append(", ");
            }
        }
        RatedMessage rm = new RatedMessage();
        rm.msg = StringUtils.chomp(builder.toString(), ", ");
        return rm;
    }

private static String getMessage(WhateverTypeIsFromMStackVariable stackVariable) {
    if(stackVariable != null) {
        RatedMessage message = stackVariable.getCurrentMsg();
        if(message != null) {
            return message.msg;
        }
     }
     return null;
 }

Apache Commons Lang is here: http://commons.apache.org/lang/

MetroidFan2002