如何使用 dotTrace 来诊断 netcore 应用的性能问题
最近在为 Newbe.Claptrap 做性能升级,因此将过程中使用到的 dotTrace 软件的基础用法介绍给各位开发者。
Newbe.Claptrap 是一个用于轻松应对并发问题的分布式开发框架。如果您是首次阅读本系列文章。建议可以先从本文末尾的入门文章开始了解。
开篇摘要
dotTrace 是 Jetbrains 公司为 .net 应用提供的一款 profile 软件。有助于对于软件中的耗时函数和内存问题进行诊断分析。
本篇,我们将使用 Jetbrains 公司的 dotTrace 软件对一些已知的性能问题进行分析。从而使读者能够掌握使用该软件的基本技能。
过程中我们将搭配一些经典的面试问题进行演示,逐步解释该软件的使用。
此次示例使用的是 Rider 作为主要演示的 IDE。 开发者也可以使用 VS + Resharper 做出相同的效果。
如何获取 dotTrace
dotTrace 是付费软件。目前只要购买 dotUltimate 及以上的许可证便可以直接使用该软件。
当然,该软件也包含试用版本,可以免费开启 7 天的试用时间。Jetbrains 的 IDE 购买满一年以上即可获取一个当前最新的永久使用版本。
或者也可以直接购买 Jetbrains 全家桶许可证,一次性全部带走。
经典场景再现
接下来,我们通过一些经典的面试问题,来体验一下如何使用 dotTrace。
何时要使用 StringBuilder
这是多么经典的面试问题。能够看到这篇文章的朋友,我相信各位都知道 StringBuilder 能够减少 string 直接拼接的碎片,减少内存压力这个道理。
我们这是真的吗?会不会只是面试官想要刁难我,欺负我信息不对称呢?
没有关系,接下来,让我们使用 dotTrace 来具体的结合代码来分析一波。看看使用 StringBuilder 究竟有没有减低内存分配的压力。
首先,我们创建一个单元测试项目,并添加以下这样一个测试类:
using System.Linq;
using System.Text;
using NUnit.Framework; namespace Newbe.DotTrace.Tests
{
public class X01StringBuilderTest
{
[Test]
public void UsingString()
{
var source = Enumerable.Range(0, 10)
.Select(x => x.ToString())
.ToArray();
var re = string.Empty;
for (int i = 0; i < 10_000; i++)
{
re += source[i % 10];
}
} [Test]
public void UsingStringBuilder()
{
var source = Enumerable.Range(0, 10)
.Select(x => x.ToString())
.ToArray();
var sb = new StringBuilder();
for (var i = 0; i < 10_000; i++)
{
sb.Append(source[i % 10]);
} var _ = sb.ToString();
}
}
}
然后,如下图所示,我们将 Rider 中的 profile 模式设置为 Timeline 。
TimeLine 是多种模式中的一种,相较而言,该模式可以更全面的了解各个线程的工作情况,包括有内存分配、IO 处理、锁、反射等等多维度数据。这将会作为本示例主要使用的一种模式。
接着,如下图所示,通过单元测试左侧的小图标启动对应测试的 profile。
启动 profile 之后,等待一段时间之后,便会出现最新生成的 timeline 报告。查看报告的位置如下所示:
右键选择对应的报告,选择”Open in External Viewer”,便可以使用 dotTrace 打开生成好的报告。
那么首先,让我打开第一个报告,查看 UsingString 方法生成的报告。
如下图所示,选择 .Net Memory Allocations 以查看该测试运行过程中分配的内存数额。
根据上图我们可以得出以下结论:
- 在这测试中,有 102M 的内存被分配给 String 。注意,在 dotTrace 中显示的分配是指整个运行过程中全部分配的内存。即使后续被回收,该数值也不会减少。
- 内存的分配只要在 CLR Worker 线程进行。并且非常的密集。
Tip: Timeline 所显示的运行时间比正常运行测试的时间更长,因为在 profile 过程中需要对数据进行记录会有额外的消耗。
因此,我们就得出了第一个结论:使用 string 进行直接拼接,确实会消耗更多的内存分配。
接着,我们继续按照上面的步骤,查看一下 UsingStringBuilder 方法的报告,如下所示:
根据上图,我们可以得出第二个结论:使用 StringBuilder 可以明显的减少相较于 string 直接拼接所消耗的内存。
当然,我们得到的最终的结论其实是:看来面试官不是糊弄人。
class 和 struct 对内存有什么影响
class 和 struct 的区别有很多,面试题常客了。其中,两者在内存方面就存在区别。
那么我们通过一个测试来看看区别。
using System;
using System.Collections.Generic;
using NUnit.Framework; namespace Newbe.DotTrace.Tests
{
public class X02ClassAndStruct
{
[Test]
public void UsingClass()
{
Console.WriteLine($"memory in bytes before execution: {GC.GetGCMemoryInfo().TotalAvailableMemoryBytes}");
const int count = 1_000_000;
var list = new List<Student>(count);
for (var i = 0; i < count; i++)
{
list.Add(new Student
{
Level = int.MinValue
});
} list.Clear(); var gcMemoryInfo = GC.GetGCMemoryInfo();
Console.WriteLine($"heap size: {gcMemoryInfo.HeapSizeBytes}");
Console.WriteLine($"memory in bytes end of execution: {gcMemoryInfo.TotalAvailableMemoryBytes}");
} [Test]
public void UsingStruct()
{
Console.WriteLine($"memory in bytes before execution: {GC.GetGCMemoryInfo().TotalAvailableMemoryBytes}");
const int count = 1_000_000;
var list = new List<Yueluo>(count);
for (var i = 0; i < count; i++)
{
list.Add(new Yueluo
{
Level = int.MinValue
});
} list.Clear(); var gcMemoryInfo = GC.GetGCMemoryInfo();
Console.WriteLine($"heap size: {gcMemoryInfo.HeapSizeBytes}");
Console.WriteLine($"memory in bytes end of execution: {gcMemoryInfo.TotalAvailableMemoryBytes}");
} public class Student
{
public int Level { get; set; }
} public struct Yueluo
{
public int Level { get; set; }
}
}
}
代码要点:
- 两个测试,分别创建 1,000,000 个 class 和 struct 加入到 List 中。
- 运行测试之后,在测试的末尾输出当前堆空间的大小。
按照上一节提供的基础步骤,我们对比两个方法生成的报告。
UsingClass
UsingStruct
对比两个报告,可以得出以下这些结论:
- Timeline 报告中的内存分配,只包含分配在堆上的内存情况。
- struct 不需要分配在堆上,但是,数组是引用对象,需要分配在堆上。
- List 自增的过程本质是扩张数组的特性在报告中也得到了体现。
- 另外,没有展示在报告上,而展示在测试打印文本中可以看到,UsingStruct 运行之后的堆大小也证实了 struct 不会被分配在堆上。
装箱和拆箱
经典面试题 X3,来,上代码,上报告!
using NUnit.Framework; namespace Newbe.DotTrace.Tests
{
public class X03Boxing
{
[Test]
public void Boxing()
{
for (int i = 0; i < 1_000_000; i++)
{
UseObject(i);
}
} [Test]
public void NoBoxing()
{
for (int i = 0; i < 1_000_000; i++)
{
UseInt(i);
}
} public static void UseInt(int age)
{
// nothing
} public static void UseObject(object obj)
{
// nothing
}
}
}
Boxing, 发生装箱拆箱
NoBoxing,未发生装箱拆箱
对比两个报告,可以得出以下这些结论:
- 没有买卖就没有杀害,没有装拆就没有分配消耗。
Thread.Sleep 和 Task.Delay 有什么区别
经典面试题 X4,来,上代码,上报告!
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework; namespace Newbe.DotTrace.Tests
{
public class X04SleepTest
{
[Test]
public Task TaskDelay()
{
return Task.Delay(TimeSpan.FromSeconds(3));
} [Test]
public Task ThreadSleep()
{
return Task.Run(() => { Thread.Sleep(TimeSpan.FromSeconds(3)); });
}
}
}
ThreadSleep
TaskDelay
对比两个报告,可以得出以下这些结论:
- 在 dotTrace 中 Thread.Sleep 会被单独标记,因为这是一种性能不不佳的做法,容易造成线程饥饿。
- Thread.Sleep 比起 Task.Delay 会多出一个线程处于 Sleep 状态
阻塞大量的 Task 真的会导致应用一动不动吗
有了上一步的结论,笔者产生了一个大胆的想法。我们都知道线程的有限的,那如果启动非常多的 Thread.Sleep 或者 Task.Delay 会如何呢?
来,代码:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework; namespace Newbe.DotTrace.Tests
{
public class X04SleepTest
{ [Test]
public Task RunThreadSleep()
{
return Task.WhenAny(GetTasks(50)); IEnumerable<Task> GetTasks(int count)
{
for (int i = 0; i < count; i++)
{
var i1 = i;
yield return Task.Run(() =>
{
Console.WriteLine($"Task {i1}");
Thread.Sleep(int.MaxValue);
});
} yield return Task.Run(() => { Console.WriteLine("yueluo is the only one dalao"); });
}
} [Test]
public Task RunTaskDelay()
{
return Task.WhenAny(GetTasks(50)); IEnumerable<Task> GetTasks(int count)
{
for (int i = 0; i < count; i++)
{
var i1 = i;
yield return Task.Run(() =>
{
Console.WriteLine($"Task {i1}");
return Task.Delay(TimeSpan.FromSeconds(int.MaxValue));
});
} yield return Task.Run(() => { Console.WriteLine("yueluo is the only one dalao"); });
}
}
}
}
这里就不贴报告了,读者可以试一下这个测试,也可以将报告的内容写在本文的评论中参与讨论~
反射调用和表达式树编译调用
有时,我们需要动态调用一个方法。最广为人知的方式就是使用反射。
但是,这也是广为人知的耗时相对较高的方式。
这里,笔者提供一种使用表达式树创建委托来取代反射提高效率的思路。
那么,究竟有没有减少时间消耗呢?好报告,自己会说话。
using System;
using System.Diagnostics;
using System.Linq.Expressions;
using NUnit.Framework; namespace Newbe.DotTrace.Tests
{
public class X05ReflectionTest
{
[Test]
public void RunReflection()
{
var methodInfo = GetType().GetMethod(nameof(MoYue));
Debug.Assert(methodInfo != null, nameof(methodInfo) + " != null");
for (int i = 0; i < 1_000_000; i++)
{
methodInfo.Invoke(null, null);
} Console.WriteLine(_count);
} [Test]
public void RunExpression()
{
var methodInfo = GetType().GetMethod(nameof(MoYue));
Debug.Assert(methodInfo != null, nameof(methodInfo) + " != null");
var methodCallExpression = Expression.Call(methodInfo);
var lambdaExpression = Expression.Lambda<Action>(methodCallExpression);
var func = lambdaExpression.Compile();
for (int i = 0; i < 1_000_000; i++)
{
func.Invoke();
} Console.WriteLine(_count);
} private static int _count = 0; public static void MoYue()
{
_count++;
}
}
}
RunReflection,直接使用反射调用。
RunExpression,使用表达式树编译一个委托。
本篇小结
使用 dotTrace 可以查看方法的内存和时间消耗。本篇所演示的内容只是其中很小的部分。开发者们可以尝试上手,大有裨益。
本篇内容中的示例代码,均可以在以下链接仓库中找到:
最后但是最重要!
如果读者对该内容感兴趣,欢迎转发、评论、收藏文章以及项目。
最近作者正在构建以反应式
、Actor模式
和事件溯源
为理论基础的一套服务端开发框架。希望为开发者提供能够便于开发出 “分布式”、“可水平扩展”、“可测试性高” 的应用系统 ——Newbe.Claptrap
本篇文章是该框架的一篇技术选文,属于技术构成的一部分。
联系方式:
- Github Issue
- Gitee Issue
- 公开邮箱 newbe-claptrap@googlegroups.com (发送到该邮箱的内容将被公开)
- Gitter
- QQ 群 610394020
您还可以查阅本系列的其他选文:
理论入门篇
术语介绍篇
- Actor 模式
- 事件溯源(Event Sourcing)
- Claptrap
- Minion
- 事件 (Event)
- 状态 (State)
- 状态快照 (State Snapshot)
- Claptrap 设计图 (Claptrap Design)
- Claptrap 工厂 (Claptrap Factory)
- Claptrap Identity
- Claptrap Box
- Claptrap 生命周期(Claptrap Lifetime Scope)
- 序列化(Serialization)
实现入门篇
- Newbe.Claptrap 框架入门,第一步 —— 创建项目,实现简易购物车
- Newbe.Claptrap 框架入门,第二步 —— 简单业务,清空购物车
- Newbe.Claptrap 框架入门,第三步 —— 定义 Claptrap,管理商品库存
- Newbe.Claptrap 框架入门,第四步 —— 利用 Minion,商品下单
样例实践篇
其他番外篇
- 谈反应式编程在服务端中的应用,数据库操作优化,从 20 秒到 0.5 秒
- 谈反应式编程在服务端中的应用,数据库操作优化,提速 Upsert
- 十万同时在线用户,需要多少内存?——Newbe.Claptrap 框架水平扩展实验
- docker-mcr 助您全速下载 dotnet 镜像
- 十多位全球技术专家,为你献上近十个小时的.Net 微服务介绍
- 年轻的樵夫哟,你掉的是这个免费 8 核 4G 公网服务器,还是这个随时可用的 Docker 实验平台?
- 如何使用 dotTrace 来诊断 netcore 应用的性能问题
GitHub 项目地址:https://github.com/newbe36524/Newbe.Claptrap
Gitee 项目地址:https://gitee.com/yks/Newbe.Claptrap
您当前查看的是先行发布于 www.newbe.pro 上的博客文章,实际开发文档随版本而迭代。若要查看最新的开发文档,需要移步 claptrap.newbe.pro。
如何使用 dotTrace 来诊断 netcore 应用的性能问题的更多相关文章
- 用DotTrace 来分析.NET-Core程序
1. 前言 看园子里面讲dotTrace 的文章不多,最近也有这方面的需要,于是去搜索了一下,.NET 性能分析方面的工具.目的呢,主要是想发现我的代码中,哪些代码占用了最多时间,来进行优化.主要 ...
- 【性能诊断】十一、性能问题综合分析(案例2,windbg、wireshark)
[问题描述]: 前段时间有一项目反馈,常用的审批功能有时的响应较慢,多个管理员功能不定期的出现客户端无响应的状况,并且管理员功能一旦出现卡死,也会影响到普通的业务用户致使很多用户无法操作. ...
- 【性能诊断】十、性能问题综合分析(案例1,windbg、Network Monitor)
[问题描述]: 产品中某业务功能A,在进行"刷新"->选择制单->新增->切换其他行等一系列操作后,突然发生客户端不响应的现象. 经反复测 ...
- 使用 Tye 辅助开发 k8s 应用竟如此简单(一)
最近正巧在进行 Newbe.Claptrap 新版本的开发,其中使用到了 Tye 来辅助 k8s 应用的开发.该系列我们就来简单了解一下其用法. Newbe.Claptrap 是一个用于轻松应对并发问 ...
- 使用 Tye 辅助开发 k8s 应用竟如此简单(二)
续上篇,这篇我们来进一步探索 Tye 更多的使用方法.本篇我们来了解一下如何在 Tye 中使用服务发现. Newbe.Claptrap 是一个用于轻松应对并发问题的分布式开发框架.如果您是首次阅读本系 ...
- 使用 Tye 辅助开发 k8s 应用竟如此简单(三)
续上篇,这篇我们来进一步探索 Tye 更多的使用方法.本篇我们来了解一下如何在 Tye 中如何对数据库进行链接. Newbe.Claptrap 是一个用于轻松应对并发问题的分布式开发框架.如果您是首次 ...
- 使用 Tye 辅助开发 k8s 应用竟如此简单(四)
续上篇,这篇我们来进一步探索 Tye 更多的使用方法.本篇我们来了解一下如何在 Tye 中如何进行日志的统一管理. Newbe.Claptrap 是一个用于轻松应对并发问题的分布式开发框架.如果您是首 ...
- 使用 Tye 辅助开发 k8s 应用竟如此简单(五)
续上篇,这篇我们来进一步探索 Tye 更多的使用方法.本篇我们来了解一下如何在 Tye 中实现对分布式链路追踪. Newbe.Claptrap 是一个用于轻松应对并发问题的分布式开发框架.如果您是首次 ...
- 使用 Tye 辅助开发 k8s 应用竟如此简单(六)
续上篇,这篇我们来进一步探索 Tye 更多的使用方法.本篇我们将进一步研究 Tye 与分布式应用程序运行时 Dapr 如何碰撞出更精彩的火花. Newbe.Claptrap 是一个用于轻松应对并发问题 ...
随机推荐
- 这应该是最适合国内用户的K3s HA方案
前 言 在面向生产环境的实践中,高可用是我们无法避免的问题,K3s本身也历经多个版本的迭代,HA方案也进行了不断优化,形成了目前的比较稳定的HA方案. 目前官方提供两种HA方案: 嵌入式DB的高可用( ...
- 【好文分享】为什么强烈禁止开发人员使用isSuccess作为变量名
原文来自阿里云hollies:https://developer.aliyun.com/article/701413 简介: 在日常开发中,我们会经常要在类中定义布尔类型的变量,比如在给外部系统提 ...
- Python文件.py转换为.exe可执行程序,制作.exe文件图标
当大家想要将自己写的Python程序对别人进行展示的时候,你是否还是打开你的Pycharm进行运行展示? 假如是专业的人士看你的代码,一眼就能看懂你的代码,而其实我们可以不需要给代码,利用pyinst ...
- Activiti7 生成表结构
首先创建一个Maven项目 整体的项目结构 activiti.cfg.xml配置文件 <?xml version="1.0" encoding="UTF-8&quo ...
- SqlAnalyzer1.01 源码
源码下载:https://files.cnblogs.com/files/heyang78/SqlAnalyzer-20200529-2.rar 现有功能:不带函数允许嵌套的select ...fro ...
- ZT:如果有来生,要做一棵树
出处:https://zhidao.baidu.com/question/393644025.html 原以为是三毛所作,想不到还有争议. 如果有来生,要做一棵树, 站成永恒.没有悲欢的姿势, 一半在 ...
- TIKTOK 美国制裁
今天在B站上看了沈教授和李自然说关于TIKTOK对于美国的声明采取的做法的一些看法.其实对于他们的看法,我觉得没有对错之分.正像两个新发的观点,在没有得到历史的验证前,谁也不会承认谁错了.更多的是两个 ...
- @RequestBody使用说明
@RequestBody 使用 @RequestBody这个对于一般刚接触来说,确实有点陌生,但是现在前端,后端技术分的太细,越来越多的技术层出不穷,前端就分化出POST ,GET,PUT,DELET ...
- swift基本体验
Swift初体验 1. 导入框架 2. 定义标识符: let/var 3. 语句结束时;可以省略 4. print() 5. let/var 6. 逻辑分支 7. 循环使用 // 1.Swift中如何 ...
- Docker数据卷Volume实现文件共享、数据迁移备份(三)
数据卷volume功能特性 数据卷 是一个可供一个或多个容器使用的特殊目录,实现让容器中的一个目录和宿主机中的一个文件或者目录进行绑定.数据卷 是被设计用来持久化数据的对于数据卷你可以理解为NFS中的 ...