优化服务端RPC处理性能:预编译委托、减少反射、减少对象分配 Co-authored-by: nnhy <506367+nnhy@users.noreply.github.com>copilot-swe-agent[bot] authored at 2026-02-26 00:52:30 Stone committed at 2026-02-26 03:02:00
diff --git a/Benchmark/Program.cs b/Benchmark/Program.cs
index b986d99..d256b70 100644
--- a/Benchmark/Program.cs
+++ b/Benchmark/Program.cs
@@ -1,3 +1,22 @@
using BenchmarkDotNet.Running;
+using NewLife.Remoting.Benchmarks;
+
+// 支持服务端吞吐量压力测试模式
+if (args.Length > 0 && args[0].Equals("throughput", StringComparison.OrdinalIgnoreCase))
+{
+ var clientCount = args.Length > 1 ? Int32.Parse(args[1]) : 100;
+ var duration = args.Length > 2 ? Int32.Parse(args[2]) : 10;
+ ServerThroughputTest.RunNetworkTest(clientCount, duration);
+ return;
+}
+
+// 服务端纯处理能力测试(绕过TCP网络栈)
+if (args.Length > 0 && args[0].Equals("direct", StringComparison.OrdinalIgnoreCase))
+{
+ var threadCount = args.Length > 1 ? Int32.Parse(args[1]) : 0;
+ var duration = args.Length > 2 ? Int32.Parse(args[2]) : 10;
+ ServerThroughputTest.RunDirectTest(threadCount, duration);
+ return;
+}
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
diff --git a/Benchmark/ServerThroughputTest.cs b/Benchmark/ServerThroughputTest.cs
new file mode 100644
index 0000000..55383a2
--- /dev/null
+++ b/Benchmark/ServerThroughputTest.cs
@@ -0,0 +1,338 @@
+using System.Diagnostics;
+using NewLife.Data;
+using NewLife.Log;
+using NewLife.Messaging;
+using NewLife.Remoting;
+
+#pragma warning disable CS0618 // Packet obsolete
+
+namespace NewLife.Remoting.Benchmarks;
+
+/// <summary>服务端吞吐量压力测试。模拟多客户端并发对ApiServer施加压力,测量服务端处理能力</summary>
+public class ServerThroughputTest
+{
+ /// <summary>运行服务端吞吐量测试(通过TCP网络)</summary>
+ /// <param name="clientCount">客户端连接数</param>
+ /// <param name="durationSeconds">测试持续时间(秒)</param>
+ /// <param name="warmupSeconds">预热时间(秒)</param>
+ public static void RunNetworkTest(Int32 clientCount = 100, Int32 durationSeconds = 10, Int32 warmupSeconds = 3)
+ {
+ Console.WriteLine("========================================");
+ Console.WriteLine(" 网络吞吐量测试(TCP端到端)");
+ Console.WriteLine("========================================");
+ Console.WriteLine();
+ Console.WriteLine($"客户端连接数:{clientCount}");
+ Console.WriteLine($"测试持续时间:{durationSeconds} 秒");
+ Console.WriteLine($"预热时间:{warmupSeconds} 秒");
+ Console.WriteLine();
+
+ // 创建服务端
+ var server = new ApiServer(0)
+ {
+ Log = Logger.Null,
+ EncoderLog = Logger.Null,
+ StatPeriod = 0,
+ };
+ server.Register<BenchController>();
+ server.Start();
+
+ var port = server.Port;
+ Console.WriteLine($"服务端启动完成,端口:{port}");
+
+ // 创建客户端连接
+ Console.Write($"正在创建 {clientCount} 个客户端连接...");
+ var clients = new ApiClient[clientCount];
+ for (var i = 0; i < clientCount; i++)
+ {
+ clients[i] = new ApiClient($"tcp://127.0.0.1:{port}") { Log = Logger.Null };
+ clients[i].Invoke<String[]>("Api/All");
+ }
+ Console.WriteLine(" 完成");
+ Console.WriteLine();
+
+ RunScenario("NoArg_ReturnInt32", clients, clientCount, durationSeconds, warmupSeconds,
+ (client) => client.InvokeAsync<Int32>("Bench/NoArg"));
+
+ RunScenario("EchoPacket_16B", clients, clientCount, durationSeconds, warmupSeconds,
+ (client) => client.InvokeAsync<Packet>("Bench/EchoPacket", new Byte[16]));
+
+ foreach (var client in clients) client?.TryDispose();
+ server.TryDispose();
+ }
+
+ /// <summary>运行服务端纯处理能力测试(绕过TCP网络栈)</summary>
+ /// <param name="threadCount">并发线程数</param>
+ /// <param name="durationSeconds">测试持续时间(秒)</param>
+ /// <param name="warmupSeconds">预热时间(秒)</param>
+ public static void RunDirectTest(Int32 threadCount = 0, Int32 durationSeconds = 10, Int32 warmupSeconds = 3)
+ {
+ if (threadCount <= 0) threadCount = Environment.ProcessorCount;
+
+ Console.WriteLine("========================================");
+ Console.WriteLine(" 服务端纯处理能力测试(绕过TCP)");
+ Console.WriteLine("========================================");
+ Console.WriteLine();
+ Console.WriteLine($"并发线程数:{threadCount}");
+ Console.WriteLine($"CPU逻辑核心数:{Environment.ProcessorCount}");
+ Console.WriteLine($"测试持续时间:{durationSeconds} 秒");
+ Console.WriteLine($"预热时间:{warmupSeconds} 秒");
+ Console.WriteLine();
+
+ // 创建服务端(不需要监听端口)
+ var server = new ApiServer(0)
+ {
+ Log = Logger.Null,
+ EncoderLog = Logger.Null,
+ StatPeriod = 0,
+ };
+ server.Register<BenchController>();
+ server.Start();
+
+ var encoder = server.Encoder;
+
+ // 预创建请求消息模板(NoArg场景)
+ Console.Write("预创建请求消息模板...");
+ var noArgTemplate = CreateRequestPayload(encoder, "Bench/NoArg", null);
+ var echoPacketTemplate = CreateRequestPayload(encoder, "Bench/EchoPacket", new Byte[16]);
+ Console.WriteLine(" 完成");
+ Console.WriteLine();
+
+ // 创建模拟会话
+ var sessions = new MockApiSession[threadCount];
+ for (var i = 0; i < threadCount; i++)
+ sessions[i] = new MockApiSession(server);
+
+ // 测试NoArg场景
+ RunDirectScenario("NoArg_ReturnInt32(纯处理)", server, sessions, noArgTemplate, threadCount, durationSeconds, warmupSeconds);
+
+ // 测试EchoPacket场景
+ RunDirectScenario("EchoPacket_16B(纯处理)", server, sessions, echoPacketTemplate, threadCount, durationSeconds, warmupSeconds);
+
+ server.TryDispose();
+
+ Console.WriteLine();
+ Console.WriteLine("========================================");
+ Console.WriteLine(" 所有测试完成");
+ Console.WriteLine("========================================");
+ }
+
+ /// <summary>创建请求消息的Payload模板</summary>
+ private static Byte[] CreateRequestPayload(IEncoder encoder, String action, Object? args)
+ {
+ using var msg = encoder.CreateRequest(action, args);
+ return msg.Payload!.ToArray();
+ }
+
+ /// <summary>运行直接处理场景测试</summary>
+ private static void RunDirectScenario(String name, ApiServer server, MockApiSession[] sessions, Byte[] requestTemplate, Int32 threadCount, Int32 durationSeconds, Int32 warmupSeconds)
+ {
+ Console.WriteLine($"--- 场景:{name} ---");
+
+ var totalRequests = 0L;
+ var errors = 0L;
+ var running = true;
+
+ // 预热
+ Console.Write($" 预热 {warmupSeconds} 秒...");
+ var warmupCts = new CancellationTokenSource();
+ var warmupTasks = new Thread[threadCount];
+ for (var i = 0; i < threadCount; i++)
+ {
+ var session = sessions[i];
+ warmupTasks[i] = new Thread(() =>
+ {
+ while (!warmupCts.Token.IsCancellationRequested)
+ {
+ try
+ {
+ var msg = CreateMessage(requestTemplate);
+ using var rs = server.Process(session, msg, session);
+ rs?.Payload?.TryDispose();
+ }
+ catch { }
+ }
+ });
+ warmupTasks[i].IsBackground = true;
+ warmupTasks[i].Start();
+ }
+ Thread.Sleep(warmupSeconds * 1000);
+ warmupCts.Cancel();
+ foreach (var t in warmupTasks) t.Join(3000);
+ Console.WriteLine(" 完成");
+
+ // GC 基线
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+ var gen0Before = GC.CollectionCount(0);
+ var gen1Before = GC.CollectionCount(1);
+ var gen2Before = GC.CollectionCount(2);
+ var memBefore = GC.GetTotalMemory(false);
+
+ // 正式测试
+ var sw = Stopwatch.StartNew();
+ var threads = new Thread[threadCount];
+ for (var i = 0; i < threadCount; i++)
+ {
+ var session = sessions[i];
+ threads[i] = new Thread(() =>
+ {
+ var localCount = 0L;
+ var localErrors = 0L;
+ while (running)
+ {
+ try
+ {
+ var msg = CreateMessage(requestTemplate);
+ using var rs = server.Process(session, msg, session);
+ rs?.Payload?.TryDispose();
+ localCount++;
+ }
+ catch
+ {
+ localErrors++;
+ }
+ }
+ Interlocked.Add(ref totalRequests, localCount);
+ Interlocked.Add(ref errors, localErrors);
+ });
+ threads[i].IsBackground = true;
+ threads[i].Start();
+ }
+
+ Thread.Sleep(durationSeconds * 1000);
+ running = false;
+ foreach (var t in threads) t.Join(3000);
+ sw.Stop();
+
+ var gen0After = GC.CollectionCount(0);
+ var gen1After = GC.CollectionCount(1);
+ var gen2After = GC.CollectionCount(2);
+ var memAfter = GC.GetTotalMemory(false);
+
+ var elapsed = sw.Elapsed.TotalSeconds;
+ var rps = totalRequests / elapsed;
+
+ Console.WriteLine($" 总请求数:{totalRequests:N0}");
+ Console.WriteLine($" 错误数:{errors:N0}");
+ Console.WriteLine($" 耗时:{elapsed:F2} 秒");
+ Console.WriteLine($" 吞吐量:{rps:N0} RPC/s");
+ Console.WriteLine($" 每请求分配:{(memAfter - memBefore) * 1.0 / totalRequests:F0} B/req(估算)");
+ Console.WriteLine($" GC: Gen0={gen0After - gen0Before}, Gen1={gen1After - gen1Before}, Gen2={gen2After - gen2Before}");
+ Console.WriteLine();
+ }
+
+ /// <summary>从模板创建IMessage</summary>
+ private static DefaultMessage CreateMessage(Byte[] template)
+ {
+ var payload = new ArrayPacket(template);
+ return new DefaultMessage { Payload = payload };
+ }
+
+ private static void RunScenario(String name, ApiClient[] clients, Int32 clientCount, Int32 durationSeconds, Int32 warmupSeconds, Func<ApiClient, Task> action)
+ {
+ Console.WriteLine($"--- 场景:{name} ---");
+
+ var totalRequests = 0L;
+ var errors = 0L;
+ var running = true;
+
+ // 预热
+ Console.Write($" 预热 {warmupSeconds} 秒...");
+ var warmupCts = new CancellationTokenSource();
+ var warmupTasks = new Task[clientCount];
+ for (var i = 0; i < clientCount; i++)
+ {
+ var client = clients[i];
+ warmupTasks[i] = Task.Run(async () =>
+ {
+ while (!warmupCts.Token.IsCancellationRequested)
+ {
+ try { await action(client); } catch { }
+ }
+ });
+ }
+ Thread.Sleep(warmupSeconds * 1000);
+ warmupCts.Cancel();
+ try { Task.WaitAll(warmupTasks, 5000); } catch { }
+ Console.WriteLine(" 完成");
+
+ // GC 基线
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+ var gen0Before = GC.CollectionCount(0);
+ var gen1Before = GC.CollectionCount(1);
+ var gen2Before = GC.CollectionCount(2);
+ var memBefore = GC.GetTotalMemory(false);
+
+ var sw = Stopwatch.StartNew();
+ var tasks = new Task[clientCount];
+ for (var i = 0; i < clientCount; i++)
+ {
+ var client = clients[i];
+ tasks[i] = Task.Run(async () =>
+ {
+ while (running)
+ {
+ try
+ {
+ await action(client);
+ Interlocked.Increment(ref totalRequests);
+ }
+ catch
+ {
+ Interlocked.Increment(ref errors);
+ }
+ }
+ });
+ }
+
+ Thread.Sleep(durationSeconds * 1000);
+ running = false;
+ try { Task.WaitAll(tasks, 5000); } catch { }
+ sw.Stop();
+
+ var gen0After = GC.CollectionCount(0);
+ var gen1After = GC.CollectionCount(1);
+ var gen2After = GC.CollectionCount(2);
+ var memAfter = GC.GetTotalMemory(false);
+
+ var elapsed = sw.Elapsed.TotalSeconds;
+ var rps = totalRequests / elapsed;
+ var avgLatencyUs = elapsed * 1_000_000 * clientCount / totalRequests;
+
+ Console.WriteLine($" 总请求数:{totalRequests:N0}");
+ Console.WriteLine($" 错误数:{errors:N0}");
+ Console.WriteLine($" 耗时:{elapsed:F2} 秒");
+ Console.WriteLine($" 吞吐量:{rps:N0} RPC/s");
+ Console.WriteLine($" 平均延迟:{avgLatencyUs:F1} μs/请求");
+ Console.WriteLine($" GC: Gen0={gen0After - gen0Before}, Gen1={gen1After - gen1Before}, Gen2={gen2After - gen2Before}");
+ Console.WriteLine();
+ }
+}
+
+/// <summary>模拟Api会话,用于绕过TCP直接测试服务端处理能力</summary>
+class MockApiSession : IApiSession, IServiceProvider
+{
+ private readonly ApiServer _host;
+ private IDictionary<String, Object?>? _items;
+
+ public MockApiSession(ApiServer host) => _host = host;
+
+ public IApiHost Host => _host;
+ public DateTime LastActive => DateTime.Now;
+ public IApiSession[] AllSessions => [this];
+ public String? Token { get; set; }
+ public IDictionary<String, Object?> Items => _items ??= new Dictionary<String, Object?>();
+
+ public Object? this[String key]
+ {
+ get => _items != null && _items.TryGetValue(key, out var v) ? v : null;
+ set => Items[key] = value;
+ }
+
+ public Int32 InvokeOneWay(String action, Object? args = null, Byte flag = 0) => 0;
+
+ public Object? GetService(Type serviceType) => (_host as IServiceProvider).GetService(serviceType);
+}
diff --git a/NewLife.Remoting/ApiAction.cs b/NewLife.Remoting/ApiAction.cs
index 61da6e0..3e3f753 100644
--- a/NewLife.Remoting/ApiAction.cs
+++ b/NewLife.Remoting/ApiAction.cs
@@ -1,4 +1,5 @@
-using System.Reflection;
+using System.Linq.Expressions;
+using System.Reflection;
using System.Threading.Tasks;
using NewLife.Data;
using NewLife.Log;
@@ -35,6 +36,12 @@ public class ApiAction : IExtend
/// <summary>是否Accessor返回</summary>
public Boolean IsAccessorReturn { get; }
+ /// <summary>是否无参数方法</summary>
+ public Boolean IsNoParameter { get; }
+
+ /// <summary>预编译的快速调用委托</summary>
+ public Func<Object, Object?[], Object?>? FastInvoker { get; private set; }
+
/// <summary>处理统计</summary>
public ICounter StatProcess { get; set; } = new PerfCounter();
@@ -67,12 +74,65 @@ public class ApiAction : IExtend
if (ps[0].ParameterType.As<IAccessor>()) IsAccessorParameter = true;
}
+ IsNoParameter = ps == null || ps.Length == 0;
+
var returnType = method.ReturnType;
if (returnType.As(typeof(Task<>)))
returnType = returnType.GetGenericArguments()[0];
if (returnType.As<IPacket>()) IsPacketReturn = true;
if (returnType.As<IAccessor>()) IsAccessorReturn = true;
+
+ // 预编译快速调用委托
+ FastInvoker = CompileInvoker(method);
+ }
+
+ /// <summary>使用表达式树编译快速调用委托,避免每次调用走反射</summary>
+ /// <param name="method">方法信息</param>
+ /// <returns>编译后的委托,参数为(instance, args[]),返回Object</returns>
+ private static Func<Object, Object?[], Object?>? CompileInvoker(MethodInfo method)
+ {
+ try
+ {
+ var instanceParam = Expression.Parameter(typeof(Object), "instance");
+ var argsParam = Expression.Parameter(typeof(Object?[]), "args");
+
+ // 转换实例对象类型
+ var instance = method.IsStatic ? null : Expression.Convert(instanceParam, method.DeclaringType!);
+
+ // 构造参数列表
+ var parameters = method.GetParameters();
+ var argExpressions = new Expression[parameters.Length];
+ for (var i = 0; i < parameters.Length; i++)
+ {
+ var index = Expression.ArrayIndex(argsParam, Expression.Constant(i));
+ argExpressions[i] = Expression.Convert(index, parameters[i].ParameterType);
+ }
+
+ // 调用方法
+ var call = method.IsStatic
+ ? Expression.Call(method, argExpressions)
+ : Expression.Call(instance!, method, argExpressions);
+
+ // 处理返回值
+ Expression body;
+ if (method.ReturnType == typeof(void))
+ {
+ body = Expression.Block(call, Expression.Constant(null, typeof(Object)));
+ }
+ else
+ {
+ body = Expression.Convert(call, typeof(Object));
+ }
+
+ var lambda = Expression.Lambda<Func<Object, Object?[], Object?>>(body, instanceParam, argsParam);
+ return lambda.Compile();
+ }
+ catch
+ {
+ // 编译失败时回退到反射调用
+ return null;
+ }
}
/// <summary>获取名称</summary>
diff --git a/NewLife.Remoting/ApiServer.cs b/NewLife.Remoting/ApiServer.cs
index 64489ef..c2c51d9 100644
--- a/NewLife.Remoting/ApiServer.cs
+++ b/NewLife.Remoting/ApiServer.cs
@@ -263,7 +263,7 @@ public class ApiServer : ApiHost, IServer, IServiceProvider
if (request == null || request.Action.IsNullOrEmpty()) return null;
// Action动作名必须是Ascii字符,跳过扫描乱码
- if (!request.Action.All(e => e < 127u)) return null;
+ if (!IsAscii(request.Action)) return null;
// 根据动作名,开始跟踪
using var span = Tracer?.NewSpan("rps:" + request.Action, request.Data);
@@ -403,5 +403,14 @@ public class ApiServer : ApiHost, IServer, IServiceProvider
return ServiceProvider?.GetService(serviceType)!;
}
+
+ private static Boolean IsAscii(String str)
+ {
+ for (var i = 0; i < str.Length; i++)
+ {
+ if (str[i] >= 127) return false;
+ }
+ return true;
+ }
#endregion
}
\ No newline at end of file
diff --git a/NewLife.Remoting/IApiHandler.cs b/NewLife.Remoting/IApiHandler.cs
index 4417785..76c19b5 100644
--- a/NewLife.Remoting/IApiHandler.cs
+++ b/NewLife.Remoting/IApiHandler.cs
@@ -59,9 +59,9 @@ public class ApiHandler : IApiHandler
?? throw new ApiException(ApiCode.Forbidden, $"无法创建名为[{api.Name}]的服务!");
if (controller is IApi capi) capi.Session = session;
if (session is INetSession ss)
- api.LastSession = ss.Remote + "";
+ api.LastSession = ss.Remote?.ToString();
else
- api.LastSession = session + "";
+ api.LastSession = session?.ToString();
var st = api.StatProcess;
var sw = st.StartCount();
@@ -108,6 +108,29 @@ public class ApiHandler : IApiHandler
{
rs = controller.Invoke(api.Method, args);
}
+ else if (api.FastInvoker != null)
+ {
+ // 使用预编译委托快速调用,避免反射开销
+ var ps = ctx.ActionParameters;
+ if (api.IsNoParameter || ps == null || ps.Count == 0)
+ {
+ rs = api.FastInvoker(controller, _emptyArgs);
+ }
+ else
+ {
+ var pis = api.Method.GetParameters();
+ var pv = new Object?[pis.Length];
+ for (var i = 0; i < pis.Length; i++)
+ {
+ var pn = pis[i].Name;
+ if (pn != null) ps.TryGetValue(pn, out pv[i]);
+ // 值类型参数不能为null,否则表达式树Unbox会抛NullReferenceException
+ if (pv[i] == null && pis[i].ParameterType.IsValueType)
+ pv[i] = Activator.CreateInstance(pis[i].ParameterType);
+ }
+ rs = api.FastInvoker(controller, pv);
+ }
+ }
else
{
rs = controller.InvokeWithParams(api.Method, ctx.ActionParameters as IDictionary);
@@ -152,6 +175,8 @@ public class ApiHandler : IApiHandler
return rs;
}
+ private static readonly System.Collections.Concurrent.ConcurrentDictionary<Type, PropertyInfo?> _taskResultCache = new();
+
private static Object? GetTaskResult(Task task)
{
task.GetAwaiter().GetResult();
@@ -159,7 +184,8 @@ public class ApiHandler : IApiHandler
var taskType = task.GetType();
if (!taskType.IsGenericType) return null;
- var resultProperty = taskType.GetProperty("Result", BindingFlags.Public | BindingFlags.Instance);
+ var resultProperty = _taskResultCache.GetOrAdd(taskType,
+ t => t.GetProperty("Result", BindingFlags.Public | BindingFlags.Instance));
return resultProperty?.GetValue(task);
}
@@ -190,6 +216,9 @@ public class ApiHandler : IApiHandler
// 如果服务只有一个二进制参数,则走快速通道
if (api.IsPacketParameter) return ctx;
+ // 无参数方法,跳过参数解码和绑定
+ if (api.IsNoParameter) return ctx;
+
// IAccessor参数,直接进行二进制反序列化
if (api.IsAccessorParameter)
{
@@ -247,6 +276,9 @@ public class ApiHandler : IApiHandler
return ctx;
}
+ private static readonly IDictionary<String, Object?> _emptyParameters = new Dictionary<String, Object?>();
+ private static readonly Object?[] _emptyArgs = new Object?[0];
+
/// <summary>获取接口方法对应的参数值集合</summary>
/// <param name="method">接口方法</param>
/// <param name="dic">请求参数</param>
@@ -256,11 +288,11 @@ public class ApiHandler : IApiHandler
/// <returns></returns>
protected virtual IDictionary<String, Object?> GetParameterValues(MethodInfo method, IDictionary<String, Object?> dic, Object? raw, Object? args, IEncoder encoder)
{
- var ps = new Dictionary<String, Object?>();
-
// 该方法没有参数,无视外部传入参数
var pis = method.GetParameters();
- if (pis == null || pis.Length <= 0) return ps;
+ if (pis == null || pis.Length <= 0) return _emptyParameters;
+
+ var ps = new Dictionary<String, Object?>();
if (pis.Length == 1 && dic.Count == 0)
{