VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • C#网络编程之性能测试与调优(BenchmarkDotNet、Wireshark)

第20章 性能测试与调优
20.1 性能测试与调优(BenchmarkDotNet、Wireshark)
一、我踩过的性能测试坑:从“Stopwatch不准”到“抓包10GB找不到问题”
刚做API性能优化时,用Stopwatch测AES加密的时间,结果每次测的时间都不一样,一会儿1ms,一会儿10ms,根本找不到瓶颈;后来用BenchmarkDotNet才发现,是JIT编译和GC的影响,Stopwatch测的是“冷启动”时间,而BenchmarkDotNet测的是“稳定运行”时间。还有一次,API延迟高,用Wireshark抓包,没设置过滤规则,抓了10GB数据,翻了3小时都没找到问题,后来用tcp.port == 443 && http.request.method == GET过滤,才发现是DNS解析慢,每次请求要等500ms。这节我把自己从“性能测试小白”到“调优专家”的踩坑经验揉进去,用大白话讲透BenchmarkDotNet的代码性能测试,Wireshark的网vb.net教程C#教程python教程SQL教程access 2010教程络抓包,以及如何定位和解决性能瓶颈,让你的代码和网络既快又稳。
二、BenchmarkDotNet:代码性能测试的“瑞士军刀”
BenchmarkDotNet是C#最流行的性能测试库,它会自动处理JIT编译、GC、预热等影响测试结果的因素,给出准确的性能数据——就像你用专业的测速仪测汽车的速度,而不是用手机秒表。
为什么Stopwatch不准?
Stopwatch的问题:
1.JIT编译:第一次运行代码时,.NET会把IL编译成机器码,这个过程耗时,Stopwatch会把这个时间算进去;
2.GC:测试过程中如果发生GC,会暂停程序,Stopwatch会把GC时间算进去;
3.预热:代码第一次运行时,CPU缓存没命中,速度慢,Stopwatch测的是冷启动时间,不是稳定运行时间。
BenchmarkDotNet的解决方法:
1.预热:先运行几次代码,让JIT编译完成,CPU缓存命中;
2.多次运行:运行多次代码,取平均值,减少误差;
3.隔离测试:每次测试都创建新的AppDomain,避免GC和JIT的影响;
4.详细报告:给出平均时间、中位数、内存分配、GC次数等数据。
实战1:用BenchmarkDotNet测试AES加密性能
测试AES-GCM和AES-CBC的性能对比,看哪个更快,内存占用更少。
步骤1:安装BenchmarkDotNet NuGet包
bash
Install-Package BenchmarkDotNet
步骤2:编写性能测试代码
csharp

	using System;
	using System.Security.Cryptography;
	using System.Text;
	using BenchmarkDotNet.Attributes;
	using BenchmarkDotNet.Running;
	
	namespace PerformanceTesting;
	
	[MemoryDiagnoser] // 启用内存诊断,显示内存分配和GC次数
	[DisassemblyDiagnoser] // 启用汇编诊断,显示生成的机器码(可选)
	[RPlotExporter] // 导出结果为R图表(可选)
	public class AesBenchmark
	{
	private byte[] _key;
	private byte[] _plaintext;
	private byte[] _iv;
	private byte[] _nonce;
	
	// 初始化:测试前运行一次,生成密钥、明文、IV、Nonce
	[GlobalSetup]
	public void Setup()
	{
	// 生成256位AES密钥
	_key = AesGcm.GenerateKey(AesGcm.KeyByteSizes.MaxSize);
	// 生成1KB明文(模拟实际数据)
	_plaintext = Encoding.UTF8.GetBytes(new string('a', 1024));
	// 生成AES-CBC的IV
	_iv = new byte[16];
	RandomNumberGenerator.Fill(_iv);
	// 生成AES-GCM的Nonce
	_nonce = new byte[AesGcm.NonceByteSizes.MaxSize];
	RandomNumberGenerator.Fill(_nonce);
	}
	
	// 测试AES-GCM加密
	[Benchmark]
	public byte[] AesGcmEncrypt()
	{
	using var aesGcm = new AesGcm(_key);
	byte[] ciphertext = new byte[_plaintext.Length];
	byte[] tag = new byte[AesGcm.TagByteSizes.MaxSize];
	aesGcm.Encrypt(_nonce, _plaintext, ciphertext, tag);
	// 返回密文+标签,避免被JIT优化掉
	return ciphertext.Concat(tag).ToArray();
	}
	
	// 测试AES-CBC加密
	[Benchmark]
	public byte[] AesCbcEncrypt()
	{
	using var aes = Aes.Create();
	aes.Key = _key;
	aes.IV = _iv;
	aes.Mode = CipherMode.CBC;
	aes.Padding = PaddingMode.PKCS7;
	using var encryptor = aes.CreateEncryptor();
	return encryptor.TransformFinalBlock(_plaintext, 0, _plaintext.Length);
	}
	
	public static void Main(string[] args)
	{
	// 运行性能测试
	var summary = BenchmarkRunner.Run<AesBenchmark>();
	}
	}

