Selaa lähdekoodia

[micronaut] Add loom variants (#10055)

* loom benchmarks

* inherit JAVA_OPTIONS

* add async-profiler support

* Revert "add async-profiler support"

This reverts commit 497c017121af2f8ec119b0fe22ebd370ea5508b8.

* disable trackAllThreads

* more logging

* try disabling throughput mode

* io_uring

* remove views dependency

* use io_uring for all vertx connections
Jonas Konrad 3 viikkoa sitten
vanhempi
commit
357165c495

+ 56 - 10
frameworks/Java/micronaut/benchmark_config.json

@@ -21,7 +21,7 @@
         "webserver": "Netty",
         "os": "Linux",
         "database_os": "Linux",
-        "display_name": "Micronaut Vertx PG Client",
+        "display_name": "Micronaut [Vertx PG Client]",
         "notes": "",
         "versus": "None"
       },
@@ -44,7 +44,53 @@
         "webserver": "Netty",
         "os": "Linux",
         "database_os": "Linux",
-        "display_name": "Micronaut Vertx PG Client GraalVM",
+        "display_name": "Micronaut [Vertx PG Client] [GraalVM]",
+        "notes": "",
+        "versus": "None"
+      },
+      "loom-fjp": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "fortune_url": "/fortunes",
+        "update_url": "/updates?queries=",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "Micronaut",
+        "language": "Java",
+        "flavor": "None",
+        "orm": "Raw",
+        "platform": "Netty",
+        "webserver": "Netty",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Micronaut [Vertx PG Client] [Virtual Threads FJP]",
+        "notes": "",
+        "versus": "None"
+      },
+      "loom-on-netty": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "fortune_url": "/fortunes",
+        "update_url": "/updates?queries=",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "Micronaut",
+        "language": "Java",
+        "flavor": "None",
+        "orm": "Raw",
+        "platform": "Netty",
+        "webserver": "Netty",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Micronaut [Vertx PG Client] [Virtual Threads Loom-On-Netty]",
         "notes": "",
         "versus": "None"
       },
@@ -65,7 +111,7 @@
         "webserver": "Netty",
         "os": "Linux",
         "database_os": "Linux",
-        "display_name": "Micronaut JDBC",
+        "display_name": "Micronaut [JDBC]",
         "notes": "",
         "versus": "None"
       },
@@ -86,7 +132,7 @@
         "webserver": "Netty",
         "os": "Linux",
         "database_os": "Linux",
-        "display_name": "Micronaut JDBC GraalVM",
+        "display_name": "Micronaut [JDBC] [GraalVM]",
         "notes": "",
         "versus": "None"
       },
@@ -107,7 +153,7 @@
         "webserver": "Netty",
         "os": "Linux",
         "database_os": "Linux",
-        "display_name": "Micronaut R2DBC",
+        "display_name": "Micronaut [R2DBC]",
         "notes": "",
         "versus": "None"
       },
@@ -128,7 +174,7 @@
         "webserver": "Netty",
         "os": "Linux",
         "database_os": "Linux",
-        "display_name": "Micronaut R2DBC GraalVM",
+        "display_name": "Micronaut [R2DBC] [GraalVM]",
         "notes": "",
         "versus": "None"
       },
@@ -149,7 +195,7 @@
         "webserver": "Netty",
         "os": "Linux",
         "database_os": "Linux",
-        "display_name": "Micronaut Data JDBC",
+        "display_name": "Micronaut [Data JDBC]",
         "notes": "",
         "versus": "None"
       },
@@ -170,7 +216,7 @@
         "webserver": "Netty",
         "os": "Linux",
         "database_os": "Linux",
-        "display_name": "Micronaut Data JDBC GraalVM",
+        "display_name": "Micronaut [Data JDBC] [GraalVM]",
         "notes": "",
         "versus": "None"
       },
@@ -191,7 +237,7 @@
         "webserver": "Netty",
         "os": "Linux",
         "database_os": "Linux",
-        "display_name": "Micronaut Data MongoDB",
+        "display_name": "Micronaut [Data MongoDB]",
         "notes": "",
         "versus": "None"
       },
@@ -212,7 +258,7 @@
         "webserver": "Netty",
         "os": "Linux",
         "database_os": "Linux",
