Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improving Qute Escaper to be as branch-free as possible #45546

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

franz1981
Copy link
Contributor

@franz1981 franz1981 commented Jan 13, 2025

This is a show case of the approach at lemire/Code-used-on-Daniel-Lemire-s-blog#116 but biased for the one and two replacement latin chars case.
It could be expanded to cover for non-latin and the 6 bytes replacement too, with more painfull and complex (in term of logic) changes - but it looks already complex as it is IMO.

Feedbacks are wellcome as questions.
I didn't yet benchmarked it (is sadly low in my prio list - so IDK when I'll have time to contribute a proper bench which stress the branch-predictor in qute-benchmark) - and there's a good chance my "bet" to use StringBuilder to simplify it, making use of the horrible setLength in the hot path, won't pay off.
In such unfortunate case, I will move to using char[] despite it requires latinness checks due to compact string, on String construction :"(

@quarkus-bot quarkus-bot bot added the area/qute The template engine label Jan 13, 2025
@franz1981 franz1981 marked this pull request as ready for review January 15, 2025 13:55
@franz1981
Copy link
Contributor Author

franz1981 commented Jan 15, 2025

Using the benchmark at mkouba/qute-benchmarks#1

shows these differences in perf - now:

Benchmark                   (ctrlProbabibility)  (latinCharsProbability)  (replacementProbability)  (samples)  (size)  Mode  Cnt   Score   Error  Units
JsonEscaping.escape                           0                      100                         0        100      32  avgt   10  10.129 ± 0.009  ns/op
JsonEscaping.escape                           0                      100                         0      10000      32  avgt   10  10.129 ± 0.038  ns/op
JsonEscaping.escape                           0                      100                        10        100      32  avgt   10  45.312 ± 0.193  ns/op
JsonEscaping.escape                           0                      100                        10      10000      32  avgt   10  55.989 ± 1.826  ns/op
JsonEscaping.escape                          10                      100                        10        100      32  avgt   10  44.131 ± 2.367  ns/op
JsonEscaping.escape                          10                      100                        10      10000      32  avgt   10  77.187 ± 2.496  ns/op

vs before

Benchmark            (ctrlProbabibility)  (latinCharsProbability)  (replacementProbability)  (samples)  (size)  Mode  Cnt    Score    Error  Units
JsonEscaping.escape                    0                      100                         0        100      32  avgt   10   16.864 ±  0.514  ns/op
JsonEscaping.escape                    0                      100                         0      10000      32  avgt   10   66.942 ±  0.524  ns/op
JsonEscaping.escape                    0                      100                        10        100      32  avgt   10   97.493 ±  5.357  ns/op
JsonEscaping.escape                    0                      100                        10      10000      32  avgt   10  220.471 ±  7.429  ns/op
JsonEscaping.escape                   10                      100                        10        100      32  avgt   10  178.098 ± 15.693  ns/op
JsonEscaping.escape                   10                      100                        10      10000      32  avgt   10  319.793 ± 40.863  ns/op

which is a good improvement - which tends to pay-off more as the number of chars increases

Fyi @mkouba the relevant ones are using 10000 samples since with just 100 the data are very predictable for my CPU model and you cannot really see the benefits of reducing the number of branches

This comment has been minimized.

@franz1981
Copy link
Contributor Author

franz1981 commented Jan 15, 2025

@galderz on mkouba/qute-benchmarks#1

this implementation should really shine with native image, since it doesn't use StringBuilder :)

This comment has been minimized.

@mkouba
Copy link
Contributor

mkouba commented Jan 17, 2025

Using the benchmark at mkouba/qute-benchmarks#1

I had to rewrite the JsonEscaping benchmark so that it's using a template instead of JsonEscaper because this class was only introduced in 3.18 and we need to compile/run benchmarks for older versions as well...

shows these differences in perf with before:

