From f7fc9e355e195e2ed99591ef64ced8dd53084b6c Mon Sep 17 00:00:00 2001 From: charlesbr1 Date: Sat, 1 Aug 2015 02:34:50 -0500 Subject: [PATCH 1/5] Create NumbersCache.java A cache for strings of frequently used integers --- .../src/main/java/quickfix/NumbersCache.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 quickfixj-core/src/main/java/quickfix/NumbersCache.java diff --git a/quickfixj-core/src/main/java/quickfix/NumbersCache.java b/quickfixj-core/src/main/java/quickfix/NumbersCache.java new file mode 100644 index 000000000..a3c72707a --- /dev/null +++ b/quickfixj-core/src/main/java/quickfix/NumbersCache.java @@ -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 littleNumbers; + public static final ArrayList bigNumbers; + + static { + littleNumbers = new ArrayList(littleNumbersLength); + bigNumbers = new ArrayList(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; + } +} From 77c0bf448959a8bfa2526e74761ff4b641cb8d5f Mon Sep 17 00:00:00 2001 From: charlesbr1 Date: Sat, 1 Aug 2015 02:42:40 -0500 Subject: [PATCH 2/5] Optimized message checksum and length calculation Avoided some temporary IntField creation to compute length. In fact, the "isStringEquivalent" branch that I added is useless with previous commit on Message class, which directly compute length & checksum if possible. Change some 'for each' list iteration by old loop over index to avoid iterator allocations, as the jvm will not always do it for us. --- .../src/main/java/quickfix/FieldMap.java | 56 +++++++++++++------ 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/quickfixj-core/src/main/java/quickfix/FieldMap.java b/quickfixj-core/src/main/java/quickfix/FieldMap.java index 2e9202f3f..eb29233e6 100644 --- a/quickfixj-core/src/main/java/quickfix/FieldMap.java +++ b/quickfixj-core/src/main/java/quickfix/FieldMap.java @@ -42,6 +42,7 @@ import quickfix.field.converter.UtcDateOnlyConverter; import quickfix.field.converter.UtcTimeOnlyConverter; import quickfix.field.converter.UtcTimestampConverter; +import org.quickfixj.CharsetSupport; /** * Field container used by messages, groups, and composites. @@ -437,8 +438,9 @@ protected void calculateString(StringBuilder buffer, int[] preFields, int[] post } else if (isGroupField(tag) && isOrderedField(tag, fieldOrder) && getGroupCount(tag) > 0) { appendField(buffer, field); - for (Group group : getGroups(tag)) { - group.calculateString(buffer, preFields, postFields); + List groups = getGroups(tag); + for (int i = 0; i < groups.size(); i++) { + groups.get(i).calculateString(buffer, preFields, postFields); } } } @@ -449,10 +451,10 @@ && getGroupCount(tag) > 0) { final List groups = entry.getValue(); int groupCount = groups.size(); if (groupCount > 0) { - final IntField countField = new IntField(groupCountTag.intValue(), groupCount); - appendField(buffer, countField); - for (Group group : groups) { - group.calculateString(buffer, preFields, postFields); + buffer.append(NumbersCache.get(groupCountTag.intValue())).append('='); + buffer.append(NumbersCache.get(groupCount)).append('\001'); + for (int i = 0; i < groups.size(); i++) { + groups.get(i).calculateString(buffer, preFields, postFields); } } } @@ -465,6 +467,8 @@ && getGroupCount(tag) > 0) { } } + private static final boolean isStringEquivalent = CharsetSupport.isStringEquivalent(CharsetSupport.getCharsetInstance()); + int calculateLength() { int result = 0; for (final Field field : fields.values()) { @@ -478,17 +482,27 @@ int calculateLength() { for (Entry> entry : groups.entrySet()) { final List groupList = entry.getValue(); if (!groupList.isEmpty()) { - final IntField groupField = new IntField(entry.getKey()); - groupField.setValue(groupList.size()); - result += groupField.getLength(); - for (final Group group : groupList) { - result += group.calculateLength(); + if(isStringEquivalent) { + result += getStringLength(entry.getKey()) + getStringLength(groupList.size()) + 2; + } else { + result += MessageUtils.length(CharsetSupport.getCharsetInstance(), NumbersCache.get(entry.getKey())); + result += MessageUtils.length(CharsetSupport.getCharsetInstance(), NumbersCache.get(groupList.size())); + result += 2; + } + for (int i = 0; i < groupList.size(); i++) { + result += groupList.get(i).calculateLength(); } } } return result; } + private static int getStringLength(int num) { + if(num == 0) + return 1; + return (int)(num > 0 ? Math.log10(num) + 1 : Math.log10(-num) + 2); + } + int calculateChecksum() { int result = 0; for (final Field field : fields.values()) { @@ -500,11 +514,21 @@ int calculateChecksum() { for (Entry> entry : groups.entrySet()) { final List groupList = entry.getValue(); if (!groupList.isEmpty()) { - final IntField groupField = new IntField(entry.getKey()); - groupField.setValue(groupList.size()); - result += groupField.getChecksum(); - for (final Group group : groupList) { - result += group.calculateChecksum(); + if(isStringEquivalent) { + String value = NumbersCache.get(entry.getKey()); + for (int i = value.length(); i-- != 0;) + result += value.charAt(i); + value = NumbersCache.get(groupList.size()); + for (int i = value.length(); i-- != 0;) + result += value.charAt(i); + result += '=' + 1; + } else { + final IntField groupField = new IntField(entry.getKey()); + groupField.setValue(groupList.size()); + result += groupField.getChecksum(); + } + for (int i = 0; i < groupList.size(); i++) { + result += groupList.get(i).calculateChecksum(); } } } From 36f8aaf8569e633896d4ec6040a7d2287171ff5e Mon Sep 17 00:00:00 2001 From: Christoph John Date: Sat, 31 Mar 2018 23:44:43 +0200 Subject: [PATCH 3/5] no-op change to trigger Travis build --- quickfixj-core/src/main/java/quickfix/FieldMap.java | 1 + 1 file changed, 1 insertion(+) diff --git a/quickfixj-core/src/main/java/quickfix/FieldMap.java b/quickfixj-core/src/main/java/quickfix/FieldMap.java index eb29233e6..3f97faac5 100644 --- a/quickfixj-core/src/main/java/quickfix/FieldMap.java +++ b/quickfixj-core/src/main/java/quickfix/FieldMap.java @@ -657,4 +657,5 @@ public boolean hasGroup(Group group) { return hasGroup(group.getFieldTag()); } + } From a0444cae0142995eaa4ef98016a38240c1993319 Mon Sep 17 00:00:00 2001 From: chrjohn Date: Mon, 2 Apr 2018 00:24:00 +0200 Subject: [PATCH 4/5] - minor renaming - changed NumbersCache to only hold numbers up to 99999 since it should be sufficient for normal use cases - removed NumbersCache.get(double) method since its usage is questionable as it will truncate a double to a long value --- .../src/main/java/quickfix/FieldMap.java | 8 ++-- .../src/main/java/quickfix/NumbersCache.java | 45 +++++-------------- 2 files changed, 15 insertions(+), 38 deletions(-) diff --git a/quickfixj-core/src/main/java/quickfix/FieldMap.java b/quickfixj-core/src/main/java/quickfix/FieldMap.java index 840f87633..53057d908 100644 --- a/quickfixj-core/src/main/java/quickfix/FieldMap.java +++ b/quickfixj-core/src/main/java/quickfix/FieldMap.java @@ -476,7 +476,7 @@ && getGroupCount(tag) > 0) { final List groups = entry.getValue(); int groupCount = groups.size(); if (groupCount > 0) { - buffer.append(NumbersCache.get(groupCountTag.intValue())).append('='); + buffer.append(NumbersCache.get(groupCountTag)).append('='); buffer.append(NumbersCache.get(groupCount)).append('\001'); for (int i = 0; i < groups.size(); i++) { groups.get(i).calculateString(buffer, preFields, postFields); @@ -492,7 +492,7 @@ && getGroupCount(tag) > 0) { } } - private static final boolean isStringEquivalent = CharsetSupport.isStringEquivalent(CharsetSupport.getCharsetInstance()); + private static final boolean IS_STRING_EQUIVALENT = CharsetSupport.isStringEquivalent(CharsetSupport.getCharsetInstance()); int calculateLength() { int result = 0; @@ -507,7 +507,7 @@ int calculateLength() { for (Entry> entry : groups.entrySet()) { final List groupList = entry.getValue(); if (!groupList.isEmpty()) { - if(isStringEquivalent) { + if(IS_STRING_EQUIVALENT) { result += getStringLength(entry.getKey()) + getStringLength(groupList.size()) + 2; } else { result += MessageUtils.length(CharsetSupport.getCharsetInstance(), NumbersCache.get(entry.getKey())); @@ -539,7 +539,7 @@ int calculateChecksum() { for (Entry> entry : groups.entrySet()) { final List groupList = entry.getValue(); if (!groupList.isEmpty()) { - if(isStringEquivalent) { + if(IS_STRING_EQUIVALENT) { String value = NumbersCache.get(entry.getKey()); for (int i = value.length(); i-- != 0;) result += value.charAt(i); diff --git a/quickfixj-core/src/main/java/quickfix/NumbersCache.java b/quickfixj-core/src/main/java/quickfix/NumbersCache.java index a3c72707a..ed6b22e61 100644 --- a/quickfixj-core/src/main/java/quickfix/NumbersCache.java +++ b/quickfixj-core/src/main/java/quickfix/NumbersCache.java @@ -22,53 +22,30 @@ 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 + * A cache for commonly used strings representing numbers. + * Holds values from 0 to 99999. */ 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 littleNumbers; - public static final ArrayList bigNumbers; + private static final int LITTLE_NUMBERS_LENGTH = 100000; + private static final ArrayList LITTLE_NUMBERS; static { - littleNumbers = new ArrayList(littleNumbersLength); - bigNumbers = new ArrayList(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)); - + LITTLE_NUMBERS = new ArrayList<>(LITTLE_NUMBERS_LENGTH); + for (int i = 0; i < LITTLE_NUMBERS_LENGTH; i++) + LITTLE_NUMBERS.add(Integer.toString(i)); } /** - * Get the string representing the given number + * 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); + public static String get(int i) { + if (i < LITTLE_NUMBERS_LENGTH) + return LITTLE_NUMBERS.get(i); 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; - } } From 39699f3426b0919abc3d9e58b8b771f9e01944f7 Mon Sep 17 00:00:00 2001 From: chrjohn Date: Wed, 2 May 2018 23:05:20 +0200 Subject: [PATCH 5/5] re-added get(double) method to NumbersCache --- .../src/main/java/quickfix/NumbersCache.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/quickfixj-core/src/main/java/quickfix/NumbersCache.java b/quickfixj-core/src/main/java/quickfix/NumbersCache.java index ed6b22e61..d34c1e7bc 100644 --- a/quickfixj-core/src/main/java/quickfix/NumbersCache.java +++ b/quickfixj-core/src/main/java/quickfix/NumbersCache.java @@ -48,4 +48,17 @@ public static String get(int i) { 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; + } + }