代码逐行讲解:
1.特性配置:
1.[MemoryDiagnoser]:启用内存诊断,显示每次测试的内存分配和GC次数;
2.[DisassemblyDiagnoser]:启用汇编诊断,显示生成的机器码,适合深入分析性能瓶颈;
3.[RPlotExporter]:导出结果为R图表,方便可视化对比;
2.[GlobalSetup]:测试前运行一次,生成密钥、明文、IV、Nonce——避免在测试方法中生成,影响测试结果;
3.[Benchmark]:标记测试方法,BenchmarkDotNet会自动运行这些方法,统计性能数据;
4.返回结果:测试方法必须返回结果,避免被JIT优化掉——如果方法没有返回值,JIT可能会把整个方法优化掉,导致测试结果为0;
5.BenchmarkRunner.Run:运行性能测试,生成详细报告。
步骤3:运行测试(必须用Release模式)
注意:必须用Release模式运行,Debug模式的性能测试结果不准!
bash

	# 用命令行运行,避免Visual Studio调试影响
	dotnet run -c Release

步骤4:分析测试结果
BenchmarkDotNet会生成类似下面的报告:

Method Mean Error StdDev Gen0 Allocated
AesGcmEncrypt 1.234 μs 0.012 μs 0.011 μs 0.0019 48 B
AesCbcEncrypt 3.456 μs 0.034 μs 0.032 μs 0.0038 96 B

报告分析:
Mean:平均时间,AES-GCM比AES-CBC快2倍多;
Error/StdDev:误差和标准差,数值越小,测试结果越准确;
Gen0:每次测试触发的Gen0 GC次数,AES-GCM触发的GC更少;
Allocated:每次测试的内存分配,AES-GCM的内存分配是AES-CBC的一半。
我踩过的坑:用Debug模式运行测试,结果AES-GCM的时间是10μs,AES-CBC的时间是20μs,和Release模式的结果差10倍——Debug模式会禁用JIT优化,所以测试结果不准,必须用Release模式!
拓展知识:BenchmarkDotNet的最佳实践
1.用Release模式运行:禁用JIT优化,测试结果不准;
2.避免在测试方法中创建对象:对象创建会触发GC,影响测试结果,应该在[GlobalSetup]中创建;
3.返回结果:测试方法必须返回结果,避免被JIT优化掉;
4.用[IterationSetup]和[IterationCleanup]:如果每次测试都需要初始化和清理,用这两个特性,而不是在测试方法中;
5.用[Arguments]测试不同参数:比如测试不同大小的明文,看性能变化:
csharp

	[Benchmark]
	[Arguments(1024)] // 1KB
	[Arguments(1024*1024)] // 1MB
	public byte[] AesGcmEncrypt(int plaintextSize)
	{
	byte[] plaintext = new byte[plaintextSize];
	// ...
	}

