Skip to content

Fields optimization #37

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 15 commits into
base: master
Choose a base branch
from
16 changes: 13 additions & 3 deletions quickfixj-core/src/main/java/quickfix/BooleanField.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

package quickfix;

import java.lang.Boolean;
import quickfix.field.converter.BooleanConverter;

public class BooleanField extends Field<Boolean> {

Expand All @@ -28,11 +28,11 @@ public BooleanField(int field) {
}

public BooleanField(int field, Boolean data) {
super(field, data);
super(field, null != data ? data : Boolean.FALSE);
}

public BooleanField(int field, boolean data) {
super(field, data);
super(field, Boolean.valueOf(data));
}

public void setValue(Boolean value) {
Expand All @@ -54,4 +54,14 @@ public boolean valueEquals(Boolean value) {
public boolean valueEquals(boolean value) {
return getObject().equals(value);
}

@Override
protected String objectAsString() {
return BooleanConverter.convert(getValue());
}

@Override
protected void objectAsString(StringBuilder stringBuilder) {
stringBuilder.append(objectAsString());
}
}
73 changes: 65 additions & 8 deletions quickfixj-core/src/main/java/quickfix/DoubleField.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,37 @@

package quickfix;

import quickfix.field.converter.DoubleConverter;

/**
* A double-values message field.
*/
public class DoubleField extends Field<Double> {

private static final Double ZERO = 0d;
private double value = 0d;
private int padding = 0;

public DoubleField(int field) {
super(field, 0d);
super(field, ZERO);
}

public DoubleField(int field, Double data) {
super(field, data);
checkForValidDouble(data);
value = data;
}

public DoubleField(int field, double data) {
super(field, data);
super(field, ZERO);
checkForValidDouble(data);
value = data;
}

public DoubleField(int field, double data, int padding) {
super(field, data);
super(field, ZERO);
checkForValidDouble(data);
value = data;
this.padding = padding;
}

Expand All @@ -53,29 +60,79 @@ public void setValue(Double value) {

public void setValue(double value) {
checkForValidDouble(value);
setObject(value);
this.value = value;
}

public double getValue() {
return getObject();
return value;
}

@Override
protected void setObject(Double object) {
super.setObject(object);
value = object;
}

@Override
public Double getObject() {
Double val = super.getObject();
if(null != val && value == val)
return val;
super.setObject(value);
return super.getObject();
}

public int getPadding() {
return padding;
}

public boolean valueEquals(Double value) {
return getObject().equals(value);
return null != value && valueEquals(value.doubleValue());
}

public boolean valueEquals(double value) {
return getObject().equals(value);
return this.value == value;
}

// QFJ-808: NaN or infinity values cannot be transmitted via FIX in a DoubleField
private void checkForValidDouble(Double value) throws NumberFormatException {
private void checkForValidDouble(double value) throws NumberFormatException {
if (Double.isInfinite(value) || Double.isNaN(value)) {
throw new NumberFormatException("Tried to set NaN or infinite value.");
}
}

@Override
void toString(StringBuilder buffer) {
if (null != super.data) {
buffer.append(super.data);
return;
}
buffer.append(NumbersCache.get(getTag())).append('=');
double val = value;
if(val < 0d) {
buffer.append('-');
val = Math.abs(val);
}
String str = NumbersCache.get(val);
if(null != str)
buffer.append(str);
else
buffer.append(DoubleConverter.convert(val));
}

@Override
protected String objectAsString() {
return DoubleConverter.convert(value);
}

@Override
protected void objectAsString(StringBuilder stringBuilder) {
stringBuilder.append(objectAsString());
}

@Override
public int hashCode() {
long bits = Double.doubleToLongBits(value);
return (int)(bits ^ (bits >>> 32));
}
}
93 changes: 74 additions & 19 deletions quickfixj-core/src/main/java/quickfix/Field.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@
package quickfix;

import org.quickfixj.CharsetSupport;
import quickfix.field.converter.DoubleConverter;

import java.io.Serializable;
import java.math.BigDecimal;

/**
* Base class for FIX message fields. This class should be
Expand All @@ -32,8 +34,7 @@
static final long serialVersionUID = 7098326013456432197L;
private int tag;
private T object;
private boolean isCalculated = false;
private String data;
protected String data;

public Field(int field, T object) {
this.tag = field;
Expand Down Expand Up @@ -66,7 +67,7 @@ public int getField() {
*/
protected void setObject(T object) {
this.object = object;
isCalculated = false;
data = null;
}

/**
Expand All @@ -89,22 +90,70 @@ public String toString() {
}

/*package*/ void toString(StringBuilder buffer) {
buffer.append(tag).append('=').append(objectAsString());
if(null != data) {
buffer.append(data);
return;
}
buffer.append(quickfix.NumbersCache.get(tag)).append('=');
if(object instanceof Number) {
if(object instanceof Integer)
appendNumber(buffer, ((Integer)object).longValue());
else if(object instanceof Double)
appendDouble(buffer, (Double) object);
else if(object instanceof Float)
buffer.append(((Float)object).floatValue());
else if(object instanceof BigDecimal)
buffer.append(((BigDecimal)object).toPlainString());
else
appendNumber(buffer, ((Number)object).longValue());
} else
objectAsString(buffer);
}

private static void appendNumber(StringBuilder buffer, long value) {
if(value < 0) {
buffer.append('-');
value = Math.abs(value);
}
buffer.append(quickfix.NumbersCache.get(value));
}

private static void appendDouble(StringBuilder buffer, double value) {
if(value < 0d) {
buffer.append('-');
value = Math.abs(value);
}
String str = quickfix.NumbersCache.get(value);
if(null != str)
buffer.append(str);
else
buffer.append(DoubleConverter.convert(value));
}

protected String objectAsString() {
return object.toString();
}

protected void objectAsString(StringBuilder stringBuilder) {
stringBuilder.append(objectAsString());
}

public boolean equals(Object object) {
return super.equals(object)
|| object instanceof Field
&& tag == ((Field<?>) object).getField()
&& getObject().equals(((Field<?>) object).getObject());
return super.equals(object) || (object instanceof Field &&
((Field<?>) object).equals(tag, getObject()));
}

private boolean equals(int tag, Object object) {
if(this.tag != tag)
return false;
Object thisObject = getObject();
if(thisObject == null)
return object == null;
return thisObject.equals(object);
}

public int hashCode() {
return object.hashCode();
return getObject().hashCode();
}

/**
Expand All @@ -129,21 +178,27 @@ public int hashCode() {
return (MessageUtils.checksum(CharsetSupport.getCharsetInstance(), data, false) + 1) & 0xFF;
}

private void calculate() {
if (isCalculated) {
return;
private static final ThreadLocal<StringBuilder> buffers = new ThreadLocal<StringBuilder>() {
@Override
protected StringBuilder initialValue() {
return new StringBuilder(256);
}
};

StringBuilder buffer = new StringBuilder();
toString(buffer);
data = buffer.toString();

isCalculated = true;
private void calculate() {
if(null == data) {
StringBuilder buffer = buffers.get();
try {
toString(buffer);
data = buffer.toString();
} finally {
buffer.setLength(0);
}
}
}

public void setTag(int tag) {
this.tag = tag;
isCalculated = false;
calculate();
data = null;
}
}
4 changes: 2 additions & 2 deletions quickfixj-core/src/main/java/quickfix/FileStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -403,12 +403,12 @@ public boolean set(int sequence, String message) throws IOException {

private void storeSenderSequenceNumber() throws IOException {
senderSequenceNumberFile.seek(0);
senderSequenceNumberFile.writeUTF("" + cache.getNextSenderMsgSeqNum());
senderSequenceNumberFile.writeUTF(NumbersCache.get(cache.getNextSenderMsgSeqNum()));
}

private void storeTargetSequenceNumber() throws IOException {
targetSequenceNumberFile.seek(0);
targetSequenceNumberFile.writeUTF("" + cache.getNextTargetMsgSeqNum());
targetSequenceNumberFile.writeUTF(NumbersCache.get(cache.getNextTargetMsgSeqNum()));
}

String getHeaderFileName() {
Expand Down
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;
}
}
Loading