Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Allocations optimization for converters #34

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
969 changes: 969 additions & 0 deletions quickfixj-core/src/main/java/quickfix/CharSequenceReader.java

Large diffs are not rendered by default.

74 changes: 74 additions & 0 deletions quickfixj-core/src/main/java/quickfix/NumbersCache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*******************************************************************************
* Copyright (c) quickfixengine.org All rights reserved.
*
* This file is part of the QuickFIX FIX Engine
*
* This file may be distributed under the terms of the quickfixengine.org
* license as defined by quickfixengine.org and appearing in the file
* LICENSE included in the packaging of this file.
*
* This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
* THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE.
*
* See http://www.quickfixengine.org/LICENSE for licensing information.
*
* Contact ask@quickfixengine.org if any conditions of this licensing
* are not clear to you.
******************************************************************************/

package quickfix;

import java.util.ArrayList;

/**
* A cache for commonly used string representing numbers.
* Hold values from 0 to 999999 and from 1000 to 200 000 000 by step of 1000
*/
public final class NumbersCache {

private static final int littleNumbersLength = 1000000;
private static final int bigNumbersLength = 200000;
private static final int bigNumbersOffset = 1000;
private static final int bigNumbersMax = bigNumbersLength * bigNumbersOffset;

public static final ArrayList<String> littleNumbers;
public static final ArrayList<String> bigNumbers;

static {
littleNumbers = new ArrayList<String>(littleNumbersLength);
bigNumbers = new ArrayList<String>(bigNumbersLength);
for (int i = 0; i < littleNumbersLength; i++)
littleNumbers.add(Integer.toString(i));
for (long i = 0; i < bigNumbersLength;)
bigNumbers.add(Long.toString(++i * bigNumbersOffset));

}

/**
* Get the string representing the given number
*
* @param i the long to convert
* @return the String representing the long
*/
public static String get(long i) {
if (i < littleNumbersLength)
return littleNumbers.get((int)i);
if (i <= bigNumbersMax && i % bigNumbersOffset == 0)
return bigNumbers.get((int)(i/bigNumbersOffset)-1);
return String.valueOf(i);
}

/**
* Get the string representing the given double if it's an integer
*
* @param d the double to convert
* @return the String representing the double or null if the double is not an integer
*/
public static String get(double d) {
long l = (long)d;
if (d == (double)l)
return get(l);
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import java.text.DateFormat;
import java.text.DateFormatSymbols;
import java.text.FieldPosition;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.TimeZone;
Expand Down Expand Up @@ -56,18 +57,31 @@ protected static void throwFieldConvertError(String value, String type)
}

protected static long parseLong(String s) {
return parseLong(s, 0, s.length());
}

protected static long parseLong(String s, int begin, int end) {
long n = 0;
for (int i = 0; i < s.length(); i++) {
for (int i = begin; i < end; i++) {
n = (n * 10) + (s.charAt(i) - '0');
}
return n;
}

protected DateFormat createDateFormat(String format) {
protected static DateFormat createDateFormat(String format) {
SimpleDateFormat sdf = new SimpleDateFormat(format);
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
sdf.setDateFormatSymbols(new DateFormatSymbols(Locale.US));
return sdf;
}

static final class DontCareFieldPosition extends FieldPosition {
// The singleton of DontCareFieldPosition.
static final FieldPosition INSTANCE = new DontCareFieldPosition();

private DontCareFieldPosition() {
super(0);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@
*/
public class CharConverter {

private static final String characters[] = new String[Character.MAX_VALUE+1];

static {
for(int c = Character.MAX_VALUE+1; c-- != 0;)
characters[c] = String.valueOf(c);
}

/**
* Converts a character to a String
*
Expand All @@ -34,7 +41,7 @@ public class CharConverter {
* @see java.lang.Character#toString(char)
*/
public static String convert(char c) {
return Character.toString(c);
return characters[c];
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,23 @@
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import quickfix.CharSequenceReader;
import quickfix.FieldConvertError;
import quickfix.NumbersCache;
import quickfix.RuntimeError;

/**
* Converts between a double and a String.
*/
public class DoubleConverter {
private static final Pattern decimalPattern = Pattern.compile("-?\\d*(\\.\\d*)?");
private static final ThreadLocal<DecimalFormat[]> threadDecimalFormats = new ThreadLocal<DecimalFormat[]>();

private static final ThreadLocal<DecimalFormat[]> threadDecimalFormats = new ThreadLocal<DecimalFormat[]>() {
@Override
protected DecimalFormat[] initialValue() {
return new DecimalFormat[14];
}
};

/**
* Converts a double to a string with no padding.
Expand All @@ -52,10 +57,6 @@ static DecimalFormat getDecimalFormat(int padding) {
throw new RuntimeError("maximum padding of 14 zeroes is supported: " + padding);
}
DecimalFormat[] decimalFormats = threadDecimalFormats.get();
if (decimalFormats == null) {
decimalFormats = new DecimalFormat[14];
threadDecimalFormats.set(decimalFormats);
}
DecimalFormat f = decimalFormats[padding];
if (f == null) {
StringBuilder buffer = new StringBuilder("0.");
Expand All @@ -80,7 +81,8 @@ static DecimalFormat getDecimalFormat(int padding) {
* @return the formatted String representing the double.
*/
public static String convert(double d, int padding) {
return getDecimalFormat(padding).format(d);
String value = NumbersCache.get(d);
return null != value ? value : getDecimalFormat(padding).format(d);
}

/**
Expand All @@ -92,11 +94,7 @@ public static String convert(double d, int padding) {
*/
public static double convert(String value) throws FieldConvertError {
try {
Matcher matcher = decimalPattern.matcher(value);
if (!matcher.matches()) {
throw new NumberFormatException();
}
return Double.parseDouble(value);
return CharSequenceReader.valueOf(value);
} catch (NumberFormatException e) {
throw new FieldConvertError("invalid double value: " + value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package quickfix.field.converter;

import quickfix.FieldConvertError;
import quickfix.NumbersCache;

/**
* Convert between an integer and a String
Expand All @@ -34,7 +35,7 @@ public final class IntConverter {
* @see java.lang.Long#toString(long)
*/
public static String convert(int i) {
return Long.toString(i);
return NumbersCache.get(i);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,20 @@
* Convert between a date and a String
*/
public class UtcDateOnlyConverter extends AbstractDateTimeConverter {

protected static final class Context {
private final DateFormat dateFormat = createDateFormat("yyyyMMdd");
private final StringBuffer buffer = new StringBuffer(128);
}

// SimpleDateFormats are not thread safe. A thread local is being
// used to maintain high concurrency among multiple session threads
private static final ThreadLocal<UtcDateOnlyConverter> utcDateConverter = new ThreadLocal<UtcDateOnlyConverter>();
private final DateFormat dateFormat = createDateFormat("yyyyMMdd");
private static final ThreadLocal<Context> utcDateConverter = new ThreadLocal<Context>() {
@Override
protected Context initialValue() {
return new Context();
}
};

/**
* Convert a date to a String ("YYYYMMDD")
Expand All @@ -41,16 +51,29 @@ public class UtcDateOnlyConverter extends AbstractDateTimeConverter {
* @return the formatted date
*/
public static String convert(Date d) {
return getFormatter().format(d);
Context context = utcDateConverter.get();
try {
context.dateFormat.format(d, context.buffer, DontCareFieldPosition.INSTANCE);
return context.buffer.toString();
} finally {
context.buffer.setLength(0);
}
}

private static DateFormat getFormatter() {
UtcDateOnlyConverter converter = utcDateConverter.get();
if (converter == null) {
converter = new UtcDateOnlyConverter();
utcDateConverter.set(converter);
/**
* Convert a date to a String ("YYYYMMDD")
*
* @param d the date to convert
* @param stringBuilder the out buffer to hold formatted date
*/
public static void convert(Date d, StringBuilder stringBuilder) {
Context context = utcDateConverter.get();
try {
context.dateFormat.format(d, context.buffer, DontCareFieldPosition.INSTANCE);
stringBuilder.append(context.buffer);
} finally {
context.buffer.setLength(0);
}
return converter.dateFormat;
}

/**
Expand All @@ -66,7 +89,7 @@ public static Date convert(String value) throws FieldConvertError {
assertLength(value, 8, type);
assertDigitSequence(value, 0, 8, type);
try {
d = getFormatter().parse(value);
d = utcDateConverter.get().dateFormat.parse(value);
} catch (ParseException e) {
throwFieldConvertError(value, type);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,21 @@
* Convert between a time and a String.
*/
public class UtcTimeOnlyConverter extends AbstractDateTimeConverter {

protected static final class Context {
private final DateFormat utcTimeFormat = createDateFormat("HH:mm:ss");
private final DateFormat utcTimeFormatMillis = createDateFormat("HH:mm:ss.SSS");
private final StringBuffer buffer = new StringBuffer(128);
}

// SimpleDateFormats are not thread safe. A thread local is being
// used to maintain high concurrency among multiple session threads
private static final ThreadLocal<UtcTimeOnlyConverter> utcTimeConverter = new ThreadLocal<UtcTimeOnlyConverter>();
private final DateFormat utcTimeFormat = createDateFormat("HH:mm:ss");
private final DateFormat utcTimeFormatMillis = createDateFormat("HH:mm:ss.SSS");
private static final ThreadLocal<Context> utcTimeConverter = new ThreadLocal<Context>() {
@Override
protected Context initialValue() {
return new Context();
}
};

/**
* Convert a time (represented as a Date) to a String (HH:MM:SS or HH:MM:SS.SSS)
Expand All @@ -43,15 +53,36 @@ public class UtcTimeOnlyConverter extends AbstractDateTimeConverter {
* @return a String representing the time.
*/
public static String convert(Date d, boolean includeMilliseconds) {
return getFormatter(includeMilliseconds).format(d);
Context context = utcTimeConverter.get();
try {
(includeMilliseconds ? context.utcTimeFormatMillis : context.utcTimeFormat)
.format(d, context.buffer, DontCareFieldPosition.INSTANCE);
return context.buffer.toString();
} finally {
context.buffer.setLength(0);
}
}

private static DateFormat getFormatter(boolean includeMillis) {
UtcTimeOnlyConverter converter = utcTimeConverter.get();
if (converter == null) {
converter = new UtcTimeOnlyConverter();
utcTimeConverter.set(converter);
/**
* Convert a time (represented as a Date) to a String (HH:MM:SS or HH:MM:SS.SSS)
*
* @param d the date with the time to convert
* @param includeMilliseconds controls whether milliseconds are included in the result
* @param stringBuilder the out buffer to hold a String representing the time.
*/
public static void convert(Date d, StringBuilder stringBuilder, boolean includeMilliseconds) {
Context context = utcTimeConverter.get();
try {
(includeMilliseconds ? context.utcTimeFormatMillis : context.utcTimeFormat)
.format(d, context.buffer, DontCareFieldPosition.INSTANCE);
stringBuilder.append(context.buffer);
} finally {
context.buffer.setLength(0);
}
}

private static DateFormat getFormatter(boolean includeMillis) {
Context converter = utcTimeConverter.get();
return includeMillis ? converter.utcTimeFormatMillis : converter.utcTimeFormat;
}

Expand Down
Loading