三、Wireshark:网络性能测试的“显微镜”
Wireshark是最流行的网络抓包工具,它可以捕获和分析网络数据包,帮助你定位网络性能瓶颈——比如DNS解析慢、TCP握手慢、服务器响应慢、HTTPS握手慢等。
核心概念(拓展知识)
数据包:网络传输的基本单位,每个数据包包含源IP、目标IP、源端口、目标端口、协议、数据等;
过滤规则:Wireshark的核心,用过滤规则筛选数据包,比如tcp.port == 443只显示HTTPS数据包;
解密HTTPS:用服务器的私钥解密HTTPS数据包,看明文内容;
统计功能:Wireshark的统计功能,比如TCP流分析、HTTP请求时间分析、DNS解析时间分析。
实战2:用Wireshark定位API延迟高的问题
假设你的API延迟高,每次请求要等1秒,用Wireshark抓包,定位问题。
步骤1:安装Wireshark
从官网下载安装:https://www.wireshark.org/,安装时选择“Install WinPcap”或“Install Npcap”,用于捕获数据包。
步骤2:抓包并设置过滤规则
1.打开Wireshark,选择你的网络适配器(比如Wi-Fi或以太网);
2.点击“Start”开始抓包;
3.访问你的API(比如https://example.com/api/order);
4.点击“Stop”停止抓包;
5.设置过滤规则:http.request.method == GET && tcp.port == 443,只显示GET请求的HTTPS数据包。
步骤3:分析HTTP请求时间
1.找到你的API请求,右键选择“Follow”→“HTTP Stream”,查看完整的HTTP流;
2.看Wireshark的“Time”列,计算每个阶段的时间:
DNS解析时间:从DNS请求到DNS响应的时间,正常应该小于100ms;
TCP握手时间:从SYN到SYN-ACK-ACK的时间,正常应该小于10ms;
HTTPS握手时间:从Client Hello到Server Hello Done的时间,TLS 1.3应该小于10ms,TLS 1.2应该小于100ms;
服务器响应时间:从Request到Response的时间,正常应该小于100ms;
总时间:从DNS请求到Response的时间,正常应该小于300ms。
我踩过的坑:抓包时没设置过滤规则,抓了10GB数据,翻了3小时都没找到问题——解决方法是先设置过滤规则,只抓你需要的数据包,比如tcp.port == 443或http.host == example.com。
实战3:解密HTTPS流量,看明文内容
有时候你需要看HTTPS的明文内容,比如API请求的参数和响应,用Wireshark解密HTTPS流量。
步骤1:获取服务器的私钥
如果是用Let's Encrypt证书,私钥路径是/etc/letsencrypt/live/example.com/privkey.pem;如果是用自签名证书,私钥是你生成的.pfx文件。
步骤2:配置Wireshark解密HTTPS
1.打开Wireshark,选择“Edit”→“Preferences”→“Protocols”→“TLS”;
2.在“RSA keys list”中点击“Edit”,添加服务器的私钥:
IP address:服务器的IP地址;
Port:443;
Protocol:http;
Key file:选择服务器的私钥文件(.pem或.pfx);
3.点击“OK”保存配置;
4.重新抓包,Wireshark会自动解密HTTPS数据包,显示明文内容。
拓展知识:HTTPS解密的原理
HTTPS用对称加密传输数据,对称密钥是在握手时生成的,服务器的私钥用于解密握手过程中的对称密钥,所以Wireshark用服务器的私钥解密握手过程,得到对称密钥,然后用对称密钥解密HTTPS数据包。
实战4:分析TLS 1.3和TLS 1.2的握手时间
1.抓TLS 1.3的握手数据包,过滤规则:tls.handshake.type == 1(Client Hello);
2.看Client Hello到Server Hello Done的时间,TLS 1.3应该小于10ms;
3.抓TLS 1.2的握手数据包,过滤规则:tls.handshake.type == 1;
4.看Client Hello到Server Hello Done的时间,TLS 1.2应该小于100ms;
5.对比两者的握手时间,TLS 1.3比TLS 1.2快10倍。
拓展知识:Wireshark常用过滤规则

过滤规则 作用
tcp.port == 443 只显示HTTPS数据包
http.request.method == GET 只显示GET请求的HTTP数据包
dns 只显示DNS数据包
tcp.flags.syn == 1 只显示TCP SYN数据包(握手开始)
http.host == example.com 只显示example.com的HTTP数据包
ip.src == 192.168.1.100 只显示源IP是192.168.1.100的数据包
ip.dst == 192.168.1.100 只显示目标IP是192.168.1.100的数据包

四、性能测试与调优的完整流程

  1. 定位瓶颈
    代码性能瓶颈:用BenchmarkDotNet测试代码的性能,看哪个方法慢,内存分配多,GC次数多;
    网络性能瓶颈:用Wireshark抓包,分析DNS解析时间、TCP握手时间、HTTPS握手时间、服务器响应时间。
  2. 优化
    代码优化:
    用AES-GCM代替AES-CBC;
    避免频繁创建对象,用对象池;
    用异步代码代替同步代码,提高并发;
    用Span代替byte[],减少内存分配;
    网络优化:
    启用TLS 1.3,减少HTTPS握手时间;
    启用HTTP/2,多路复用,减少TCP连接数;
    用CDN加速静态资源,减少DNS解析时间;
    优化服务器配置,比如增加CPU、内存,调整Kestrel的最大连接数。
  3. 验证
    用BenchmarkDotNet重新测试代码性能,看是否提升;
    用Wireshark重新抓包,看网络延迟是否降低;
    用JMeter或LoadRunner做压力测试,看并发数是否提升。
    拓展知识:压力测试工具
    JMeter:开源的压力测试工具,支持HTTP、HTTPS、TCP等协议;
    LoadRunner:商业的压力测试工具,功能强大,适合大型项目;
    k6:基于JavaScript的压力测试工具,适合DevOps场景;
    BenchmarkDotNet:适合代码性能测试,不适合压力测试。
    五、总结:性能测试与调优的核心思想
    性能测试与调优的核心是“先定位,后优化,再验证”,不要盲目优化,要先找到瓶颈,再针对性优化,最后验证优化效果。
    工具选择
    代码性能测试:用BenchmarkDotNet,准确、详细;
    网络性能测试:用Wireshark,强大、灵活;
    压力测试:用JMeter或k6,模拟高并发场景。
    最佳实践
    1.定期测试:每周或每月做一次性能测试,及时发现瓶颈;
    2.监控生产环境:用Prometheus、Grafana监控生产环境的性能指标,比如CPU、内存、网络延迟、API响应时间;
    3.优化要适度:不要过度优化,比如为了提高1%的性能,增加10倍的代码复杂度,得不偿失;
    4.用数据说话:优化前后都要用数据对比,看是否真的提升了性能。
    下一节我们会学习生产环境的性能监控与告警,让你的系统在性能下降时及时通知你。

 本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49548.html


相关教程