Skip to content

Commit

Permalink
[core] optimize template rendering (#1479)
Browse files Browse the repository at this point in the history
  • Loading branch information
cyrilou242 committed Jul 11, 2024
1 parent 16b969d commit a9fb04b
Show file tree
Hide file tree
Showing 9 changed files with 570 additions and 70 deletions.
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
<!-- Run the Integration test modules after building the distribution (required for plugins) -->
<module>pinot-test-container</module>
<module>thirdeye-integration-tests</module>
<module>thirdeye-benchmarks</module>
</modules>

<properties>
Expand Down
38 changes: 38 additions & 0 deletions thirdeye-benchmarks/benchmark_notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
## How to run
Build
```
./mvnw clean install -DskipTests
./mvnw clean verify -pl 'thirdeye-benchmarks'
```

Run benchmark
```
java -jar thirdeye-benchmarks/target/benchmarks.jar
# or with a regex filter on ClassName.methodName
java -jar thirdeye-benchmarks/target/benchmarks.jar applyContext
```

## Speed-Optimizations
### 1 - StringTemplateUtils - Template properties
Problem: StringTemplateUtils#applyContext - the method to apply properties to template - was extremely slow.
An ObjectMapper was instantiated at each call, so the ser/deserializer obtained by reflection (slow) were re-computed each time.
Optimization: re-use the ObjectMapper. It's a bit tricky because we use custom ser/deser to inject values. Values change at each call.
So we mutate the custom ser/deserializer of the re-used ObjectMapper at each call. This is not thread safe so we introduce a pool of re-usable ObjectMappers.

Before:
```
Benchmark Mode Cnt Score Error Units
StringTemplateUtilsBenchmark.applyContext avgt 7 1480.610 ± 56.571 us/op
```

```
Benchmark Mode Cnt Score Error Units
StringTemplateUtilsBenchmark.applyContext avgt 7 55.539 ± 0.272 us/op
```

For an alert with 1000 enumeration items:
- before: applying properties takes ~ 1000 *1480us ~= 1.5 seconds --> slow in the UI
- after: applying properties takes ~ 1000 *55us ~= 50 milliseconds

Please make sure this does not get slower.
I think there is still a 10x speed improvement opportunity here but this should be fast enough for the moment.
157 changes: 157 additions & 0 deletions thirdeye-benchmarks/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2024 StarTree Inc
Licensed under the StarTree Community License (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.startree.ai/legal/startree-community-license
Unless required by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OF ANY KIND,
either express or implied.
See the License for the specific language governing permissions and limitations under
the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ai.startree.thirdeye</groupId>
<artifactId>thirdeye</artifactId>
<version>1.303.0-SNAPSHOT</version>
</parent>

<artifactId>thirdeye-benchmarks</artifactId>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jmh.version>1.37</jmh.version>
<javac.target>17</javac.target>
<uberjar.name>benchmarks</uberjar.name>
</properties>

<dependencies>
<dependency>
<groupId>ai.startree.thirdeye</groupId>
<artifactId>thirdeye-spi</artifactId>
</dependency>
<dependency>
<groupId>ai.startree.thirdeye</groupId>
<artifactId>thirdeye-core</artifactId>
</dependency>

<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<compilerVersion>${javac.target}</compilerVersion>
<source>${javac.target}</source>
<target>${javac.target}</target>
<annotationProcessorPaths>
<path>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.37</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>${uberjar.name}</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
<filters>
<filter>
<!--
Shading signed JARs will fail without this.
http://stackoverflow.com/questions/999489/invalid-signature-file-when-attempting-to-run-a-jar
-->
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>2.5</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.1</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
</plugin>
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.3</version>
</plugin>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.17</version>
</plugin>
</plugins>
</pluginManagement>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright 2024 StarTree Inc
*
* Licensed under the StarTree Community License (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of the
* License at http://www.startree.ai/legal/startree-community-license
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OF ANY KIND,
* either express or implied.
* See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.sample;

import ai.startree.thirdeye.spi.Constants;
import ai.startree.thirdeye.spi.datalayer.dto.AlertTemplateDTO;
import ai.startree.thirdeye.util.StringTemplateUtils;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;

@Fork(value = 1, jvmArgsPrepend = "-Xmx128m")
@Measurement(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
@Warmup(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
@State(Scope.Thread)
@Threads(1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public class StringTemplateUtilsBenchmark {

AlertTemplateDTO template;
Map<String, Object> valuesMap;

@Setup
public void setup() throws IOException {
String alertTemplateDtoString = IOUtils.resourceToString("/alertTemplateDto.json",
StandardCharsets.UTF_8);
template = Constants.TEMPLATABLE_OBJECT_MAPPER.readValue(alertTemplateDtoString,
AlertTemplateDTO.class);
valuesMap = ImmutableMap.<String, Object>builder()
.put("aggregationColumn", "views")
.put("completenessDelay", "P0D")
.put("monitoringGranularity", "P1D")
.put("max", "${max}")
.put("timezone", "UTC")
.put("queryFilters", "")
.put("aggregationFunction", "sum")
.put("rcaExcludedDimensions", List.of())
.put("timeColumnFormat", "1,DAYS,SIMPLE_DATE_FORMAT,yyyyMMdd")
.put("timeColumn", "date")
.put("min", "${min}")
.put("rcaAggregationFunction", "")
.put("queryLimit", "100000000")
.put("startTime", 1)
.put("endTime", 2)
.put("dataSource", "pinotQuickStartLocal")
.put("dataset", "pageviews")
.build();
}

@Benchmark
public void applyContext(Blackhole blackhole) throws IOException {
blackhole.consume(StringTemplateUtils.applyContext(template, valuesMap));
}
}
Loading

0 comments on commit a9fb04b

Please sign in to comment.