Benchmark                   (ctrlProbabibility)  (latinCharsProbability)  (replacementProbability)  (samples)  (size)  Mode  Cnt   Score   Error  Units
JsonEscaping.escape                           0                      100                         0        100      32  avgt   10  10.129 ± 0.009  ns/op
JsonEscaping.escape                           0                      100                         0      10000      32  avgt   10  10.129 ± 0.038  ns/op
JsonEscaping.escape                           0                      100                        10        100      32  avgt   10  45.312 ± 0.193  ns/op
JsonEscaping.escape                           0                      100                        10      10000      32  avgt   10  55.989 ± 1.826  ns/op
JsonEscaping.escape                          10                      100                        10        100      32  avgt   10  44.131 ± 2.367  ns/op
JsonEscaping.escape                          10                      100                        10      10000      32  avgt   10  77.187 ± 2.496  ns/op

vs 7ec85cc

Benchmark            (ctrlProbabibility)  (latinCharsProbability)  (replacementProbability)  (samples)  (size)  Mode  Cnt    Score    Error  Units
JsonEscaping.escape                    0                      100                         0        100      32  avgt   10   16.864 ±  0.514  ns/op
JsonEscaping.escape                    0                      100                         0      10000      32  avgt   10   66.942 ±  0.524  ns/op
JsonEscaping.escape                    0                      100                        10        100      32  avgt   10   97.493 ±  5.357  ns/op
JsonEscaping.escape                    0                      100                        10      10000      32  avgt   10  220.471 ±  7.429  ns/op
JsonEscaping.escape                   10                      100                        10        100      32  avgt   10  178.098 ± 15.693  ns/op
JsonEscaping.escape                   10                      100                        10      10000      32  avgt   10  319.793 ± 40.863  ns/op

which is a good improvement - which tends to pay-off more as the number of chars increases

Aren't those numbers swapped given the fact that avgt mode is used (average time per per operation - lower is better)?

Fyi @mkouba the relevant ones are using 10000 samples since with just 100 the data are very predictable for my CPU model and you cannot really see the benefits of reducing the number of branches

Ok 👍

@franz1981
Copy link
Contributor Author

franz1981 commented Jan 17, 2025

using a template instead of JsonEscaper

Numbers still look the same? Did you checked 🙏?

Aren't those numbers swapped given the fact that avgt mode is used (average time per per operation - lower is better)?

Ops 🤣 I copied in the wrong order , let me fix it

@franz1981 franz1981 marked this pull request as draft January 20, 2025 15:02
@franz1981
Copy link
Contributor Author

The HTML one can still made faster, working on it

@quarkus-bot quarkus-bot bot added area/devtools Issues/PR related to maven, gradle, platform and cli tooling/plugins area/documentation labels Jan 20, 2025
@franz1981 franz1981 marked this pull request as ready for review January 20, 2025 15:34
Copy link

quarkus-bot bot commented Jan 20, 2025

Status for workflow Quarkus Documentation CI

This is the status report for running Quarkus Documentation CI on commit d4d3d81.

✅ The latest workflow run for the pull request has completed successfully.

It should be safe to merge provided you have a look at the other checks in the summary.

Warning

There are other workflow runs running, you probably need to wait for their status before merging.

Copy link

🎊 PR Preview 04deb60 has been successfully built and deployed to https://quarkus-pr-main-45546-preview.surge.sh/version/main/guides/

  • Images of blog posts older than 3 months are not available.
  • Newsletters older than 3 months are not available.

