如何使用 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 是一个用于轻松应对并发问题 ...
随机推荐
- 删除MBR分区如何使用光盘恢复
1.备份MBR分区表 dd if=/dev/sda of=/data/mbr.bak bs=1 count=64 skip=446 分区表前512字节分为三部分,第一部分446字节与启动相关 ...
- 在C++/CLI环境下,千万不要把普通全局函数当标准C/C++的函数指针传递给native的库使用
先上一个简单代码: #include <cstdlib> #include <cstdio> // native apis extern "C" { typ ...
- POJ-2299-Ultra-QuickSort(单点更新 + 区间查询+离散化)
In this problem, you have to analyze a particular sorting algorithm. The algorithm processes a seque ...
- HDOJ 1003
动态规划一直AC不了竟然是因为一厢情愿的多加了一个#! printf("Case #%d:\n",count); --------------------------------- ...
- 深入Spring Security-获取认证机制核心原理讲解
文/朱季谦 本文基于Springboot+Vue+Spring Security框架而写的原创笔记,demo代码参考<Spring Boot+Spring Cloud+Vue+Element项目 ...
- Linq 下的扩展方法太少了,您期待的 MoreLinq 来啦
一:背景 1. 讲故事 前几天看同事在用 linq 给内存中的两个 model 做左连接,用过的朋友都知道,你一定少不了一个叫做 DefaultIfEmpty 函数,这玩意吧,本来很流畅的 from. ...
- 测试JsonAnalyzer解析Json的十一个测试用例
目测以下测试用例都是通过的. 01. 原文={"status":"","message":"success"," ...
- 转贴:修改springboot控制台输出的图案
Post from:https://blog.csdn.net/WXN069/article/details/90667668 修改springboot控制台输出的图案1.在src\main\reso ...
- InnoDB 引擎中的索引类型
首先索引是一种数据结构,并且索引不是越多越好.合理的索引可以提高存储引擎对数据的查询效率. 形象一点来说呢,索引跟书本的目录一样,能否快速的查找到你需要的信息,取决于你设计的目录是否合理. MySQL ...
- 循环删除list的方法
错误的方法: 正确的方法: