经常这样我发现自己对小块代码进行基准测试,看看哪个实现最快.
我经常看到基准测试代码没有考虑到jitting或垃圾收集器的评论.
我有以下简单的基准测试功能,我已经慢慢演变了:
static void Profile(string description, int iterations, Action func) {
// warm up
func();
// clean up
GC.Collect();
var watch = new Stopwatch();
watch.Start();
for (int i = 0; i < iterations; i++) {
func();
}
watch.Stop();
Console.Write(description);
Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
}
Run Code Online (Sandbox Code Playgroud)
用法:
Profile("a descriptions", how_many_iterations_to_run, () =>
{
// ... code being profiled
});
Run Code Online (Sandbox Code Playgroud)
这个实现是否有任何缺陷?是否足以表明实现X比Z迭代实现Y更快?您能想出任何可以改善这种情况的方法吗?
编辑 很明显,基于时间的方法(与迭代相反)是首选,是否有人有任何实施时间检查不会影响性能?
请参阅以下并发性能分析,该分析表示并行foreach所完成的工作:

在循环内部,每个线程从DB读取数据并对其进行处理.线程之间没有锁,因为每个处理不同的数据.
由于未知原因,看起来foreach的所有线程中都存在周期性锁定(请参阅黑色垂直矩形).如果您看到所选的锁定段(深红色段),您将看到堆栈显示在StockModel.Quotation构造函数处锁定的线程.那里的代码只构造了两个空列表!
我在某处读过这可能是由GC引起的所以我已经将垃圾收集更改为在服务器模式下运行:
<runtime>
<gcServer enabled="true"/>
</runtime>
Run Code Online (Sandbox Code Playgroud)
我得到了一个小的改进(大约10% - 快15%),但我仍然有垂直锁定.
我还在所有数据库查询中添加了WITH(NOLOCK),因为我只读数据而没有任何区别.
有什么暗示这里发生的事情?
分析完成的计算机有8个核心.
编辑:启用Microsoft Symbol服务器后,所有线程都在wait_gor_gc_done或WaitUntilGCComplete等调用上被阻止.我认为启用GCServer我为每个线程都有一个GC,所以我会避免"垂直"锁定,但似乎并非如此.我错了吗?
第二个问题:由于机器没有内存压力(使用8个演出中的5个)有没有办法延迟GC执行或暂停它直到并行foreach结束(或将其配置为不经常发射)?
有没有办法让GC完全保留一段代码?我在其他类似问题中找到的唯一的事情是GC.TryStartNoGCRegion它只限于你指定的内存量本身仅限于短暂段的大小.
有没有办法完全绕过它并告诉.NET "分配你需要的任何东西,不做GC期间"或增加段的大小?从我发现它在许多核心服务器上最多1GB,这比我需要分配的少,但我不希望GC发生(我有多达TB的空闲RAM,并且有数千个GC峰值在那一节中,我非常乐意将这些内容换成RAM使用量的10倍甚至100倍.
现在有了赏金,我认为如果我指定用例就更容易了.我正在使用LINQ to XML加载和解析一个非常大的XML文件(现在1GB,很快就会12GB)到内存中的对象.我不是在寻找替代方案.我正在创建数以百万计的数百万个小对象,XElements并且GC正试图不间断地收集,而我很高兴保持所有RAM用完.我有100英镑的内存,一旦它达到4GB使用,GC开始收集不间断,这是非常友好的内存,但性能不友好.我不关心记忆但我关心表现.我想采取相反的权衡.
虽然我不能在这里发布实际代码是一些非常接近最终代码的示例代码,可以帮助那些要求更多信息的人:
var items = XElement.Load("myfile.xml")
.Element("a")
.Elements("b") // There are about 2 to 5 million instances of "b"
.Select(pt => new
{
aa = pt.Element("aa"),
ab = pt.Element("ab"),
ac = pt.Element("ac"),
ad = pt.Element("ad"),
ae = pt.Element("ae")
})
.Select(pt => new
{
aa = new
{
aaa = double.Parse(pt.aa.Attribute("aaa").Value),
aab = double.Parse(pt.aa.Attribute("aab").Value),
aac = double.Parse(pt.aa.Attribute("aac").Value),
aad = double.Parse(pt.aa.Attribute("aad").Value),
aae = double.Parse(pt.aa.Attribute("aae").Value)
},
ab = new
{
aba = …Run Code Online (Sandbox Code Playgroud) 我正在研究将相对较小的服务从C++重写为C#的可能性.该服务有两个主要功能:
实时任务由一个单独的库处理,该库执行自己的线程,并且根本不与服务的其余部分进行交互.服务的其余部分每5分钟左右从HTTP请求中获取一些数据.
问题是,由于实时部分有很长的期限,我无法真正容忍库的线程上的GC暂停.在我自己的代码方面,GC应该有足够的时间在Web请求之间运行,但我不能容忍它在我尝试将数据提供给库时启动.
我发现我可以创建一个垃圾收集器不会开始使用的关键部分GC.TryStartNoGCRegion(),这解决了一半的问题.
但是,我仍然不知道是否有办法告诉.NET GC单独留下不运行托管代码的特定线程.那可能吗?
我的应用程序分配了大量的内存(数百万个小对象,总计几千兆字节)并且长时间保留它.
更新:Perf计数器"GC中的%时间"显示平均值为10.6%.
程序员是否有可能以C#编程语言以编程方式启动/停止垃圾收集?例如,用于性能优化等.
渲染框架时,我的C#应用程序中出现了意外的峰值。我在探查器中进行了遍历,发现以下内容:
在上面,增量时间为21毫秒(每秒60帧超过16.6毫秒),并在游戏中造成明显的停顿。甚至比这更糟,因为它仅在它之后才开始渲染(如您之后所看到的实心矩形),所以这21ms是一个谎言,根据该图,它更像是40ms,这太可怕了。
由于我不了解的原因,当打开vsync时,这种巨大差距永远不会发生。对于不熟悉vsync和游戏的用户,您不能在第一人称射击游戏中使用vsync,因为由于它的工作原理,它会削弱输入处理,因此我无法使用vsync,并且必须研究非vsync版本。我需要找到为什么非vsync版本具有这些主要停滞点的原因。
我的问题是,如何知道您从上面看到的图像中导致此延迟的原因?
性能分析器表示,这里有大量等待时间,超过80%的等待时间需要20%的CPU使用率(相比之下,准备渲染数据时需要100%的CPU使用率)。
探查器还显示该循环中没有运行任何渲染代码...这很奇怪,因为渲染器几乎完全控制了性能,因此,如果每秒不对帧进行任何上限,则应该使用纯蓝色淹没整个图形长方形。
问题在于,探查器显示的代码只是调用我在上面选择的区域中的轮询输入和dll:
请注意,的另一个调用DispatchRenderFrame是进行OpenGL调用,当我完全删除它时,它对程序没有影响,因此您可以忽略它。这可能意味着下面看到的用户输入也不会影响微笔画问题...但是我无法删除它,因为它是我用于窗口管理(OpenTK)的库的一部分。
我不确定CLR Worker线程是什么或正在做什么。它也发生在vsync一个(希望平滑分析一个)中,因此即使我不知道它是否是罪魁祸首,我猜也可能不是,但我不确定,因为它显示在“所需的vsync示例”也位于同一常规位置。
是否发生了一些中断,操作系统正在接管但未恢复我的线程,因为它被归类为CPU吞噬?只是一个想法...但是在示例中再次显示了蓝色条,因此我认为该Main线程实际上并未在我突出显示的内容中处于睡眠状态,并且实际上正在运行?
如您所见,我不确定突出显示的时间段是在告诉我什么,这是我需要帮助的地方。我不知道为什么此时代码中的等待百分比如此之高,或者从这里何处去进一步诊断问题。
编辑:我对Stopwatch类进行了一些粗略的剖析,以查看导致尖峰的位置,并且尖峰仅来自计算部分。OpenGL调用均不会导致任何延迟。它纯粹是在执行数学运算和访问数据的功能的函数内部发生的,或者写入预先分配的结构数组中。
额外说明:
这也让我好奇是否有办法让我强制使用,以使C#虚拟机获得尽可能多的CPU使用率?
没有产生垃圾。这不是 GC问题。我不能在申请期间运行GC,所以不会产生垃圾。渲染函数中的所有内容都是堆栈上的所有结构,唯一一次进入堆管理对象的是池化数组,其大小足以容纳所有渲染数据。它在探查器中出现的唯一时间是在开始和结束时,但是在渲染阶段不会运行。
我已经完成了我的作业,并且一再保证,无论是在for循环内部还是外部声明变量,它在性能上都没有区别,它实际上编译为完全相同的MSIL.但是我一直在弄乱它,并发现在循环中移动变量声明确实会导致相当大的一致性能增益.
我编写了一个小型控制台测试类来测量这种效果.我初始化一个静态double[]数组项,两个方法对它执行循环操作,将结果写入静态double[]数组缓冲区.最初,我的方法是那些我注意到差异的方法,即复数的大小计算.对于长度为1000000 的项目数组运行这些100次,我得到的变量(6个double变量)在循环内的运行时间一直较低:例如,32,83±0,64 ms v 43,44±使用Intel Core 2 Duo @ 2.66 GHz的老式配置为0.45 ms.我尝试以不同的顺序执行它们,但它没有影响结果.
然后我意识到计算复数的大小远非最小的工作示例,并测试了两个更简单的方法:
static void Square1()
{
double x;
for (int i = 0; i < buffer.Length; i++) {
x = items[i];
buffer[i] = x * x;
}
}
static void Square2()
{
for (int i = 0; i < buffer.Length; i++) {
double x;
x = items[i];
buffer[i] = x * x;
}
}
Run Code Online (Sandbox Code Playgroud)
有了这些,结果就出现了另一种方式:在循环外声明变量似乎更有利:对于Square1() …
我正在分析代码的关键部分的时间花费.
//Take a timestamp before
var before = DateTime.UtcNow;
DoCriticalMethod();
//Take a timestamp after
var after = DateTime.UtcNow;
Run Code Online (Sandbox Code Playgroud)
有时我的某些价值明显高于其他价值.
我怀疑它是垃圾收集,所以我想将高值与在进程中发生垃圾收集的事实相关联.
到目前为止我试过这个:我之前拿了一个柜台:
//Take the number before
int gc2CountBefore = GC.CollectionCount(2);
...
//Take the number after
bool hasgc2occured = (GC.CollectionCount(2) - gc2CountBefore) != 0;
Run Code Online (Sandbox Code Playgroud)
我这样做的好吗?
我只是在阅读几种编程语言的性能,我注意到垃圾收集经常被提及。垃圾收集是否会影响语言的性能?如果是,如何?
我的金融软件不断处理几乎相同的对象.例如我在线有这样的数据:
HP 100 1
HP 100 2
HP 100.1 1
etc.
Run Code Online (Sandbox Code Playgroud)
我每秒钟大约有1000次更新.
每个更新都存储在对象中 - 但我不想动态分配这些对象以改善延迟.我只在很短的时间内使用物品 - 我收听它们,申请和免费.一旦对象空闲,它实际上可以重用于另一个数据包.
所以我需要一些存储(可能是环形缓冲区),它可以分配一次所需数量的对象,它们可以"获取"并"释放"它们.在c#中这样做的最佳方法是什么?
每个对象都有id,我id's按顺序分配并释放它们sequentially.例如我收到的ID 1 2和3,然后我自由1,2,3.所以任何FIFO集合都可以工作,但我正在寻找一些涵盖所需功能的库类.
即我需要FIFO集合,它不分配对象,但重用它们并允许重新配置它们.
UPD
我添加了我想要的实现.这不是经过测试的代码,可能有错误.想法很简单.作家应该调用Obtain Commit方法.读者应该调用TryGet方法.读者和编写者可以从不同的线程访问此结构:
public sealed class ArrayPool<T> where T : class
{
readonly T[] array;
private readonly uint MASK;
private volatile uint curWriteNum;
private volatile uint curReadNum;
public ArrayPool(uint length = 1024) // length must be power of 2 …Run Code Online (Sandbox Code Playgroud)