StringBuilder's performance lower than that of StringBuffer

R

Raga

Hi,

I read in many websites than StringBuilder offers better performance
than StringBuffer. So, in my code, I changed all StringBuffer usages to
StringBuilder. But now the performance has worsened. Is it that
StringBuffer is really more performant? Also, is there any better
performant way than using StringBuilder & StringBuffer?

Thanks.
 
J

Jeffrey Schwab

Raga said:
I read in many websites than StringBuilder offers better performance
than StringBuffer. So, in my code, I changed all StringBuffer usages to
StringBuilder. But now the performance has worsened. Is it that
StringBuffer is really more performant? Also, is there any better
performant way than using StringBuilder & StringBuffer?

By "performant," I think you mean "performing." :)

StringBuffer is supposed to be thread-safe. Since StringBuilder offers
no such guarantee, it is potentially much faster. I don't think we're
talking about a difference in big-O complexity, though, just a constant
factor speed difference.


Hmmm... A quick test makes this it look an awful lot like StringBuilder
has *more* overhead, but is using a better algorithm. Odd. The code is
below: For 1024 iterations, I got the same result you did, i.e.
StringBuffer gave better performance:

StringBuffer: 1268318 ns
StringBuilder: 1804978 ns

For 1024*1024 iterations, though, the reverse was the case:

StringBuffer: 170694041 ns
StringBuilder: 127406416 ns

I reiterate: Hmmm...

public class Main {

static final int numIterations = 1024 * 1024;

public static void main(String[] args){

StringBuffer sbuf =
new StringBuffer("same text as the other guy");
StringBuilder sbld =
new StringBuilder("same text as the other guy");

long t = System.nanoTime();

for(int i = 0; i < numIterations; ++i) {
sbuf.append("a");
sbuf.deleteCharAt(0);
}

t = System.nanoTime() - t;
System.out.println("StringBuffer:\t" + t + " ns");

t = System.nanoTime();

for(int i = 0; i < numIterations; ++i) {
sbld.append("a");
sbld.deleteCharAt(0);
}

t = System.nanoTime() - t;
System.out.println("StringBuilder:\t" + t + " ns");
}
}
 
P

Patricia Shanahan

Jeffrey said:
By "performant," I think you mean "performing." :)

StringBuffer is supposed to be thread-safe. Since StringBuilder offers
no such guarantee, it is potentially much faster. I don't think we're
talking about a difference in big-O complexity, though, just a constant
factor speed difference.


Hmmm... A quick test makes this it look an awful lot like StringBuilder
has *more* overhead, but is using a better algorithm. Odd. The code is
below: For 1024 iterations, I got the same result you did, i.e.
StringBuffer gave better performance:

The actual performance is going to be very dependent on the operation
mix. A variation of your program suggests that some of the effects may
be due to outside influences such as page faulting or cache missing. I
modified the program to do each test multiple times, alternating:

StringBuffer: 3255575 ns
StringBuilder: 6224512 ns
StringBuffer: 3741265 ns
StringBuilder: 2922576 ns
StringBuffer: 296705 ns
StringBuilder: 229733 ns
StringBuffer: 186285 ns
StringBuilder: 148315 ns
StringBuffer: 1421934 ns
StringBuilder: 149066 ns
StringBuffer: 181475 ns
StringBuilder: 145825 ns
StringBuffer: 192282 ns
StringBuilder: 143341 ns
StringBuffer: 185336 ns
StringBuilder: 151989 ns

The first two lines look as though StringBuffer is faster than
StringBuilder, but the later lines suggest the opposite. I assume this
behavior is unrelated to the real program issue.

Here's my version of the program:

public class StringBufferPerformance {

static final int numIterations = 1024;

public static void main(String[] args){

StringBuffer sbuf =
new StringBuffer("same text as the other guy");
StringBuilder sbld =
new StringBuilder("same text as the other guy");

timeBuffer(sbuf);
timeBuilder(sbld);
timeBuffer(sbuf);
timeBuilder(sbld);
timeBuffer(sbuf);
timeBuilder(sbld);
timeBuffer(sbuf);
timeBuilder(sbld);
timeBuffer(sbuf);
timeBuilder(sbld);
timeBuffer(sbuf);
timeBuilder(sbld);
timeBuffer(sbuf);
timeBuilder(sbld);
timeBuffer(sbuf);
timeBuilder(sbld);
}

/**
* @param sbld
*/
private static void timeBuilder(StringBuilder sbld) {
long t;
t = System.nanoTime();

for(int i = 0; i < numIterations; ++i) {
sbld.append("a");
sbld.deleteCharAt(0);
}

t = System.nanoTime() - t;
System.out.println("StringBuilder:\t" + t + " ns");
}

/**
* @param sbuf
*/
private static void timeBuffer(StringBuffer sbuf) {
long t = System.nanoTime();

for(int i = 0; i < numIterations; ++i) {
sbuf.append("a");
sbuf.deleteCharAt(0);
}

t = System.nanoTime() - t;
System.out.println("StringBuffer:\t" + t + " ns");
}
}
 
J

Jeffrey Schwab

Patricia said:
The actual performance is going to be very dependent on the operation
mix. A variation of your program suggests that some of the effects may
be due to outside influences such as page faulting or cache missing. I
modified the program to do each test multiple times, alternating:

StringBuffer: 3255575 ns
StringBuilder: 6224512 ns
StringBuffer: 3741265 ns
StringBuilder: 2922576 ns
StringBuffer: 296705 ns
StringBuilder: 229733 ns
StringBuffer: 186285 ns
StringBuilder: 148315 ns
StringBuffer: 1421934 ns
StringBuilder: 149066 ns
StringBuffer: 181475 ns
StringBuilder: 145825 ns
StringBuffer: 192282 ns
StringBuilder: 143341 ns
StringBuffer: 185336 ns
StringBuilder: 151989 ns

The first two lines look as though StringBuffer is faster than
StringBuilder, but the later lines suggest the opposite. I assume this
behavior is unrelated to the real program issue.

That makes sense. String modification is so pervasive in actual
programs... It's hard to come up with test cases that mimic the Wild.
 
T

Thomas Hawtin

Patricia said:
The first two lines look as though StringBuffer is faster than
StringBuilder, but the later lines suggest the opposite. I assume this
behavior is unrelated to the real program issue.

There is a lot of work going on behind the scenes to load your program
even before it starts off on the first iteration. Most of the Java
library is still using StringBuffer. Therefore, it seems likely that the
StringBuffer code is mostly compiled while the StringBuilder starts off
interpreted.

Tom Hawtin
 
?

=?ISO-8859-1?Q?Arne_Vajh=F8j?=

Patricia said:
The actual performance is going to be very dependent on the operation
mix.

I have a StringBuffer test program that do a mix of append and
substring combined with some String operations.

StringBuffer results says 1.11 while StringBuilder
results say 1.18 on 32 bit Win32 SUN Java 1.5.

(code attached in StringBuilder incarnation below for the curious)

That is a 6% difference.

I am not surprised that the difference is not bigger.

The synchronized overhead has become smaller in newer
JVM's.

And the code do other things than just StringB*
operations. But so do real world applications.

Arne

===================================================

import java.text.NumberFormat;
import java.text.DecimalFormat;
import java.util.Random;

