Conversation
29cc3ae to
130f727
Compare
He-Pin
left a comment
There was a problem hiding this comment.
Verified the new byte-table validator is semantically equivalent to the old charAt scan (exhaustive differential check across all 65,536 code units, 0 divergences) and binary-compatible (only a private method body changed). Two suggestions inline.
| [info] ActorPathValidationBenchmark.charAtLoopActor_1 true thrpt 5 49.306 ± 8.706 ops/us | ||
| [info] ActorPathValidationBenchmark.handLoop7000 false thrpt 5 0.176 ± 0.012 ops/us | ||
| [info] ActorPathValidationBenchmark.handLoop7000 true thrpt 5 0.142 ± 0.094 ops/us | ||
| [info] ActorPathValidationBenchmark.handLoopActor_1 false thrpt 5 138.841 ± 111.054 ops/us |
There was a problem hiding this comment.
The handLoopActor_1 confidence intervals here (+/-111 and +/-281 ops/us) are wider than the scores themselves, so this run cannot support the unpolluted speedup claim for short names. Could you rerun with more forks/iterations (e.g. -f 3 -i 10) before merge?
There was a problem hiding this comment.
I need to come back to this - I may have access to another machine later this week where I can run benchmarks with less noise
There was a problem hiding this comment.
Reran with -f 3 -wi 5 -i 10 (2s iterations) and updated the benchmark comment and PR description:
| Benchmark | polluted=false | polluted=true |
|---|---|---|
charAtLoopActor_1 (previous code) |
108.3 ± 4.3 ops/µs | 41.8 ± 0.5 ops/µs |
handLoopActor_1 (this PR) |
161.0 ± 6.8 ops/µs | 162.0 ± 3.5 ops/µs |
charAtLoop7000 (previous code) |
0.152 ± 0.003 ops/µs | 0.086 ± 0.004 ops/µs |
handLoop7000 (this PR) |
0.101 ± 0.014 ops/µs | 0.096 ± 0.007 ops/µs |
The short-name speedup holds with tight intervals (~1.5× unpolluted, ~3.9× polluted). The longer run also shows that for the 7000-char input the byte-table version is ~34% slower than charAt when unpolluted (the getBytes copy dominates) and roughly equal when polluted; the earlier noisy run hid that, and the description now says so. Actor names are short in practice, so I think the trade-off is worth it.
| "must not be empty") | ||
| } | ||
|
|
||
| "accept valid path elements" in { |
There was a problem hiding this comment.
Consider adding an exhaustive sweep over all 65,536 chars asserting isValidPathElement matches an independently computed predicate; it runs in milliseconds and would permanently lock in equivalence with the previous charAt implementation.
There was a problem hiding this comment.
Added an exhaustive sweep over all 65,536 chars in 5199810 (branch rebased on main too). It checks isValidPathElement against an independently defined valid-char/hex-digit predicate for each char at position 0, after another char, and in both hex positions of a %XX escape. Runs in under a second.
Motivation: ActorPath.findInvalidPathElementCharPosition runs for every named actorOf and scanned the name with String.charAt plus a pattern match with guards and a ValidSymbols.indexOf per non-alphanumeric character. String.charAt inlines to `isLatin1() ? StringLatin1.charAt : StringUTF16.charAt`, and that branch is profiled once, JVM-wide, in String.charAt's own bytecode. Any non-ASCII string handled anywhere in the process pollutes it, after which C2 compiles both coders into every charAt loop. Measured locally, the validator halves in throughput (101 -> 49 ops/us for "actor-1") once the profile is polluted. Modification: - Copy the element out once with getBytes(ISO_8859_1) (an intrinsic array copy for Latin-1 strings) and scan the byte[] with a 128-entry flag table (valid char / hex digit) instead of calling charAt per character. Characters outside Latin-1 encode as '?' and 0x80-0xFF as negative bytes, both invalid, so accepted set and reported position are unchanged. - Add directional ActorPathSpec tests for accepted names, rejected names and the reported position, including Latin-1, non-Latin-1 and surrogate-pair input. - Extend ActorPathValidationBenchmark with a `polluted` param that pre-warms String.charAt with UTF-16 strings, and keep the previous charAt-based validator there as charAtLoop* for comparison. Result: Name validation no longer depends on the String.charAt coder profile: 138-162 ops/us for "actor-1" polluted or not, versus 101 unpolluted and 49 polluted before. Also tried the same for MurmurHash.stringHash and Helpers.base64 and found no benefit (hash arithmetic and StringBuilder.append dominate), so those are unchanged. Tests: - sbt "actor-tests/testOnly org.apache.pekko.actor.ActorPathSpec org.apache.pekko.actor.LocalActorRefProviderSpec" (25 passed) - sbt "actor-tests/testOnly org.apache.pekko.routing.ConsistentHashingRouterSpec" (earlier run, passed) - sbt "actor/mimaReportBinaryIssues" (no issues) - sbt "bench-jmh/Jmh/compile" and Jmh/run of the pollution benchmark - scalafmt on changed Scala files, git diff --check References: None - performance follow-up to the hand-written actor name validator
Sweep all 65,536 chars and assert isValidPathElement agrees with an independently defined valid-char and hex-digit predicate, at position 0, after another char, and inside a %XX escape.
130f727 to
5199810
Compare
Motivation: Review feedback: the handLoopActor_1 confidence intervals in the recorded single-fork run were wider than the scores, so they could not support the short-name speedup claim. Modification: Replace the recorded results with a -f 3 -wi 5 -i 10 run and note that for very long names the byte-table scanner is slower than an unpolluted charAt loop because of the getBytes copy. Result: "actor-1": 161.0 +/- 6.8 / 162.0 +/- 3.5 ops/us (unpolluted/polluted) versus 108.3 +/- 4.3 / 41.8 +/- 0.5 for the charAt loop. 7000 chars: 0.101 / 0.096 versus 0.152 / 0.086. Tests: - sbt "bench-jmh/Jmh/run -f 3 -wi 5 -w 2s -i 10 -r 2s .*ActorPathValidationBenchmark.(handLoop|charAtLoop).*" - Not run - scalafmt, comment-only change References: None - review follow-up on apache#3542
Motivation
ActorPath.findInvalidPathElementCharPositionruns for every namedactorOf. It scanned the name withString.charAtinside a pattern match with guards, plus aValidSymbols.indexOffor every non-alphanumeric character.String.charAtinlines toisLatin1() ? StringLatin1.charAt : StringUTF16.charAt, and that branch is profiled once, JVM-wide, inString.charAt's own bytecode. Any non-ASCII string handled anywhere in the process (a Jackson payload, a log line) pollutes that profile, after which C2 compiles both coders into everycharAtloop. Measured locally, the validator halves in throughput once the profile is polluted.Modification
getBytes(ISO_8859_1)— an intrinsic array copy for Latin-1 strings — and scan thebyte[]with a 128-entry flag table (valid-char bit, hex-digit bit) instead of callingcharAtper character. Characters outside Latin-1 encode as?and 0x80–0xFF as negative bytes; both are invalid, so the accepted set and the reported position are unchanged.ActorPathSpectests for accepted names, rejected names and the reported error position, including Latin-1, non-Latin-1 and surrogate-pair input.isValidPathElementagrees with an independently defined valid-char/hex-digit predicate (at position 0, after another char, and inside a%XXescape).ActorPathValidationBenchmarkwith apollutedparam that pre-warmsString.charAtwith UTF-16 strings, and keep the previouscharAt-based validator there ascharAtLoop*for comparison.Result
Name validation no longer depends on the
String.charAtcoder profile. Local JMH (-f 3 -wi 5 -w 2s -i 10 -r 2s):charAtLoopActor_1(previous code)handLoopActor_1(this PR)charAtLoop7000(previous code)handLoop7000(this PR)For typical short names this PR is ~1.5× faster unpolluted and ~3.9× faster polluted. For a pathological 7000-char name it is ~34% slower than an unpolluted
charAtloop (thegetBytescopy dominates) and roughly on par when polluted.I tried the same treatment on the other hot
charAtusers,MurmurHash.stringHashandHelpers.base64, and measured no benefit (hash arithmetic andStringBuilder.appenddominate;toCharArrayonly adds an allocation), so those are left as they are.Tests
sbt "actor-tests/testOnly org.apache.pekko.actor.ActorPathSpec org.apache.pekko.actor.LocalActorRefProviderSpec"— 25 passedsbt "actor-tests/testOnly org.apache.pekko.actor.ActorPathSpec"after adding the exhaustive sweep — 18 passed (sweep takes under a second)sbt "actor-tests/testOnly org.apache.pekko.routing.ConsistentHashingRouterSpec"— passedsbt "actor/mimaReportBinaryIssues"— no issuessbt "bench-jmh/Jmh/compile"andsbt "bench-jmh/Jmh/run -f 3 -wi 5 -w 2s -i 10 -r 2s .*ActorPathValidationBenchmark.(handLoop|charAtLoop).*"scalafmton changed Scala files,git diff --checkReferences
None - performance follow-up to the hand-written actor name validator