我最近开始使用 gRPC,并且总是听说它比 REST 快得多。所以我创建了一个基准项目,因为我想知道它实际上快了多少。事实证明,经过几种不同的方法后,REST 总是比 gRPC 稍好一些。我对基准测试和 gRPC 都没有经验,所以一定是出了问题。我的猜测是,也许我的基准测试设置不正确。
所以我的问题是:我的基准测试有什么问题?或者说结果真的有意义吗?
我在这里创建了一个包含完整代码的存储库:https://github.com/henridd/RestVsGrpcBenchmark。对于这个问题,我将讨论 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)
您可以在下面找到此基准测试的相关代码。
rpc SayHello (HelloRequest) returns (HelloReply);
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
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;
}
}
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();
public class Service : Greeter.GreeterBase
{
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
return Task.FromResult(new HelloReply
{
Message = "Hello " + request.Name
});
}
}
public class HelloRequest
{
public string Name { get; set; }
}
public class HelloResponse
{
public string Message { get; set; }
}
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;
}
}
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();
var summary = BenchmarkDotNet.Running.BenchmarkRunner.Run<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();
}
}
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();
}
}
}
public class BenchmarkConfig : ManualConfig
{
public BenchmarkConfig()
{
AddJob(Job.MediumRun.WithToolchain(InProcessNoEmitToolchain.Instance));
}
}
首先,使用微基准测试工具对 Web 应用程序进行性能测试可能被认为并不理想,因为 Web 服务通常设计为提供比延迟更好的吞吐量(即处理更多负载而不是更快地处理单个请求)。
其次(基于代码)您正在非常特定的情况下执行测试 - 您正在同一台机器上测试“简单”响应,而 GRPC 的目标是减少通过网络发送的消息的大小,这可能是较小的因素在同一台机器上通过 TCP 进行单线程测试。生成的 GRPC 服务器是为吞吐量而设计的(因此方法签名中的
Task
),因此已经存在一个小差异 - REST 不会打扰 Task.FromResult
,这也会增加一些开销(尽管不是那么明显) ).
TL;博士
最好通过网络使用一些负载测试工具(例如NBomber),并检查负载服务能够处理的差异。