You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何我的基准测试中gRPC性能不及REST?求排查原因

疑问:gRPC基准测试性能不如REST,是配置问题还是结果合理?

近期开始使用gRPC,一直听闻它比REST性能更优,因此搭建了基准测试项目验证性能差距,但多次测试后均发现REST性能略优于gRPC。由于在基准测试和gRPC方面经验不足,怀疑是测试配置存在问题,想知道:测试配置哪里出错了?还是该结果本身合理?

本次针对BenchmarkWithSamePayload测试用例。


最新测试结果

// * Summary *

BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3155/23H2/2023Update/SunValley3)
AMD Ryzen 5 5600X, 1 CPU, 12 logical and 6 physical cores
.NET SDK 8.0.201
  [Host]     : .NET 8.0.2 (8.0.224.6711), X64 RyuJIT AVX2
  DefaultJob : .NET 8.0.2 (8.0.224.6711), X64 RyuJIT AVX2


| Method        | Mean     | Error   | StdDev  |
|-------------- |---------:|--------:|--------:|
| BenchmarkGrpc | 147.2 us | 0.71 us | 0.59 us |
| BenchmarkRest | 111.6 us | 0.68 us | 0.90 us |

// * Hints *
Outliers
  BenchmarkWithSamePayload.BenchmarkGrpc: Default -> 2 outliers were removed, 3 outliers were detected (145.56 us, 150.79 us, 152.36 us)
  BenchmarkWithSamePayload.BenchmarkRest: Default -> 8 outliers were removed (118.18 us..126.86 us)

// * Legends *
  Mean   : Arithmetic mean of all measurements
  Error  : Half of 99.9% confidence interval
  StdDev : Standard deviation of all measurements
  1 us   : 1 Microsecond (0.000001 sec)

相关代码

gRPC部分

Protos定义

rpc SayHello (HelloRequest) returns (HelloReply);
message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

gRPC客户端代码

public class Sender
{
    private GrpcChannel _grpcChannel;
    private Greeter.GreeterClient _greeter;
    private HelloRequest _defaultRequest;

    public Sender()
    {
        _grpcChannel = GrpcChannel.ForAddress("http://localhost:5264");
        _greeter = new Greeter.GreeterClient(_grpcChannel);
        _defaultRequest = new HelloRequest() { Name = "default" };
    }

    public async Task<string> PostDefault()
    {
        var reply = await _greeter.SayHelloAsync(_defaultRequest);

        return reply.Message;
    }
}

gRPC服务端代码

Program.cs
using GrpcService.Services;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddGrpc();

var app = builder.Build();

// Configure the HTTP request pipeline.
app.MapGrpcService<Service>();

app.Run();
Service实现
public class Service : Greeter.GreeterBase
{
    public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
    {
        return Task.FromResult(new HelloReply
        {
            Message = "Hello " + request.Name
        });
    }
}

REST部分

消息定义

HelloRequest
public class HelloRequest
{
    public string Name { get; set; }
}
HelloResponse
public class HelloResponse
{
    public string Message { get; set; }
}

REST客户端代码

public class Sender
{
    private HttpClient _httpClient;
    private HelloRequest _defaultRequest;

    public Sender()
    {
        _httpClient = new HttpClient();
        _defaultRequest = new HelloRequest() { Name = "default" };
    }


    public async Task<string> PostDefault()
    {
        var content = JsonContent.Create(_defaultRequest);
        var response = await _httpClient.PostAsync("http://localhost:5082/greet", content);
        var responseString = await response.Content.ReadAsStringAsync();
        return JsonSerializer.Deserialize<HelloResponse>(responseString)!.Message;
    }
}

REST服务端代码

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var app = builder.Build();

app.MapPost("/greet", (HelloRequest request) =>
{
    return new HelloResponse() { Message = "Hello " + request.Name };
});

app.Run();

Benchmark部分

Program.cs

var summary = BenchmarkDotNet.Running.BenchmarkRunner.Run<BenchmarkWithSamePayload>();

BenchmarkWithSamePayload测试类

public class BenchmarkWithSamePayload : BenchmarkBase
{
    [Benchmark]
    public async Task<string> BenchmarkGrpc()
    {
        return await _gRpcSender.PostDefault();
    }

    [Benchmark]
    public async Task<string> BenchmarkRest()
    {
        return await _restSender.PostDefault();
    }
}

BenchmarkBase基类

namespace BenchmarkRunner
{
    using gRpcSender = GrpcClient.Sender;
    using RestSender = RestClient.Sender;

    public abstract class BenchmarkBase
    {
        protected gRpcSender _gRpcSender;
        protected RestSender _restSender;

        [GlobalSetup]
        public void Setup()
        {
            _gRpcSender = new gRpcSender();
            _restSender = new RestSender();
        }
    }
}

BenchmarkConfig配置类(因杀毒软件要求)

public class BenchmarkConfig : ManualConfig
{
    public BenchmarkConfig()
    {
        AddJob(Job.MediumRun.WithToolchain(InProcessNoEmitToolchain.Instance));
    }
}

分析与解答

一、当前测试结果的合理性

你的测试结果在小payload场景下REST略优是合理的,原因如下:
gRPC的性能优势核心在于大payload传输、高并发多路复用、流式场景,而当前测试仅传递简短字符串:

  • 小数据量下,Protobuf与JSON的序列化/反序列化性能差异微乎其微
  • gRPC基于HTTP/2,握手、头部压缩等额外逻辑会产生少量固定开销,反而比HTTP/1.1的REST单次请求耗时略高

二、测试配置的潜在优化点

  1. 进程内测试的干扰
    使用InProcessNoEmitToolchain会让基准测试与服务同进程运行,容易引发线程池、端口等资源竞争,建议将gRPC和REST服务作为独立进程启动后再执行测试(若杀毒软件允许)。

  2. 连接复用的优势未体现
    当前测试仅测单次请求,gRPC的长连接、HTTP/2多路复用优势无法发挥,建议增加并发测试或多次请求的吞吐量测试(测每秒处理请求数)。

  3. 缺少预热逻辑
    尽管BenchmarkDotNet会自动预热,但网络请求类测试建议在GlobalSetup中手动调用几次接口,让连接池、序列化器完成初始化,避免首次请求的冷启动开销影响结果。

三、优化测试的方向

  • 增加大payload测试:构造包含嵌套结构、大量字段的请求,对比Protobuf与JSON的序列化性能差异
  • 测试吞吐量:用[Benchmark(Baseline = true)]标记基准,关注每秒请求数(Ops/s),gRPC在高并发下的优势会更明显
  • 测试流式场景:验证gRPC的客户端/服务端双向流式能力,这是REST不具备的核心优势
  • 优化gRPC配置:调整GrpcChannelOptions的连接参数、重试策略,最大化通道复用效率

内容的提问来源于stack exchange,提问作者Henrique

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 23:32:05