public class JvmTest {
private final static int REP = 10;
private final static int NSTR = 100;
private final static int N = 1000000;
private final static String ALFA = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static NumberFormat nf = new DecimalFormat("0.00");
private static Random rng = new Random();
private static int scale;
private static void printres(long t1, long t2, int n1, int n2,
String ops) {
double xperf = (double)n1 * (double)n2 / ((t2 - t1) / 1000.0) ;
String sperf= nf.format(xperf/1000000);
System.out.println(sperf + " million " + ops + " per second");
}
public static void teststr() {
int nstrscale = NSTR / scale;
long t1 = System.currentTimeMillis();
for(int i = 0; i < nstrscale; i++) {
StringBuilder sb = new StringBuilder("");
for(int j = 0; j < N; j = j + 10) {
String s = ALFA + ALFA;
int ix = (i + j) % ALFA.length();
sb.append(s.substring(ix, ix + 1) + s.substring(ix + 1,
ix + 3) + s.substring(ix + 3, ix + 6) + s.substring(ix + 6, ix + 10));
}
int ix = rng.nextInt(N);
if(sb.length() != N || sb.charAt(ix) != ALFA.charAt((i +
ix) % ALFA.length())) {
System.out.println("String test error");
System.exit(0);
}
}
long t2 = System.currentTimeMillis();
printres(t1, t2, nstrscale, N / 10, "string operations");
}
public static void main(String[] args) {
System.out.println(System.getProperty("java.vm.vendor") + " " +
System.getProperty("java.vm.name") + " " +
System.getProperty("java.vm.version"));
if(args.length > 0) {
scale = Integer.parseInt(args[0]);
} else {
scale = 1;
}
for(int i = 0; i < REP; i++) {
teststr();
}
}
}
 
Joined
Apr 5, 2007
Messages
2
Reaction score
0
Missing the point

Both StringBuffer and StringBuilder perform best if you can estimate the likely maximum size. The following code is a slight adaptation of previous poster's test code (although it is 4 times faster by pre-allocating, re-using and using StingBu*er to do ALL the appends).

I get StringBuilder about 3% faster than StringBuffer (although the maximum performance of StringBuffer is no worse than the minum performance of StringBuilder). This is nothing compared with the 400% from code changes!

I have not tried performance in a multi-threaded program. This is where synchronization overhead could kick in with StringBuffer.

import java.text.NumberFormat;
import java.text.DecimalFormat;
import java.util.Random;

public class JvmTest {
private final static int REP = 5;

private final static int NSTR = 100;

private final static int N = 1000000;

private final static String ALFA = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

private static NumberFormat nf = new DecimalFormat("0.00");

private static Random rng = new Random();

private static int scale;

public static void main(String[] args) {
System.out.println(System.getProperty("java.vm.ven dor") + " "
+ System.getProperty("java.vm.name") + " "
+ System.getProperty("java.vm.version"));
if (args.length > 0) {
scale = Integer.parseInt(args[0]);
} else {
scale = 1;
}
for (int i = 0; i < REP; i++) {
teststr();
}
}

private static void printres(long t1, long t2, int n1, int n2, String ops) {
double xperf = (double) n1 * (double) n2 / ((t2 - t1) / 1000.0);
String sperf = nf.format(xperf / 1000000);
System.out.println(sperf + " million " + ops + " per second");
}

public static void teststr() {

// replace with StringBuilder or StringBuffer
// NOTE: it starts at the correct size!
StringBuffer sb = new StringBuffer(N);
String s = ALFA + ALFA;
int nstrscale = NSTR / scale;
long t1 = System.currentTimeMillis();
for (int i = 0; i < nstrscale; i++) {
sb.setLength(0); // re-use large object
for (int j = 0; j < N; j = j + 10) {
int ix = (i + j) % ALFA.length();
// perform multiple appends
sb.append(s.substring(ix, ix + 1));
sb.append(s.substring(ix + 1, ix + 3));
sb.append(s.substring(ix + 3, ix + 6));
sb.append(s.substring(ix + 6, ix + 10));
}
int ix = rng.nextInt(N);
if (sb.length() != N
|| sb.charAt(ix) != ALFA.charAt((i + ix) % ALFA.length())) {
System.out.println("String test error");
System.exit(0);
}
}
long t2 = System.currentTimeMillis();
printres(t1, t2, nstrscale, N / 10, "string operations");
}
}
 
Joined
Apr 5, 2007
Messages
2
Reaction score
0
For the original test in JVM HotSpot 1.6:
StringBuffer: 187841472 ns
StringBuilder: 144423663 ns
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,236
Members
46,822
Latest member
israfaceZa

Latest Threads

Top