当前位置:网站首页>Simple implementation of a high-performance clone of Redis using .NET (seven-end)

Simple implementation of a high-performance clone of Redis using .NET (seven-end)

2022-08-11 10:10:00 InCerry

译者注

该原文是Ayende Rahien大佬业余自己在使用C# 和 .NET构建一个简单、高性能兼容Redis协议的数据库的经历.
首先这个"Redis"是非常简单的实现,但是他在优化这个简单"Redis"路程很有趣,也能给我们在从事性能优化工作时带来一些启示.
原作者:Ayende Rahien
原链接:
https://ayende.com/blog/197665-C/high-performance-net-building-a-redis-clone-analysis-ii

另外Ayende大佬是.NETAn open source high-performance multi-paradigm databaseRavenDB所在公司的CTO,It is not excluded that these articles are intended to be published in the futureRavenDB上兼容RedisAttempt to do with the protocol.You can also support,Links are given below
RavenDB地址:https://github.com/ravendb/ravendb

构建Redis克隆版-第二次分析

I'm going to take a few steps back,See where I should look next,See where I should pay attention.到目前为止,在本系列中,My main concern is how to read and process the data.But I think we should take a step or two back,Take a look at our overall situation now.I ran the profiler using Pipelinesand the string version,Trying to understand our progress.例如,在上一篇文章中,我使用的 ConcurrentDictionary There is a large performance overhead.现在还是这样吗?

Below is the current hotspot data in the codebase:

in more detail,如下所示:

You can see that processing network requests takes up most of the time,我们再来看看HandleConnection代码:

public async Task HandleConnection()
{
    while (true)
    {
        var result = await _netReader.ReadAsync();
        var (consumed, examined) = ParseNetworkData(result);
        _netReader.AdvanceTo(consumed, examined);
        await _netWriter.FlushAsync();
    }
}

View code and profiler results,I feel like I know how to do better.A small modification below brought me2%的性能提升.

public async Task HandleConnection()
{
    // 复用了readTask 和 flushTask
    // Reduced some memory usage
    ValueTask<ReadResult> readTask = _netReader.ReadAsync();
    ValueTask<FlushResult> flushTask = ValueTask.FromResult(new FlushResult());
    while (true)
    {
        var result = await readTask;
        await flushTask;
        var (consumed, examined) = ParseNetworkData(result);
        _netReader.AdvanceTo(consumed, examined);
        readTask = _netReader.ReadAsync();
        flushTask = _netWriter.FlushAsync();
    }
}

The idea is to parallelize the reads and writes of the network.This is a small boost,But any little help is good,Especially when various optimizations correlate impacts.

看看这个,我们已经有将近20亿个ReadAsync调用,Let's see how much it costs:

真是... 哇.

为什么InternalTokenSource如此昂贵?I bet the problem is here,It's locked.在我的用例中,I know there is a separate thread running these commands,不会有并发问题,So it's worth seeing if you can skip it.不幸的是,There isn't an easy way to skip checking.幸运的是,I can copy the code from the framework and modify it locally,to understand the impact of doing so.所以我就这样做了(Initialized once in the constructor) :

This means that we have approx40%的改进.正如我前面提到的,This is not something we can do now,Because the source code is therelock,但是这是一个关于使用 PipeReader Interesting point about the performance penalty for reading data.

Another very interesting aspect is backend storage,它是一个ConcurrentDictionary.If we look at its cost,我们会发现:

您会注意到,我正在使用NonBlocking的NuGet包,It provides a lock-free one ConcurrentDictionary实现.如果我们使用.NETThe default implementation in the framework,It does use locks,我们将看到:

Below is a comparison of them:

请注意,There is a very large cost difference between these two options(Good for non-blocking).但是,when we run a real benchmark,It doesn't make a particularly big difference.
那接下来呢?
Take a look at the profiler results,We have nothing to keep improving.Most of our costs are in the network,not in the code we run.

Most of our code is there ParseNetworkData 调用中,看起来像这样:

So the time we actually spend performing server core functions is negligible.实际上,Parsing the command from the buffer takes a lot of time.注意,在这里,We don't actually implement any I/O 操作,All operations operate on buffers in memory.

RedisProtocols are not friendly to machine parsing,It takes us a lot of lookup to find the delimiter(因此有很多的IndexOf()调用).I don't think you can make a significant improvement in this area.This means we have to consider other better performance options.

我们花费了35% The runtime to parse the command stream from the client,And the code we execute is not at runtime1% .I don't think there are significant optimization opportunities for stream parsing,So we are leftI/O的优化方向.我们能做得更好吗?

We are currently using asyncI/O和Pipelines.Check out this one that interests me项目,它在Linux使用了IO_Uring(通过这个API)to meet their needs.Their parsing is also simple,请看这里,Very similar to how my code works.

因此,To move to the next stage of performance(提醒一下,Our current performance is180w/s) ,We may also need to use based onIO_Uring的方法.有一个NuGet软件包来支持它,But this allows me to spend hours a night on this task,Instead of taking days or a week to do it.I don't think I will continue to pursue this goal in the near future.

结尾

完结撒花!!!按照AyendeThe big guy means that he will try it laterlinux上使用IO_Uring来实现,There are no other updates so far,All published blog posts have been translated.

I also put forward some other small suggestions for performance optimization at the bottom of the blog post,The suggestion comes from an article I posted earlier,The same high-performance web service development.Those interested can view the link below.
https://www.cnblogs.com/InCerry/p/highperformance-alternats.html

系列链接

使用.NET简单实现一个Redis的高性能克隆版(一)
使用.NET简单实现一个Redis的高性能克隆版(二)
使用.NET简单实现一个Redis的高性能克隆版(三)
使用.NET简单实现一个Redis的高性能克隆版(四、五)
使用.NET简单实现一个Redis的高性能克隆版(六)

Follow-up if there are other updates,Aite is also welcome to urge me to update

原网站

版权声明
本文为[InCerry]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/223/202208110954596269.html