首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >在.net中开发高性能应用程序代码的技术和示例

在.net中开发高性能应用程序代码的技术和示例

作者头像
郑子铭
发布于 2024-12-09 12:43:34
发布于 2024-12-09 12:43:34
2.6K0
举报

受 Steve Gordon 的 NDC Oslo 2024 演讲的启发,本文通过实际示例探讨了编写高性能 .NET 代码的基本技术。在这里,我们将介绍关键的优化,从节省内存的数据处理到改进的 JSON 序列化。

1. 用于节省内存的字符串处理Span<T>

使用字符串时,避免不必要的内存分配至关重要。 提供了一种无需创建新字符串即可执行 subString 操作的方法。Span<T>

代码语言:javascript
复制
publicclassStringProcessor
{
publicstaticstringTraditionalSubstring(string input,int start,int length)
{
return input.Substring(start, length);// Allocates new string
}

publicstaticReadOnlySpan<char>OptimizedSubstring(ReadOnlySpan<char> input,int start,int length)
{
return input.Slice(start, length);// No allocation
}

publicstaticboolContainsOptimized(string haystack,string needle)
{
ReadOnlySpan<char> haystackSpan= haystack.AsSpan();
ReadOnlySpan<char> needleSpan= needle.AsSpan();
return haystackSpan.Contains(needleSpan, StringComparison.Ordinal);
}
}

使用允许字符串切片和搜索操作,而无需额外分配内存,从而提高性能,尤其是在高频字符串操作中。ReadOnlySpan<char>

2. 用于临时数组优化ArrayPool

使用临时数组时,可以通过重用数组来减轻内存压力,尤其适用于 IO 操作中的大型缓冲区。ArrayPool

代码语言:javascript
复制
publicclassBufferProcessor
{
privatestaticreadonlyArrayPool<byte> _arrayPool= ArrayPool<byte>.Shared;

publicasyncTaskProcessLargeData(Stream stream)
{
byte[] buffer= _arrayPool.Rent();// Rent 80KB buffer
try
{
while(await stream.ReadAsync(buffer)isint bytesRead&& bytesRead>)
{
ProcessBuffer(buffer.AsSpan(, bytesRead));
}
}
finally
{
 _arrayPool.Return(buffer);
}
}

privatevoidProcessBuffer(ReadOnlySpan<byte> buffer)
{
// Process buffer data
}
}

使用 ,您可以租用一个数组用于临时使用并返回它,从而减少高吞吐量应用程序中的垃圾收集开销。ArrayPool

3. 利用高性能数据流System.IO.Pipelines

System.IO.Pipelines提供强大的 API,用于以最少的内存分配处理高性能数据处理,在网络流或文件处理等场景中特别有用。

代码语言:javascript
复制
publicclassPipelineReader
{
privatereadonlyPipeReader _reader;

publicPipelineReader(Stream stream)
{
 _reader= PipeReader.Create(stream);
}

publicasyncTaskProcessDataAsync()
{
while(true)
{
ReadResult result=await _reader.ReadAsync();
ReadOnlySequence<byte> buffer= result.Buffer;

try
{
while(TryReadLine(ref buffer,outReadOnlySequence<byte> line))
{
awaitProcessLineAsync(line);
}

if(result.IsCompleted)
break;
}
finally
{
 _reader.AdvanceTo(buffer.Start, buffer.End);
}
}
}

privateboolTryReadLine(refReadOnlySequence<byte> buffer,outReadOnlySequence<byte> line)
{
SequencePosition? position= buffer.PositionOf((byte)'\n');

if(position==null)
{
 line=default;
returnfalse;
}

 line= buffer.Slice(, position.Value);
 buffer= buffer.Slice(buffer.GetPosition(, position.Value));
returntrue;
}
}

System.IO.Pipelines通过使用 无需重新分配即可处理数据,帮助有效地管理大量数据流。ReadOnlySequence

4. 优化 JSON 处理System.Text.Json

System.Text.Json提供高效的 JSON 序列化和反序列化选项,包括源生成的序列化,以获得额外的性能优势。