// NOTE: this holding 8 values to allow the JIT to remove the bound checks since
// the replacement id is always in range [0, 7] due to the REPLACEMENT_ID_MASK value
private static final String[] REPLACEMENTS = { null, """, "'", "&", "<", ">", null, null };
// We use 4 bits to pack the replacement id for each Latin character in the [0, 255] range.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The doc here is not up to date with the latest changes..I ended up accepting having a slightly bigger footprint, but better latency

This comment has been minimized.

Copy link

quarkus-bot bot commented Jan 21, 2025

Status for workflow Quarkus CI

This is the status report for running Quarkus CI on commit 1939891.

Failing Jobs

Status Name Step Failures Logs Raw logs Build scan
Native Tests - Misc4 Build Failures Logs Raw logs 🔍

Full information is available in the Build summary check run.
You can consult the Develocity build scans.

Failures

⚙️ Native Tests - Misc4 #

- Failing: integration-tests/observability-lgtm 

📦 integration-tests/observability-lgtm

io.quarkus.observability.test.LgtmResourcesIT.testTracing - History - More details - Source on GitHub

java.lang.RuntimeException: 
java.lang.RuntimeException: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
	[error]: Build step io.quarkus.observability.deployment.ObservabilityDevServiceProcessor#startContainers threw an exception: java.lang.RuntimeException: org.testcontainers.containers.ContainerLaunchException: Container startup failed for image docker.io/grafana/otel-lgtm:0.8.2
	at io.quarkus.observability.deployment.ObservabilityDevServiceProcessor.lambda$startContainers$3(ObservabilityDevServiceProcessor.java:170)
	at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1625)
	at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:762)
	at io.quarkus.observability.deployment.ObservabilityDevServiceProcessor.startContainers(ObservabilityDevServiceProcessor.java:113)
	at java.base/java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:732)

Flaky tests - Develocity

⚙️ JVM Tests - JDK 17

📦 integration-tests/observability-lgtm

io.quarkus.observability.test.LgtmServicesTest.testTracing - History

  • java.lang.RuntimeException: io.quarkus.builder.BuildException: Build failure: Build failed due to errors [error]: Build step io.quarkus.observability.deployment.ObservabilityDevServiceProcessor\#startContainers threw an exception: java.lang.RuntimeException: org.testcontainers.containers.ContainerLaunchException: Container startup failed for image docker.io/grafana/otel-lgtm:0.8.2 at io.quarkus.observability.deployment.ObservabilityDevServiceProcessor.lambda$startContainers$3(ObservabilityDevServiceProcessor.java:170) at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1625) at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:762) - java.lang.RuntimeException
java.lang.RuntimeException: 
java.lang.RuntimeException: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
	[error]: Build step io.quarkus.observability.deployment.ObservabilityDevServiceProcessor#startContainers threw an exception: java.lang.RuntimeException: org.testcontainers.containers.ContainerLaunchException: Container startup failed for image docker.io/grafana/otel-lgtm:0.8.2
	at io.quarkus.observability.deployment.ObservabilityDevServiceProcessor.lambda$startContainers$3(ObservabilityDevServiceProcessor.java:170)
	at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1625)
	at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:762)
	at io.quarkus.observability.deployment.ObservabilityDevServiceProcessor.startContainers(ObservabilityDevServiceProcessor.java:113)
	at java.base/java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:732)

⚙️ JVM Tests - JDK 21

📦 integration-tests/observability-lgtm

io.quarkus.observability.test.LgtmReloadTest.testReload - History

  • io.quarkus.builder.BuildException: Build failure: Build failed due to errors [error]: Build step io.quarkus.observability.deployment.ObservabilityDevServiceProcessor\#startContainers threw an exception: java.lang.RuntimeException: org.testcontainers.containers.ContainerLaunchException: Container startup failed for image docker.io/grafana/otel-lgtm:0.8.2 at io.quarkus.observability.deployment.ObservabilityDevServiceProcessor.lambda$startContainers$3(ObservabilityDevServiceProcessor.java:170) at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708) at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:762) - java.lang.RuntimeException
java.lang.RuntimeException: 
io.quarkus.builder.BuildException: Build failure: Build failed due to errors
	[error]: Build step io.quarkus.observability.deployment.ObservabilityDevServiceProcessor#startContainers threw an exception: java.lang.RuntimeException: org.testcontainers.containers.ContainerLaunchException: Container startup failed for image docker.io/grafana/otel-lgtm:0.8.2
	at io.quarkus.observability.deployment.ObservabilityDevServiceProcessor.lambda$startContainers$3(ObservabilityDevServiceProcessor.java:170)
	at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708)
	at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:762)
	at io.quarkus.observability.deployment.ObservabilityDevServiceProcessor.startContainers(ObservabilityDevServiceProcessor.java:113)
	at java.base/java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:733)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/devtools Issues/PR related to maven, gradle, platform and cli tooling/plugins area/documentation area/qute The template engine triage/flaky-test
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants