Reputation: 6152
General:
I am writing a socket client that receives "Market" data/quotes all the time (never ending loop) from some server side (distant one).
i am dividing the data in to chunks so i can use it.
each chunk contains about 200 characters and needs to be converted in to an array.
After a chunk was divided it is been parsed in to a List (No Problems here).
The problem:
The CPU usage is reaching to 40% after 10 minutes of running.
I have managed to isolate the problem.
Every chunk needs to be converted in to json.
So i am giving you now the actual code that does the problems.
this code executes every 300-400 MS.
skipping this code will leave the entire system with 1%-2% CPU usage.
Note:
I have read this thread but i don't see any solution there.
Is it better to reuse a StringBuilder in a loop?
The code:
private static StringBuffer jsonVal = new StringBuffer();
public static String toJson(List<QuotesData> quotesData) {
// Empty variable
jsonVal.delete(0, jsonVal.length());
jsonVal.append("{");
synchronized (quotesData) {
for (QuotesData quote : quotesData) {
jsonVal.append("\"").append(quote.getSymbol()).append("\":[{");
jsonVal.append("\"ask\":\"").append(quote.getAsk()).append(
"\",");
jsonVal.append("\"bid\":\"").append(quote.getBid()).append(
"\",");
jsonVal.append("\"time\":\"").append(quote.getDateTime())
.append("\"}],");
}
jsonVal.append("}");
String returnString = jsonVal.toString();
return returnString.toString().replace("}],}", "}]}");
}
}
Upvotes: 3
Views: 287
Reputation: 34271
A few suggestions:
StringBuilder
instead of StringBuffer
. StringBuffer
is synchronized, StringBuilder
is not.synchronized
statement really needed? If not, try removing it.toString()
is not needed on the return statement. You can remove it.replace()
method in the end, that could be costly if returnString
is long.StringBuffer
object before the loop instead clearing of the old one.return returnString.intern()
Upvotes: 0
Reputation: 6510
OK, so this looks like a classic case of over optimization. Object creation isn't that expensive that you need to rewrite the same string buffer, especially if this is called every 300-400ms.
I'll try to address every possible scenario: Exponential growth The above code is assigned to a new thread every 300ms but the list is enormous and takes over 300ms to serialize. If this is the case you are basically choking your resources and it is only a matter of time before the application crashes. If this is the case, you should see the CPU constantly rising. The solution would be to:
Speedups OK, so the list isn't clonable which I'm assuming means it isn't really a list but rather some sort of queue implemented as a list interface. So leaving synchronization as is I would do this:
public static final int JSON_LENGTH = 250; //you probably know this
public static String toJson(final List<QuotesData> quotesData) {
jsonVal = new StringBuilder(JSON_LENGTH * quotesData.size());
jsonVal.append("{");
synchronized (quotesData) {
for (QuotesData quote : quotesData) {
jsonVal.append("\"").append(quote.getSymbol()).append("\":[{")
.append("\"ask\":\"").append(quote.getAsk()).append("\",")
.append("\"bid\":\"").append(quote.getBid()).append("\",")
.append("\"time\":\"").append(quote.getDateTime()).append("\"}],");
}
// much much faster than replace
jsonVal.setCharAt(jsonVal.length()-1, '}');
return jsonVal.toString();
}
}
Most of the changes are cosmetic, and I'm pretty sure that the JIT would already optimize them. Only difference I would do is use StringBuilder and create a new one each time and don't use .replace(). But to stress my point further unless you fit the first description (exponential growth) I doubt the problem is here. I would look at you list implementation first.
Upvotes: 0
Reputation: 12545
First I would suggest using JProfiler or JConsole, both included in JDK6, to pinpoint exactly where the performance hit is.
Without knowing where the CPU usage is, I would avoid synchronized
. I doubt append
is the problem. Clean it up by getting rid of the static
local jsonVal
, too.
public static String toJson(final List<QuotesData> quotesData) {
final List<QuotesData> theData = new ArrayList<QuotesData>(quotesData);
StringBuffer jsonVal = new StringBuffer();
jsonVal.append("{");
for (QuotesData quote : quotesData) {
jsonVal.append("\"").append(quote.getSymbol()).append("\":[{");
jsonVal.append("\"ask\":\"").append(quote.getAsk()).append(
"\",");
jsonVal.append("\"bid\":\"").append(quote.getBid()).append(
"\",");
jsonVal.append("\"time\":\"").append(quote.getDateTime())
.append("\"}],");
}
jsonVal.append("}");
String returnString = jsonVal.toString();
return returnString.toString().replace("}],}", "}]}");
}
Consider using a JSON library like Gson. The code becomes much simpler. You can tweak the output if needed:
private static final Gson gson = new Gson();
public static String toJson(final List<QuotesData> quotesData) {
return gson.toJson(new ArrayList<QuoteData>(quotesData));
}
Upvotes: 2
Reputation: 8512
My guest is that StringBuilder is constantly being resized. How many quotesData there is? I suggest you create a StringBuilder with a size before the for loop:
StringBuffer jsonVal = new StringBuffer(quotesData.size()*200); //the 200 is on top of my head. Do a few loop to see what is the average length of a QuotesData.
By the way, have you considered using StringBuilder instead? It's the same as StringBuffer, minus the overhead of being thread-safe (StringBuffer is synchronized, StringBuild is not).
Upvotes: 0