Skip to content

Allocations optimization for converters #34

New issue

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

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

Already on GitHub? Sign in to your account

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 [email protected] 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