-        "display_name": "Micronaut Data MongoDB GraalVM",
+        "display_name": "Micronaut [Data MongoDB] [GraalVM]",
         "notes": "",
         "versus": "None"
       }

+ 3 - 1
frameworks/Java/micronaut/common/build.gradle

@@ -34,7 +34,9 @@ dependencies {
         transitive = false
     }
 
-    implementation("io.micronaut.views:micronaut-views-jte")
+    implementation("gg.jte:jte")
+
+    runtimeOnly("io.netty:netty-transport-native-io_uring::linux-x86_64")
 
     runtimeOnly("ch.qos.logback:logback-classic")
     runtimeOnly("org.yaml:snakeyaml")

+ 8 - 0
frameworks/Java/micronaut/common/src/main/java/benchmark/Application.java

@@ -1,10 +1,18 @@
 package benchmark;
 
 import io.micronaut.runtime.Micronaut;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class Application {
 
+    private static final Logger log = LoggerFactory.getLogger(Application.class);
+
     public static void main(String[] args) {
+        log.info("Runtime.maxMemory: {}",  Runtime.getRuntime().maxMemory());
+        log.info("Runtime.totalMemory: {}",  Runtime.getRuntime().totalMemory());
+        log.info("Runtime.availableProcessors: {}",  Runtime.getRuntime().availableProcessors());
+
         Micronaut.build(args).environments("common").classes(Application.class).start();
     }
 

+ 14 - 0
frameworks/Java/micronaut/common/src/main/resources/application-common.yml

@@ -6,6 +6,20 @@ micronaut:
     server-header: Micronaut
     date-header: false
     validate-url: false
+    netty:
+      parent:
+        event-loop-group: default
+        prefer-native-transport: true
+      worker:
+        event-loop-group: default
+        prefer-native-transport: true
+  http:
+    client:
+      event-loop-group: default
+  netty:
+    event-loops:
+      default:
+        prefer-native-transport: true
 
 netty:
   resource-leak-detector-level: DISABLED

+ 8 - 0
frameworks/Java/micronaut/common/src/main/resources/application-loom-on-netty.yml

@@ -0,0 +1,8 @@
+micronaut:
+  netty:
+    event-loops:
+      default:
+        loom-carrier: true
+    loom-carrier:
+      work-spill-threshold: 1000000
+      throughput-mode-threshold: 1000000

+ 3 - 0
frameworks/Java/micronaut/common/src/main/resources/application-loom.yml

@@ -0,0 +1,3 @@
+micronaut:
+  server:
+    thread-selection: blocking

+ 3 - 0
frameworks/Java/micronaut/common/src/main/resources/logback.xml

@@ -12,4 +12,7 @@
         <appender-ref ref="STDOUT" />
     </root>
 
+    <logger name="io.netty.util.internal.PlatformDependent" level="debug"/>
+    <logger name="io.netty.buffer.PooledByteBufAllocator" level="debug"/>
+    <logger name="io.netty.buffer.AdaptiveByteBufAllocator" level="debug"/>
 </configuration>

+ 13 - 0
frameworks/Java/micronaut/micronaut-loom-fjp.dockerfile

@@ -0,0 +1,13 @@
+FROM gradle:8.14.3-jdk21 as build
+COPY --chown=gradle:gradle . /home/gradle/src
+WORKDIR /home/gradle/src
+RUN gradle micronaut-vertx-pg-client:build -x test -x internalStartTestResourcesService --no-daemon
+
+FROM openjdk:24
+WORKDIR /micronaut
+COPY --from=build /home/gradle/src/micronaut-vertx-pg-client/build/libs/micronaut-vertx-pg-client-all.jar micronaut.jar
+COPY run_benchmark.sh run_benchmark.sh
+
+EXPOSE 8080
+ENV MN_ENV=loom
+ENTRYPOINT "./run_benchmark.sh"

+ 13 - 0
frameworks/Java/micronaut/micronaut-loom-on-netty.dockerfile

@@ -0,0 +1,13 @@
+FROM gradle:8.14.3-jdk21 as build
+COPY --chown=gradle:gradle . /home/gradle/src
+WORKDIR /home/gradle/src
+RUN gradle micronaut-vertx-pg-client:build -x test -x internalStartTestResourcesService --no-daemon
+
+FROM openjdk:24
+WORKDIR /micronaut
+COPY --from=build /home/gradle/src/micronaut-vertx-pg-client/build/libs/micronaut-vertx-pg-client-all.jar micronaut.jar
+COPY run_benchmark.sh run_benchmark.sh
+
+EXPOSE 8080
+ENV MN_ENV=loom,loom-on-netty
+ENTRYPOINT "./run_benchmark.sh"

+ 1 - 1
frameworks/Java/micronaut/micronaut-vertx-pg-client/build.gradle

@@ -9,6 +9,6 @@ micronaut {
 }
 
 dependencies {
-    implementation("io.vertx:vertx-pg-client")
+    implementation("io.vertx:vertx-pg-client:5.0.2")
     implementation('com.ongres.scram:client')
 }

+ 153 - 74
frameworks/Java/micronaut/micronaut-vertx-pg-client/src/main/java/benchmark/ConnectionHolder.java

@@ -1,21 +1,28 @@
 package benchmark;
 
 import io.micronaut.context.annotation.Property;
+import io.micronaut.core.util.SupplierUtil;
+import io.micronaut.http.netty.channel.loom.EventLoopVirtualThreadScheduler;
+import io.micronaut.http.netty.channel.loom.PrivateLoomSupport;
+import io.micronaut.scheduling.LoomSupport;
 import io.netty.bootstrap.Bootstrap;
 import io.netty.bootstrap.ServerBootstrap;
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelFactory;
 import io.netty.channel.EventLoop;
 import io.netty.channel.EventLoopGroup;
+import io.netty.channel.IoHandlerFactory;
 import io.netty.channel.ServerChannel;
 import io.netty.channel.socket.DatagramChannel;
 import io.netty.channel.socket.InternetProtocolFamily;
+import io.netty.util.Attribute;
+import io.netty.util.AttributeKey;
 import io.netty.util.concurrent.FastThreadLocal;
 import io.netty.util.internal.ThreadExecutorMap;
 import io.vertx.core.Future;
 import io.vertx.core.Vertx;
+import io.vertx.core.VertxBuilder;
 import io.vertx.core.datagram.DatagramSocketOptions;
-import io.vertx.core.impl.VertxBuilder;
 import io.vertx.core.net.ClientOptionsBase;
 import io.vertx.core.net.NetServerOptions;
 import io.vertx.core.spi.transport.Transport;
@@ -23,118 +30,190 @@ import io.vertx.pgclient.PgConnectOptions;
 import io.vertx.pgclient.PgConnection;
 import io.vertx.sqlclient.PoolOptions;
 import jakarta.inject.Singleton;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.net.SocketAddress;
+import java.util.List;
 import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.function.Supplier;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
 
 @Singleton
 public class ConnectionHolder {
+    private static final Logger log = LoggerFactory.getLogger(ConnectionHolder.class);
     private final FastThreadLocal<Future<PgConnection>> conn = new FastThreadLocal<>();
+    private final AttributeKey<Future<PgConnection>> loomConn = AttributeKey.valueOf("vertx-pg-connection");
 
     @Property(name = "datasources.default.url") String url;
     @Property(name = "datasources.default.username") String user;
     @Property(name = "datasources.default.password") String password;
     @Property(name = "datasources.default.maximum-pool-size") int maxPoolSize;
 
-    public Future<PgConnection> get() {
-        Future<PgConnection> c = conn.get();
-        if (c == null) {
-
-            PgConnectOptions connectOptions = PgConnectOptions.fromUri(url.substring(5))
-                    .setUser(user)
-                    .setPassword(password)
-                    .setCachePreparedStatements(true)
-                    .setTcpNoDelay(true)
-                    .setTcpQuickAck(true)
-                    .setPipeliningLimit(1024);
-            PoolOptions poolOptions = new PoolOptions();
-            poolOptions.setMaxSize(maxPoolSize);
-
-            VertxBuilder builder = new VertxBuilder()
-                    .init();
+    private final Supplier<List<Future<PgConnection>>> pool = SupplierUtil.memoized(() -> {
+        Vertx vertx = Vertx.builder()
+                .withTransport(vertxTransport())
+                .build();
+        return IntStream.range(0, maxPoolSize)
+                .mapToObj(i -> PgConnection.connect(vertx, connectOptions()))
+                .toList();
+    });
 
-            EventLoop loop = (EventLoop) ThreadExecutorMap.currentExecutor();
-
-            Vertx vertx = builder
-                    .findTransport(new ExistingTransport(builder.findTransport(), loop))
-                    .vertx();
+    public Future<PgConnection> get() {
+        if (PrivateLoomSupport.isSupported() &&
+                LoomSupport.isVirtual(Thread.currentThread()) &&
+                PrivateLoomSupport.getScheduler(Thread.currentThread()) instanceof EventLoopVirtualThreadScheduler sch) {
+
+            Attribute<Future<PgConnection>> attr = sch.attributeMap().attr(loomConn);
+            Future<PgConnection> c = attr.get();
+            if (c == null) {
+                c = connect((EventLoop) sch.eventLoop());
+                attr.set(c);
+            }
+            return c;
+        } else if (ThreadExecutorMap.currentExecutor() instanceof EventLoop el) {
 
-            c = PgConnection.connect(vertx, connectOptions);
-            conn.set(c);
+            Future<PgConnection> c = conn.get();
+            if (c == null) {
+                c = connect(el);
+                conn.set(c);
+            }
+            return c;
+        } else {
+            List<Future<PgConnection>> l = pool.get();
+            return l.get(ThreadLocalRandom.current().nextInt(l.size()));
         }
-        return c;
     }
 
-    private record ExistingTransport(Transport transport, EventLoop loop) implements Transport {
-
-        @Override
-            public boolean supportsDomainSockets() {
-                return transport.supportsDomainSockets();
-            }
+    private Future<PgConnection> connect(EventLoop loop) {
+        VertxBuilder builder = Vertx.builder();
 
+        io.vertx.core.transport.Transport original = vertxTransport();
+        ExistingTransport mapped = new ExistingTransport(original.implementation(), loop);
+        io.vertx.core.transport.Transport tr = new io.vertx.core.transport.Transport() {
             @Override
-            public boolean supportFileRegion() {
-                return transport.supportFileRegion();
+            public String name() {
+                return "ExistingTransport";
             }
 
             @Override
-            public boolean isAvailable() {
-                return transport.isAvailable();
+            public boolean available() {
+                return true;
             }
 
             @Override
             public Throwable unavailabilityCause() {
-                return transport.unavailabilityCause();
+                return null;
             }
 
             @Override
-            public SocketAddress convert(io.vertx.core.net.SocketAddress address) {
-                return transport.convert(address);
+            public Transport implementation() {
+                return mapped;
             }
+        };
+        Vertx vertx = builder
+                .withTransport(tr)
+                .build();
+        return PgConnection.connect(vertx, connectOptions());
+    }
 
-            @Override
-            public io.vertx.core.net.SocketAddress convert(SocketAddress address) {
-                return transport.convert(address);
-            }
+    private static io.vertx.core.transport.Transport vertxTransport() {
+        return Stream.of(io.vertx.core.transport.Transport.IO_URING, io.vertx.core.transport.Transport.NIO)
+                .filter(t -> t != null && t.available())
+                .findFirst().orElseThrow();
+    }
 
-            @Override
-            public EventLoopGroup eventLoopGroup(int type, int nThreads, ThreadFactory threadFactory, int ignoredIoRatio) {
-                return loop;
-            }
+    private PoolOptions poolOptions() {
+        PoolOptions poolOptions = new PoolOptions();
+        poolOptions.setMaxSize(maxPoolSize);
+        return poolOptions;
+    }
 
-            @Override
-            public DatagramChannel datagramChannel() {
-                return transport.datagramChannel();
-            }
+    private PgConnectOptions connectOptions() {
+        return PgConnectOptions.fromUri(url.substring(5))
+                .setUser(user)
+                .setPassword(password)
+                .setCachePreparedStatements(true)
+                .setPipeliningLimit(1024);
+    }
 
-            @Override
-            public DatagramChannel datagramChannel(InternetProtocolFamily family) {
-                return transport.datagramChannel(family);
-            }
+    private record ExistingTransport(Transport transport, EventLoop loop) implements Transport {
 
-            @Override
-            public ChannelFactory<? extends Channel> channelFactory(boolean domainSocket) {
-                return transport.channelFactory(domainSocket);
-            }
+        @Override
+        public boolean supportsDomainSockets() {
+            return transport.supportsDomainSockets();
+        }
 
-            @Override
-            public ChannelFactory<? extends ServerChannel> serverChannelFactory(boolean domainSocket) {
-                return transport.serverChannelFactory(domainSocket);
-            }
+        @Override
+        public boolean supportFileRegion() {
+            return transport.supportFileRegion();
+        }
 
-            @Override
-            public void configure(DatagramChannel channel, DatagramSocketOptions options) {
-                transport.configure(channel, options);
-            }
+        @Override
+        public boolean isAvailable() {
+            return transport.isAvailable();
+        }
 
-            @Override
-            public void configure(ClientOptionsBase options, boolean domainSocket, Bootstrap bootstrap) {
-                transport.configure(options, domainSocket, bootstrap);
-            }
+        @Override
+        public Throwable unavailabilityCause() {
+            return transport.unavailabilityCause();
+        }
 
-            @Override
-            public void configure(NetServerOptions options, boolean domainSocket, ServerBootstrap bootstrap) {
-                transport.configure(options, domainSocket, bootstrap);
-            }
+        @Override
+        public SocketAddress convert(io.vertx.core.net.SocketAddress address) {
+            return transport.convert(address);
         }
+
+        @Override
+        public io.vertx.core.net.SocketAddress convert(SocketAddress address) {
+            return transport.convert(address);
+        }
+
+        @Override
+        public IoHandlerFactory ioHandlerFactory() {
+            return transport.ioHandlerFactory();
+        }
+
+        @Override
+        public EventLoopGroup eventLoopGroup(int type, int nThreads, ThreadFactory threadFactory, int ignoredIoRatio) {
+            return loop;
+        }
+
+        @Override
+        public DatagramChannel datagramChannel() {
+            return transport.datagramChannel();
+        }
+
+        @Override
+        public DatagramChannel datagramChannel(InternetProtocolFamily family) {
+            return transport.datagramChannel(family);
+        }
+
+        @Override
+        public ChannelFactory<? extends Channel> channelFactory(boolean domainSocket) {
+            return transport.channelFactory(domainSocket);
+        }
+
+        @Override
+        public ChannelFactory<? extends ServerChannel> serverChannelFactory(boolean domainSocket) {
+            return transport.serverChannelFactory(domainSocket);
+        }
+
+        @Override
+        public void configure(DatagramChannel channel, DatagramSocketOptions options) {
+            transport.configure(channel, options);
+        }
+
+        @Override
+        public void configure(ClientOptionsBase options, int connectTimeout, boolean domainSocket, Bootstrap bootstrap) {
+            transport.configure(options, connectTimeout, domainSocket, bootstrap);
+        }
+
+        @Override
+        public void configure(NetServerOptions options, boolean domainSocket, ServerBootstrap bootstrap) {
+            transport.configure(options, domainSocket, bootstrap);
+        }
+    }
 }

+ 11 - 4
frameworks/Java/micronaut/run_benchmark.sh

@@ -1,16 +1,23 @@
 #!/bin/bash
 
-JAVA_OPTIONS="-server \
+if [ -z "$MN_ENV" ]; then
+  MN_ENV=benchmark
+else
+  MN_ENV=benchmark,$MN_ENV
+fi
+
+JAVA_OPTIONS="$JAVA_OPTIONS -server \
   -XX:+UseParallelGC \
   -XX:+UseNUMA \
-  -XX:-StackTraceInThrowable \
+  -Djdk.trackAllThreads=false \
   -Dio.netty.buffer.checkBounds=false \
   -Dio.netty.buffer.checkAccessible=false \
   -Dvertx.disableMetrics=true \
   -Dvertx.threadChecks=false \
   -Dvertx.disableContextTimings=true  \
   -Dvertx.disableTCCL=true  \
-  -Dmicronaut.environments=benchmark
+  -Dmicronaut.environments=$MN_ENV \
+  --add-opens=java.base/java.lang=ALL-UNNAMED \
   $@"
 
-java $JAVA_OPTIONS -jar micronaut.jar
+exec java $JAVA_OPTIONS -jar micronaut.jar