Files
MoonBridge/testing/benchmarks/results/2026-10-22-relay-batch-completion.patch
T
layue13andClaude Opus 5.5 fc1345d010 perf: 去掉冷路径上的重复开销,并记录未采用的中继批量完成实验
- Message.validateChannel:通道名正则只编译一次(原来每条消息 String.matches 都重新编译),
  长度检查不再分配字节数组
- 后端 OwnedBukkitSessionService:反射查找按接收者类用 ClassValue 缓存(含未找到),
  玩家每次加入不再重复 getMethod
- testing/benchmarks/results/2026-10-22-relay-batch-completion:中继只给每批最后一次写入
  挂完成回调。I/O 线程分配降 22%、中继 lambda 降 97%,但每帧 CPU 只低 2.4% 且范围重叠,
  未达到事先设定的 10% 规则,未采用;补丁随记录保留
- PluginHostTest.placementFailuresOnlyFailTheirOwnRequest:40 ms 超时在负载下可能早于插件
  处理器运行而偶发失败,改为 500 ms 并断言首个请求确实到达插件

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:34:36 +08:00

73 lines
3.9 KiB
Diff

diff --git a/proxy/core/src/main/java/dev/moonbridge/core/session/play/RawRelay.java b/proxy/core/src/main/java/dev/moonbridge/core/session/play/RawRelay.java
index e1832e1..a6288f2 100644
--- a/proxy/core/src/main/java/dev/moonbridge/core/session/play/RawRelay.java
+++ b/proxy/core/src/main/java/dev/moonbridge/core/session/play/RawRelay.java
@@ -3,6 +3,7 @@ package dev.moonbridge.core.session.play;
import dev.moonbridge.core.protocol.ProtocolProfile;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
@@ -173,6 +174,9 @@ public final class RawRelay {
private volatile boolean closingAfterWrites;
private boolean readsEnabled;
private boolean flushPending;
+ /** The last frame written since the previous flush, and how many frames that batch holds. */
+ private ChannelFuture batchTail;
+ private int batchWrites;
private ScheduledFuture<?> closeDeadline;
private CompletableFuture<Void> pauseComplete;
private CompletableFuture<Void> resumeComplete;
@@ -340,17 +344,10 @@ public final class RawRelay {
try {
Consumer<ByteBuf> activeObserver = observer;
if (activeObserver != null) activeObserver.accept(((ByteBuf) message).duplicate());
- peer.write(message).addListener((ChannelFutureListener) future ->
- ctx.executor().execute(() -> {
- writesInFlight--;
- if (paused && writesInFlight == 0 && pauseComplete != null) {
- if (future.isSuccess()) pauseComplete.complete(null);
- else pauseComplete.completeExceptionally(future.cause());
- pauseComplete = null;
- }
- if (!future.isSuccess() || (closingAfterWrites && writesInFlight == 0)) closePair(ctx);
- else if (!closingAfterWrites) requestRead();
- }));
+ // Only the batch's last write gets a listener, attached when the batch is flushed: one channel
+ // completes writes in order and fails every later write once one fails, so its outcome covers all.
+ batchTail = peer.write(message);
+ batchWrites++;
scheduleFlush(ctx);
} catch (RuntimeException failure) {
writesInFlight--;
@@ -379,9 +376,27 @@ public final class RawRelay {
private void flushPeer() {
if (!flushPending) return;
flushPending = false;
+ ChannelFuture tail = batchTail;
+ int writes = batchWrites;
+ batchTail = null;
+ batchWrites = 0;
+ if (tail != null) tail.addListener((ChannelFutureListener) future -> batchCompleted(future, writes));
peer.flush();
}
+ private void batchCompleted(ChannelFuture future, int writes) {
+ context.executor().execute(() -> {
+ writesInFlight -= writes;
+ if (paused && writesInFlight == 0 && pauseComplete != null) {
+ if (future.isSuccess()) pauseComplete.complete(null);
+ else pauseComplete.completeExceptionally(future.cause());
+ pauseComplete = null;
+ }
+ if (!future.isSuccess() || (closingAfterWrites && writesInFlight == 0)) closePair(context);
+ else if (!closingAfterWrites) requestRead();
+ });
+ }
+
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) {
if (ctx.channel().isWritable()) opposite.requestRead();