From 4df0c881f987533a54740cc3b548bb55c7164dbf Mon Sep 17 00:00:00 2001 From: Francesco Nigro Date: Tue, 14 Oct 2025 07:53:31 +0200 Subject: [PATCH] [JBTM-4014] Reduce allocation pressure on Uid --- .../com/arjuna/ats/arjuna/common/Uid.java | 125 ++++++++++++------ .../com/arjuna/ats/arjuna/utils/Utility.java | 116 ++++++++++++++-- .../hp/mwtests/ts/arjuna/uid/UidUnitTest.java | 7 + 3 files changed, 198 insertions(+), 50 deletions(-) diff --git a/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/common/Uid.java b/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/common/Uid.java index 9693e7aa8a..265e0ddfea 100644 --- a/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/common/Uid.java +++ b/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/common/Uid.java @@ -4,16 +4,15 @@ */ package com.arjuna.ats.arjuna.common; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; import java.io.IOException; import java.io.PrintStream; import java.io.Serializable; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; import java.net.UnknownHostException; -import java.nio.charset.StandardCharsets; +import java.nio.ByteOrder; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import com.arjuna.ats.arjuna.exceptions.FatalError; import com.arjuna.ats.arjuna.logging.tsLogger; @@ -89,14 +88,22 @@ public Uid (byte[] byteForm) try { - ByteArrayInputStream ba = new ByteArrayInputStream(byteForm); - DataInputStream ds = new DataInputStream(ba); - - hostAddr[0] = ds.readLong(); - hostAddr[1] = ds.readLong(); - process = ds.readInt(); - sec = ds.readInt(); - other = ds.readInt(); + + if (byteForm.length < UID_SIZE) { + throw new IllegalArgumentException("byteForm too small: " + byteForm.length); + } + long hostAddr0 = (long) BE_LONG_ARRAY_VIEW.get(byteForm, 0); + long hostAddr1 = (long) BE_LONG_ARRAY_VIEW.get(byteForm, 8); + int process = (int) BE_INT_ARRAY_VIEW.get(byteForm, 16); + int sec = (int) BE_INT_ARRAY_VIEW.get(byteForm, 20); + int other = (int) BE_INT_ARRAY_VIEW.get(byteForm, 24); + + long[] hostAddr = this.hostAddr; + hostAddr[0] = hostAddr0; + hostAddr[1] = hostAddr1; + this.process = process; + this.sec = sec; + this.other = other; _valid = true; } @@ -292,18 +299,44 @@ public void print (PrintStream strm) strm.print(""); } - public String stringForm () - { - // no need to synchronize since object is immutable + public String stringForm () { + // no need to synchronize since object is immutable - if (_stringForm == null) - _stringForm = Utility.longToHexString(hostAddr[0]) + Uid.breakChar - + Utility.longToHexString(hostAddr[1]) + Uid.breakChar - + Utility.intToHexString(process) + Uid.breakChar - + Utility.intToHexString(sec) + Uid.breakChar - + Utility.intToHexString(other); + if (_stringForm == null) { + _stringForm = stringForm(breakChar); + } + return _stringForm; + } - return _stringForm; + public String stringForm(byte breakChar) { + long[] hostAddr = this.hostAddr; + long hostAddr0 = hostAddr[0]; + long hostAddr1 = hostAddr[1]; + int process = this.process; + int sec = this.sec; + int other = this.other; + int hostAddr0Chars = Utility.hexCharsOf(hostAddr0); + int hostAddr1Chars = Utility.hexCharsOf(hostAddr1); + int processChars = Utility.hexCharsOf(process); + int secChars = Utility.hexCharsOf(sec); + int otherChars = Utility.hexCharsOf(other); + int totalChars = hostAddr0Chars + hostAddr1Chars + processChars + secChars + otherChars + 4; // 4 break chars + byte[] asciiBytes = new byte[totalChars]; + int pos = 0; + Utility.toHexChars(hostAddr0, asciiBytes, pos, hostAddr0Chars); + pos += hostAddr0Chars; + asciiBytes[pos++] = breakChar; + Utility.toHexChars(hostAddr1, asciiBytes, pos, hostAddr1Chars); + pos += hostAddr1Chars; + asciiBytes[pos++] = breakChar; + Utility.toHexChars(process, asciiBytes, pos, processChars); + pos += processChars; + asciiBytes[pos++] = breakChar; + Utility.toHexChars(sec, asciiBytes, pos, secChars); + pos += secChars; + asciiBytes[pos++] = breakChar; + Utility.toHexChars(other, asciiBytes, pos, otherChars); + return new String(asciiBytes, 0, 0, totalChars); } /** @@ -313,11 +346,7 @@ public String stringForm () public String fileStringForm () { - return Utility.longToHexString(hostAddr[0]) + Uid.fileBreakChar - + Utility.longToHexString(hostAddr[1]) + Uid.fileBreakChar - + Utility.intToHexString(process) + Uid.fileBreakChar - + Utility.intToHexString(sec) + Uid.fileBreakChar - + Utility.intToHexString(other); + return stringForm(fileBreakChar); } /** @@ -336,19 +365,24 @@ public byte[] getBytes () if (_byteForm == null) { - ByteArrayOutputStream ba = new ByteArrayOutputStream(UID_SIZE); - DataOutputStream ds = new DataOutputStream(ba); - - try + + + long[] hostAddr = this.hostAddr; + int process = this.process; + int sec = this.sec; + int other = this.other; + try { - ds.writeLong(hostAddr[0]); - ds.writeLong(hostAddr[1]); - ds.writeInt(process); - ds.writeInt(sec); - ds.writeInt(other); - //_byteForm = stringForm().getBytes(StandardCharsets.UTF_8); - - _byteForm = ba.toByteArray(); + + long hostAddr0 = hostAddr[0]; + long hostAddr1 = hostAddr[1]; + byte[] newByteForm = new byte[UID_SIZE]; + BE_LONG_ARRAY_VIEW.set(newByteForm, 0, hostAddr0); + BE_LONG_ARRAY_VIEW.set(newByteForm, 8, hostAddr1); + BE_INT_ARRAY_VIEW.set(newByteForm, 16, process); + BE_INT_ARRAY_VIEW.set(newByteForm, 20, sec); + BE_INT_ARRAY_VIEW.set(newByteForm, 24, other); + BYTE_FORM_UPDATER.lazySet(this, newByteForm); } catch (final Throwable ex) { tsLogger.i18NLogger.warn_common_Uid_getbytes(ex); @@ -722,9 +756,16 @@ private static final char getBreakChar (String uidString) private static volatile int initTime; - private static final char breakChar = ':'; + private static final VarHandle BE_LONG_ARRAY_VIEW = MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN); + + private static final VarHandle BE_INT_ARRAY_VIEW = MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.BIG_ENDIAN); + + private static final AtomicReferenceFieldUpdater BYTE_FORM_UPDATER = + AtomicReferenceFieldUpdater.newUpdater(Uid.class, byte[].class, "_byteForm"); + + private static final byte breakChar = ':'; - private static final char fileBreakChar = '_'; + private static final byte fileBreakChar = '_'; private static final Uid NIL_UID = new Uid("0:0:0:0:0"); diff --git a/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/utils/Utility.java b/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/utils/Utility.java index 62ed443bcc..43694a45a7 100644 --- a/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/utils/Utility.java +++ b/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/utils/Utility.java @@ -78,15 +78,115 @@ public static int hexStringToInt (String s) throws NumberFormatException return val; } - /** - * Convert a long to a hex String. - */ + private static final byte[] HEX_DIGITS; + + static { + // each byte contains 2 hex digits + HEX_DIGITS = new byte[16]; + HEX_DIGITS[0] = (byte) '0'; + HEX_DIGITS[1] = (byte) '1'; + HEX_DIGITS[2] = (byte) '2'; + HEX_DIGITS[3] = (byte) '3'; + HEX_DIGITS[4] = (byte) '4'; + HEX_DIGITS[5] = (byte) '5'; + HEX_DIGITS[6] = (byte) '6'; + HEX_DIGITS[7] = (byte) '7'; + HEX_DIGITS[8] = (byte) '8'; + HEX_DIGITS[9] = (byte) '9'; + HEX_DIGITS[10] = (byte) 'a'; + HEX_DIGITS[11] = (byte) 'b'; + HEX_DIGITS[12] = (byte) 'c'; + HEX_DIGITS[13] = (byte) 'd'; + HEX_DIGITS[14] = (byte) 'e'; + HEX_DIGITS[15] = (byte) 'f'; + } + + /** + * Determine the number of hex chars needed to represent the given int + * value. This is at least 1. + * + * @param value the int value + * @return the number of hex chars needed to represent the value + */ + public static int hexCharsOf(int value) { + int signLen = 0; + if (value < 0) { + value = -value; + signLen = 1; + } + int nonZeroBits = Integer.SIZE - Integer.numberOfLeadingZeros(value); + // each hex char represents 4 bits: align the non-zero bits to the next multiple of 4 and divide by 4 + return signLen + Math.max(((nonZeroBits + 3) >> 2), 1); + } + + /** + * Convert a int to hex chars. The byte array must be big enough to hold + * the expected number of hex chars. See {@link #hexCharsOf(int)} to determine + * the number of hex chars needed. + * + * @param value the int value to convert + * @param ascii the byte array to hold the hex chars + * @param offset the offset into the byte array to start writing + * @param expectedHexChars the expected number of hex chars to write + */ + public static void toHexChars(int value, byte[] ascii, int offset, int expectedHexChars) { + int numHexChars = expectedHexChars; + if (value < 0) { + value = -value; + ascii[offset++] = (byte) '-'; + numHexChars--; + } + // write the hex digits in reverse order + for (int i = numHexChars - 1; i >= 0; i--) { + int digit = value & 0xF; + ascii[offset + i] = HEX_DIGITS[digit]; + value >>>= 4; + } + } + + /** + * Determine the number of hex chars needed to represent the given long + * value. This is at least 1. + * + * @param value the long value + * @return the number of hex chars needed to represent the value + */ + public static int hexCharsOf(long value) { + int signLen = 0; + if (value < 0) { + value = -value; + signLen = 1; + } + int nonZeroBits = Long.SIZE - Long.numberOfLeadingZeros(value); + // each hex char represents 4 bits: align the non-zero bits to the next multiple of 4 and divide by 4 + return signLen + Math.max(((nonZeroBits + 3) >> 2), 1); + } + + /** + * Convert a long to hex chars. The byte array must be big enough to hold + * the expected number of hex chars. See {@link #hexCharsOf(long)} to determine + * the number of hex chars needed. + * + * @param value the long value to convert + * @param ascii the byte array to hold the hex chars + * @param offset the offset into the byte array to start writing + * @param expectedHexChars the expected number of hex chars to write + */ + public static void toHexChars(long value, byte[] ascii, int offset, int expectedHexChars) { + int numHexChars = expectedHexChars; + if (value < 0) { + value = -value; + ascii[offset++] = (byte) '-'; + numHexChars--; + } + // write the hex digits in reverse order + for (int i = numHexChars - 1; i >= 0; i--) { + int digit = (int) (value & 0xF); + ascii[offset + i] = HEX_DIGITS[digit]; + value >>>= 4; + } + } - public static String longToHexString (long number) - throws NumberFormatException - { - return Long.toString(number, 16); - } /** * Convert a hex String to a long diff --git a/ArjunaCore/arjuna/tests/classes/com/hp/mwtests/ts/arjuna/uid/UidUnitTest.java b/ArjunaCore/arjuna/tests/classes/com/hp/mwtests/ts/arjuna/uid/UidUnitTest.java index 3741e0b95a..8341c7e76f 100644 --- a/ArjunaCore/arjuna/tests/classes/com/hp/mwtests/ts/arjuna/uid/UidUnitTest.java +++ b/ArjunaCore/arjuna/tests/classes/com/hp/mwtests/ts/arjuna/uid/UidUnitTest.java @@ -133,5 +133,12 @@ public void testMaxMinUid () throws Exception assertTrue(uid.greaterThan(minUid)); assertTrue(maxUid.greaterThan(minUid)); assertTrue(maxUid.greaterThan(uid)); + + // check the min/max string and file string forms + + assertEquals("-8000000000000000:-8000000000000000:-80000000:-80000000:-80000000", minUid.stringForm()); + assertEquals("7fffffffffffffff:7fffffffffffffff:7fffffff:7fffffff:7fffffff", maxUid.stringForm()); + assertEquals("-8000000000000000_-8000000000000000_-80000000_-80000000_-80000000", minUid.fileStringForm()); + assertEquals("7fffffffffffffff_7fffffffffffffff_7fffffff_7fffffff_7fffffff", maxUid.fileStringForm()); } } \ No newline at end of file