代码语言:javascript
复制
publicclassJsonOptimizer
{
privatestaticreadonlyJsonSerializerOptions _options=new()
{
 PropertyNameCaseInsensitive=true,
 AllowTrailingCommas=true,
 WriteIndented=false
};

publicstaticstringSerializeOptimized<T>(Tvalue)
{
return JsonSerializer.Serialize(value, _options);
}

publicstaticT?DeserializeOptimized<T>(ReadOnlySpan<byte> utf8Json)
{
return JsonSerializer.Deserialize<T>(utf8Json, _options);
}

[JsonSourceGenerationOptions(
 WriteIndented=false,
 PropertyNamingPolicy= JsonKnownNamingPolicy.CamelCase)]
[JsonSerializable(typeof(WeatherForecast))]
internalpartialclassJsonContext:JsonSerializerContext
{
}
}

使用 source generation () 可以减少开销,并允许以更好的性能处理复杂的序列化场景。JsonSerializerContextSystem.Text.Json

5. 使用 BenchmarkDotNet 测量性能

BenchmarkDotNet 是用于测量 .NET 性能的宝贵工具。下面是用于比较和分配的示例基准。Span<T>string

代码语言:javascript
复制
[MemoryDiagnoser]
publicclassStringOperationsBenchmark
{
privateconststring Sample="Hello, World! This is a test string for benchmarking.";

[Benchmark(Baseline=true)]
publicstringTraditional()
{
return Sample.Substring(,);
}

[Benchmark]
publicReadOnlySpan<char>Optimized()
{
return Sample.AsSpan(,);
}
}

BenchmarkDotNet 提供有关内存使用情况和运行时性能的详细报告,帮助您做出明智的优化决策。

6. 使用 StringBuilder 池化最小化内存分配

池化可重用对象(如 )可在频繁构建和丢弃大型字符串的情况下减少内存流失。StringBuilder

代码语言:javascript
复制
publicclassStringBuilderPool
{
privatestaticreadonlyObjectPool<StringBuilder> _pool=
newDefaultObjectPool<StringBuilder>(newStringBuilderPooledObjectPolicy());

publicstaticstringBuildString(Action<StringBuilder> action)
{
var sb= _pool.Get();
try
{
action(sb);
return sb.ToString();
}
finally
{
 sb.Clear();
 _pool.Return(sb);
}
}
}

publicclassStringBuilderPooledObjectPolicy:PooledObjectPolicy<StringBuilder>
{
publicoverrideStringBuilderCreate()
{
returnnewStringBuilder();
}

publicoverrideboolReturn(StringBuilder obj)
{
if(obj.Capacity>)
returnfalse;

 obj.Clear();
returntrue;
}
}

使用可防止过度分配,使其适用于需要高性能字符串操作的应用程序。ObjectPool<StringBuilder>

7. 优化 HTTP 客户端使用

与配置一起重复使用有助于减少 TCP 连接的开销,并提高高流量应用程序中的请求效率。HttpClientSocketsHttpHandler

代码语言:javascript
复制
publicclassOptimizedHttpClient
{
privatestaticreadonlyHttpClient _client=new(newSocketsHttpHandler
{
 PooledConnectionLifetime= TimeSpan.FromMinutes(),
 MaxConnectionsPerServer=,
 EnableMultipleHttp2Connections=true
});

publicstaticasyncTask<string>GetDataAsync(string url,CancellationToken token=default)
{
usingvar response=await _client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
 response.EnsureSuccessStatusCode();

returnawait response.Content.ReadAsStringAsync(token);
}
}

设置像 和 这样的选项允许通过重用连接来有效地处理多个请求。PooledConnectionLifetimeMaxConnectionsPerServerHttpClient

  • 优化前测量:使用 BenchmarkDotNet 等工具确保性能提升。
  • 安全使用:在使用 和 时避免不安全的代码做法。
  • Span<T> Span<T>Memory<T>
  • 利用池化:明智地使用 和 对象池来控制内存使用。ArrayPool
  • Leverage :用于 JSON 操作,以受益于其速度和内存效率。System.Text.JsonSystem.Text.Json
  • 最小化内存分配:优先考虑可读代码,但旨在减少不必要的分配。
  • 谨慎使用高性能 API:仅在需要时应用高性能优化。

这些示例和最佳实践为优化现代 .NET 应用程序提供了一种基本方法。每种技术都有其用例,必须通过测量和测试选择最合适的优化策略。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-12-07,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 1. 用于节省内存的字符串处理Span<T>
  • 2. 用于临时数组优化ArrayPool
  • 3. 利用高性能数据流System.IO.Pipelines
  • 4. 优化 JSON 处理System.Text.Json
  • 5. 使用 BenchmarkDotNet 测量性能
  • 6. 使用 StringBuilder 池化最小化内存分配
  • 7. 优化 HTTP 客户端使用
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档