apache / apache/jmeter

Optimize getPropertyOrNull(PropertyDescriptor) access with index-based storage

Open
#5,968 0 comments 0 reactions 0 assignees View on GitHub
performance to-triage
Dominant language
Java
Stars
9.5k
Forks
2.3k
Avg merge
1d 22h
Merged PRs (30d)
5

Description

### Use case

JMeter often queries properties at runtime. For instance, `ResultCollector` `isSuccessOnlyLogging` and `isErrorLogging` call `getProperty(String)` which is cotly.

### Possible solution

If test element schemas are specified with inheritance, then we could copy properties to an array where elements are `JMeterProperty` in their declaration order.
Then access `getPropertyOrNull(PropertyDescriptor)` could be optimized with indices.

For instance:

```kotlin
public open class TestElementSchema protected constructor() : DefaultTestElementSchema() {
public companion object INSTANCE : TestElementSchema()

public val name: StringPropertyDescriptor =
string("TestElement.name")
public val comments: StringPropertyDescriptor =
string("TestElement.comments")
...
```

```kotlin
public open class ResultCollectorSchema: TestElementSchema() {
public companion object INSTANCE: ResultCollectorSchema()

public val successOnly: BooleanPropertyDescriptor =
boolean("ResultCollector.success_only_logging")

public val errorOnly: BooleanPropertyDescriptor =
boolean("ResultCollector.error_logging")
```

It would result in the following indices:

```
0 name
1 comments
2 successOnly
3 errorOnly
```

Then, if we need to query `successOnly` at runtime, we can check if the element declares `ResultCollectorSchema` (or its subtype), and then we can access element `2` from properties array.

Here's a sketch:

```java
public class ResultCollector extends ... {
public boolean isSuccessOnlyLogging() {
return get(getSchema().getSuccessOnly());
// return getPropertyAsBoolean(SUCCESS_ONLY_LOGGING,false);
}

public boolean isErrorLogging() {
return get(getSchema().getErrorOnly());
// return getPropertyAsBoolean(ERROR_LOGGING);
}
```

```java
public abstract class AbstractTestElement ... {
private final transient JMeterProperty[] fastProps = new JMeterProperty[getSchema().getProperties().size()];

// Optimized property access
public JMeterProperty getPropertyOrNull(PropertyDescriptor key) {
int index = key.getIndex();
JMeterProperty[] fastProps = this.fastProps;
if (index >= 0 && index < fastProps.length) {
if (key.getSchema().isInstance(getSchema())) {
return fastProps[index];
}
}
return getPropertyOrNull(key.getName());
}

// Every property store would store the property to fastProps array
private void storeFastProperty(JMeterProperty prop) {
PropertyDescriptor descr = getSchema().getProperties().get(prop.getName());
if (descr != null) {
int propIndex = descr.getIndex();
if (propIndex >= 0 && propIndex < fastProps.length) {
fastProps[propIndex] = prop;
}
}
}
```

I assume we have only a few "property store" calls at the runtime, so it should be fine to perform extra work in `setProperty`, `remove(JMeterProperty)`.

A draft benchmark (see `ResultCollectorBenchmark` in `core/src/jmh`) shows ~8ns for the current `isSampleWanted` and ~3ns with the optimized `getPropertyOrNull(PropertyDescriptor` case.

It is not dramatic, however, it would be interesting to test it more.

### Possible workarounds

_No response_

### JMeter Version

5.5

### Java Version

_No response_

### OS Version

_No response_

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.