From 5e63493cfdd40cc6035aad0173fa68e9305e1003 Mon Sep 17 00:00:00 2001 From: Patrick Zimmer Date: Sun, 1 Oct 2023 15:24:34 +0200 Subject: [PATCH] more comparable scaling tests --- README.md | 47 +++++------ .../paxel/lintstone/impl/MessageContext.java | 2 +- .../paxel/lintstone/api/FailingTests.java | 2 +- .../paxel/lintstone/api/InternalAskTest.java | 4 +- .../java/paxel/lintstone/api/JmhTest.java | 78 ++++++++++--------- .../lintstone/api/actors/Distributor.java | 7 +- .../actors/{Sorter.java => SorterActor.java} | 2 +- .../api/example/WordGeneratorActor.java | 4 +- 8 files changed, 70 insertions(+), 76 deletions(-) rename src/test/java/paxel/lintstone/api/actors/{Sorter.java => SorterActor.java} (94%) diff --git a/README.md b/README.md index ee84675..567d137 100644 --- a/README.md +++ b/README.md @@ -96,34 +96,27 @@ String v = dist.ask(new EndMessage()) # Benchmarks -Comparison JAVA 11 GroupExecutor variant vs JAVA 21 virtual threads - +Benchmark: +* create system with x actors. +* send 1000 messages to each actor +* finish and remove each actor +* wait until the result was sent to the final actor +* shutdown the system ``` -Benchmark Mode Cnt Score Error Units -JmhTest.run001ActorOn001Thread thrpt 5 14807.641 ± 118.880 ops/s -JmhTest.run001Actors JAVA 21 thrpt 5 318438.884 ± 8725.628 ops/s - -JmhTest.run002ActorOn001Thread thrpt 5 14793.968 ± 132.940 ops/s -JmhTest.run002Actors JAVA 21 thrpt 5 514296.649 ± 23787.449 ops/s - -JmhTest.run010ActorOn001Thread thrpt 5 14767.468 ± 141.431 ops/s -JmhTest.run010ActorOn010Thread thrpt 5 122679.731 ± 2291.937 ops/s -JmhTest.run010Actors JAVA 21 thrpt 5 1141230.953 ± 21649.833 ops/s - -JmhTest.run020ActorOn020Thread thrpt 5 165262.649 ± 9270.499 ops/s -JmhTest.run020Actors JAVA 21 thrpt 5 1268742.526 ± 49428.590 ops/s - -JmhTest.run030ActorOn020Thread thrpt 5 164761.670 ± 5103.291 ops/s -JmhTest.run030Actors JAVA 21 thrpt 5 1268501.106 ± 93959.566 ops/s - -JmhTest.run999ActorOn010Threads thrpt 5 110869.939 ± 1938.133 ops/s -JmhTest.run999Actors JAVA 21 thrpt 5 150694.498 ± 1410.907 ops/s - -JmhTest.run999ActorOnWorkStealingThreads thrpt 5 4200.800 ± 109.647 ops/s -NA - +Benchmark Mode Cnt Score Error Units +JmhTest.run_____1_Actors thrpt 5 317127.717 ± 8029.515 ops/s +JmhTest.run_____2_Actors thrpt 5 547704.933 ± 8683.210 ops/s +JmhTest.run____10_Actors thrpt 5 1384233.070 ± 4930.434 ops/s +JmhTest.run____20_Actors thrpt 5 1727040.878 ± 117252.943 ops/s +JmhTest.run____30_Actors thrpt 5 1847566.975 ± 252653.978 ops/s +JmhTest.run___999_Actors thrpt 5 2073771.534 ± 140663.794 ops/s +JmhTest.run_50000_Actors thrpt 5 1813868.496 ± 90683.616 ops/s ``` -A big improvement performance wise with a cleaner interface to the user +A better test would be: +* setup the system before the benchmark +* send the 1000 messages to each actor +* ask each actor for the sum + * remove each actor +* shutdown the system after the benchmark -Welcome to the world of tomorrow \ No newline at end of file diff --git a/src/main/java/paxel/lintstone/impl/MessageContext.java b/src/main/java/paxel/lintstone/impl/MessageContext.java index 3875f8f..f17bd09 100644 --- a/src/main/java/paxel/lintstone/impl/MessageContext.java +++ b/src/main/java/paxel/lintstone/impl/MessageContext.java @@ -63,7 +63,7 @@ public CompletableFuture ask(String name, Object msg) throws Unregistered throw new UnregisteredRecipientException("Actor with name " + name + " does not exist"); } CompletableFuture result = new CompletableFuture<>(); - actor.get().send(msg, self, mec -> mec.otherwise((m, o) -> { + actor.get().send(msg, self, mec -> mec.otherwise((o, m) -> { try { result.complete((F) o); } catch (Exception e) { diff --git a/src/test/java/paxel/lintstone/api/FailingTests.java b/src/test/java/paxel/lintstone/api/FailingTests.java index 219da31..9750206 100644 --- a/src/test/java/paxel/lintstone/api/FailingTests.java +++ b/src/test/java/paxel/lintstone/api/FailingTests.java @@ -74,7 +74,7 @@ public void testGetDataOut() throws InterruptedException, ExecutionException { // this message goes to an actor that wants to reply. but can't, because we are calling from outside the actor system // so this should be a message in the errorHandler - echoActor.send("you ok?"); + echoActor.send("the error log is expected."); // this is the correct way to ask for data from outside the actorSystem String echo = echoActor.ask("please tell me").get(); assertThat(echo, is("echo")); diff --git a/src/test/java/paxel/lintstone/api/InternalAskTest.java b/src/test/java/paxel/lintstone/api/InternalAskTest.java index 13a17e5..044d746 100644 --- a/src/test/java/paxel/lintstone/api/InternalAskTest.java +++ b/src/test/java/paxel/lintstone/api/InternalAskTest.java @@ -3,7 +3,7 @@ import org.junit.Test; import paxel.lintstone.api.actors.CharCount; import paxel.lintstone.api.actors.Distributor; -import paxel.lintstone.api.actors.Sorter; +import paxel.lintstone.api.actors.SorterActor; import paxel.lintstone.api.actors.WordCount; import paxel.lintstone.api.messages.EndMessage; @@ -41,7 +41,7 @@ public void testAskExternal() throws InterruptedException, ExecutionException, T LintStoneActorAccessor dist = system.registerActor("dist", Distributor::new, ActorSettings.DEFAULT); system.registerActor("wordCount", WordCount::new, ActorSettings.DEFAULT); system.registerActor("charCount", CharCount::new, ActorSettings.DEFAULT); - system.registerActor("sorter", Sorter::new, ActorSettings.DEFAULT); + system.registerActor("sorter", SorterActor::new, ActorSettings.DEFAULT); LintStoneSystem s = LintStoneSystemFactory.create(); LintStoneActorAccessor syncedOut = s.registerActor("out", () -> mec -> mec.otherwise((o, m)->System.out.println(o)), ActorSettings.DEFAULT); diff --git a/src/test/java/paxel/lintstone/api/JmhTest.java b/src/test/java/paxel/lintstone/api/JmhTest.java index 886674f..3218233 100644 --- a/src/test/java/paxel/lintstone/api/JmhTest.java +++ b/src/test/java/paxel/lintstone/api/JmhTest.java @@ -2,7 +2,7 @@ import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.Benchmark; @@ -11,6 +11,7 @@ import org.openjdk.jmh.annotations.OperationsPerInvocation; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; @@ -29,66 +30,74 @@ public class JmhTest { private static final String TEST = "Test"; @Benchmark - @OperationsPerInvocation(1000) - public void run001Actors() throws InterruptedException { + @OperationsPerInvocation(1_000) + public void run_____1_Actors(Blackhole blackhole) throws InterruptedException, ExecutionException { int threads = 1; int actorCount = 1; - int messages = 1000; + int messages = 1_000; - run(actorCount, messages, LintStoneSystemFactory.create()); + run(actorCount, messages, LintStoneSystemFactory.create(), blackhole); } @Benchmark - @OperationsPerInvocation(1000) - public void run002Actors() throws InterruptedException { + @OperationsPerInvocation(2_000) + public void run_____2_Actors(Blackhole blackhole) throws InterruptedException, ExecutionException { int threads = 1; int actorCount = 2; - int messages = 1000; + int messages = 2_000; - run(actorCount, messages, LintStoneSystemFactory.create()); + run(actorCount, messages, LintStoneSystemFactory.create(), blackhole); } @Benchmark - @OperationsPerInvocation(1000) - public void run010Actors() throws InterruptedException { + @OperationsPerInvocation(10_000) + public void run____10_Actors(Blackhole blackhole) throws InterruptedException, ExecutionException { int actorCount = 10; - int messages = 1000; + int messages = 10_000; - run(actorCount, messages, LintStoneSystemFactory.create()); + run(actorCount, messages, LintStoneSystemFactory.create(), blackhole); } @Benchmark - @OperationsPerInvocation(1000) - public void run020Actors() throws InterruptedException { + @OperationsPerInvocation(20_000) + public void run____20_Actors(Blackhole blackhole) throws InterruptedException, ExecutionException { int actorCount = 20; - int messages = 1000; + int messages = 20_000; - run(actorCount, messages, LintStoneSystemFactory.create()); + run(actorCount, messages, LintStoneSystemFactory.create(), blackhole); } @Benchmark - @OperationsPerInvocation(1000) - public void run030Actors() throws InterruptedException { + @OperationsPerInvocation(30_000) + public void run____30_Actors(Blackhole blackhole) throws InterruptedException, ExecutionException { int actorCount = 30; - int messages = 1000; + int messages = 30_000; - run(actorCount, messages, LintStoneSystemFactory.create()); + run(actorCount, messages, LintStoneSystemFactory.create(), blackhole); } @Benchmark - @OperationsPerInvocation(1000) - public void run999Actors() throws InterruptedException { + @OperationsPerInvocation(999_000) + public void run___999_Actors(Blackhole blackhole) throws InterruptedException, ExecutionException { int actorCount = 999; - int messages = 1000; + int messages = 999_000; - run(actorCount, messages, LintStoneSystemFactory.create()); + run(actorCount, messages, LintStoneSystemFactory.create(), blackhole); } - private void run(int actorCount, int messages, LintStoneSystem system) throws InterruptedException, UnregisteredRecipientException { - CountDownLatch latch = new CountDownLatch(actorCount); - system.registerActor("END", () -> new EndActor(latch), ActorSettings.DEFAULT); + @Benchmark + @OperationsPerInvocation(50_000_000) + public void run_50000_Actors(Blackhole blackhole) throws InterruptedException, ExecutionException { + int actorCount = 50_000; + int messages = 50_000_000; + + run(actorCount, messages, LintStoneSystemFactory.create(), blackhole); + } + + + private void run(int actorCount, int messages, LintStoneSystem system, Blackhole blackhole) throws InterruptedException, UnregisteredRecipientException, ExecutionException { List actors = new ArrayList<>(); for (int i = 0; i < actorCount; i++) { actors.add(system.registerActor(TEST + i, MessageActor::new, ActorSettings.DEFAULT)); @@ -98,9 +107,9 @@ private void run(int actorCount, int messages, LintStoneSystem system) throws In } for (int i = 0; i < actorCount; i++) { // finish the actors - actors.get(i).send("END"); + Integer end = actors.get(i).ask("END").get(); + blackhole.consume(end); } - latch.await(); system.shutDownAndWait(); } @@ -112,13 +121,6 @@ public static void main(String[] args) throws RunnerException { new Runner(opt).run(); } - private record EndActor(CountDownLatch latch) implements LintStoneActor { - - @Override - public void newMessageEvent(LintStoneMessageEventContext mec) { - latch.countDown(); - } - } private static class MessageActor implements LintStoneActor { @@ -139,7 +141,7 @@ public void newMessageEvent(LintStoneMessageEventContext mec) { } ).inCase(String.class, (name, reply) -> { // notify to the given name, the sum - reply.send(name, sum); + reply.reply(sum); // and kill yourself reply.unregister(); }).otherwise((a, b) -> System.err.println("unknown message: " + a)); diff --git a/src/test/java/paxel/lintstone/api/actors/Distributor.java b/src/test/java/paxel/lintstone/api/actors/Distributor.java index a7c6e88..3a9373e 100644 --- a/src/test/java/paxel/lintstone/api/actors/Distributor.java +++ b/src/test/java/paxel/lintstone/api/actors/Distributor.java @@ -24,12 +24,11 @@ private void send(String txt, LintStoneMessageEventContext mec) { } private void handleEnd(EndMessage dmg, LintStoneMessageEventContext askContext) { - CompletableFuture words = new CompletableFuture<>(); - CompletableFuture chars = new CompletableFuture<>(); CompletableFuture sort = new CompletableFuture<>(); // each of these completes will be called in the thread context of this actor - askContext.ask("wordCount", new EndMessage(), c -> c.inCase(Integer.class, (r, replyContext) -> words.complete(r))); - askContext.ask("charCount", new EndMessage(), c -> c.inCase(Integer.class, (r, replyContext) -> chars.complete(r))); + CompletableFuture words = askContext.ask("wordCount", new EndMessage()); + CompletableFuture chars = askContext.ask("charCount", new EndMessage()); + // other way to ask askContext.ask("sorter", new EndMessage(), c -> c.inCase(String.class, (r, replyContext) -> sort.complete(r))); // when the last reply comes, the reply of the external ask is fulfilled. diff --git a/src/test/java/paxel/lintstone/api/actors/Sorter.java b/src/test/java/paxel/lintstone/api/actors/SorterActor.java similarity index 94% rename from src/test/java/paxel/lintstone/api/actors/Sorter.java rename to src/test/java/paxel/lintstone/api/actors/SorterActor.java index 347fdc1..832970a 100644 --- a/src/test/java/paxel/lintstone/api/actors/Sorter.java +++ b/src/test/java/paxel/lintstone/api/actors/SorterActor.java @@ -7,7 +7,7 @@ import java.util.*; import java.util.stream.Collectors; -public class Sorter implements LintStoneActor { +public class SorterActor implements LintStoneActor { final Set words = new HashSet<>(); @Override diff --git a/src/test/java/paxel/lintstone/api/example/WordGeneratorActor.java b/src/test/java/paxel/lintstone/api/example/WordGeneratorActor.java index c4b3312..8eca094 100644 --- a/src/test/java/paxel/lintstone/api/example/WordGeneratorActor.java +++ b/src/test/java/paxel/lintstone/api/example/WordGeneratorActor.java @@ -18,7 +18,7 @@ public class WordGeneratorActor implements LintStoneActor { public record Init(Long seed) { } - public record Request(int sylibls) { + public record Request(int syllables) { } public record Word(String value) { @@ -39,7 +39,7 @@ private void createWord(Request request, LintStoneMessageEventContext lintStoneM if (random == null) reinit(0L); StringBuilder stringBuilder = new StringBuilder(); - for (int i = 0; i < request.sylibls(); i++) { + for (int i = 0; i < request.syllables(); i++) { stringBuilder.append(consonants.get(random.nextDouble())); stringBuilder.append(vocals.get(random.nextDouble())); }