Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/main/java/com/xceptance/common/lang/StringHasher.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package com.xceptance.common.lang;

import com.xceptance.xlt.api.util.XltCharBuffer;

/**
* A class the supports special ways to hash a string to improve the overall performance and
* reduce cache misses.
Expand Down Expand Up @@ -55,4 +57,26 @@ public static int hashCodeWithLimit(final CharSequence s, final char limitingCha

return hash;
}

/**
* Hashes a string up to a terminal character excluding it
* This implementation uses the String hashcode after finding the limiting character.
* because String::indexOf became very fast in JDK 21 (native code).
*
* @param s the sequence of characters to hash up to the limiter
* @return the hashcode
*/
public static int hashCodeWithLimit(final XltCharBuffer s, final char limitingChar)
{
String _s = s.toString();
final int pos = _s.indexOf(limitingChar);
if (pos > 0)
{
return _s.substring(0, pos).hashCode();
}
else
{
return _s.hashCode();
}
Comment on lines +71 to +80
Copy link

Copilot AI Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition pos > 0 will skip hashing when the limiting character is at position 0, returning the full string's hash instead of an empty string's hash. This should be pos >= 0 to handle the case where the limiting character is the first character correctly.

Copilot uses AI. Check for mistakes.
}
}
2 changes: 0 additions & 2 deletions src/main/java/com/xceptance/xlt/api/engine/AbstractData.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,6 @@ public void setBaseValues(final List<XltCharBuffer> values)
{
// read and check the values
name = values.get(1).toString();
name.hashCode(); // create it when it is still hot in the cache

time = ParseNumbers.parseLong(values.get(2));

if (time <= 0)
Expand Down
18 changes: 13 additions & 5 deletions src/main/java/com/xceptance/xlt/api/util/XltCharBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -704,15 +704,23 @@ public int hashCode()
final int l = length & ~(8 - 1);
final int l2 = length + from;

for (; i < l; i += 8)
for (; i < l; i += 8)
{
h = -1807454463 * h + 1742810335 * src[i + 0] + 887503681 * src[i + 1] + 28629151 * src[i + 2] + 923521 * src[i + 3] +
29791 * src[i + 4] + 961 * src[i + 5] + 31 * src[i + 6] + 1 * src[i + 7];
h = -1807454463 * h;
int h1 = 1742810335 * src[i] +
887503681 * src[i+1] +
28629151 * src[i+2] +
923521 * src[i+3] +
29791 * src[i+4] +
961 * src[i+5] +
31 * src[i+6] +
src[i+7];
h += h1;
}

for (; i < l2; i++)
for (; i < l2; i++)
{
h = 31 * h + src[i];
h = (src[i] - h) + (h << 5);
Copy link

Copilot AI Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hash computation formula (src[i] - h) + (h << 5) differs significantly from the standard multiplication-based approach used in the main loop. This inconsistency makes the algorithm harder to understand and may produce different hash distribution characteristics. Consider using a consistent approach like h = 31 * h + src[i] or document why this specific formula was chosen.

Suggested change
h = (src[i] - h) + (h << 5);
h = 31 * h + src[i];

Copilot uses AI. Check for mistakes.
}

this.hashCode = h;
Expand Down
42 changes: 37 additions & 5 deletions src/main/java/com/xceptance/xlt/report/DataRecordFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,16 @@
import java.lang.reflect.Constructor;
import java.util.Map;

import com.xceptance.xlt.agent.JvmResourceUsageData;
import com.xceptance.xlt.api.engine.ActionData;
import com.xceptance.xlt.api.engine.CustomData;
import com.xceptance.xlt.api.engine.CustomValue;
import com.xceptance.xlt.api.engine.Data;
import com.xceptance.xlt.api.engine.EventData;
import com.xceptance.xlt.api.engine.PageLoadTimingData;
import com.xceptance.xlt.api.engine.RequestData;
import com.xceptance.xlt.api.engine.TransactionData;
import com.xceptance.xlt.api.engine.WebVitalData;
import com.xceptance.xlt.api.util.XltCharBuffer;
import com.xceptance.xlt.api.util.XltException;

Expand Down Expand Up @@ -98,11 +107,34 @@ public DataRecordFactory(final Map<String, Class<? extends Data>> dataClasses)
*/
public Data createStatistics(final XltCharBuffer src) throws Exception
{
// TODO: The following may throw NullPointerException or ArrayIndexOutOfBoundsException in case of unknown type
// codes.
final Constructor<? extends Data> c = constructors[src.charAt(0) - offset];
final Data data = c.newInstance();
// com.xceptance.xlt.reportgenerator.dataRecords.T = com.xceptance.xlt.api.engine.TransactionData
// com.xceptance.xlt.reportgenerator.dataRecords.A = com.xceptance.xlt.api.engine.ActionData
// com.xceptance.xlt.reportgenerator.dataRecords.R = com.xceptance.xlt.api.engine.RequestData
// com.xceptance.xlt.reportgenerator.dataRecords.C = com.xceptance.xlt.api.engine.CustomData
// com.xceptance.xlt.reportgenerator.dataRecords.E = com.xceptance.xlt.api.engine.EventData
// com.xceptance.xlt.reportgenerator.dataRecords.J = com.xceptance.xlt.agent.JvmResourceUsageData
// com.xceptance.xlt.reportgenerator.dataRecords.V = com.xceptance.xlt.api.engine.CustomValue
// com.xceptance.xlt.reportgenerator.dataRecords.P = com.xceptance.xlt.api.engine.PageLoadTimingData
// com.xceptance.xlt.reportgenerator.dataRecords.W = com.xceptance.xlt.api.engine.WebVitalData

return data;
return switch (src.charAt(0))
{
case 'A' -> new ActionData();
case 'T' -> new TransactionData();
case 'R' -> new RequestData();
case 'C' -> new CustomData();
case 'E' -> new EventData();
case 'J' -> new JvmResourceUsageData();
case 'V' -> new CustomValue();
case 'P' -> new PageLoadTimingData();
case 'W' -> new WebVitalData();

default ->
{
// use reflection to create the instance
final Constructor<? extends Data> c = constructors[src.charAt(0) - offset];
yield c.newInstance();
}
};
}
}