这里的主要问题是你想衡量什么样的影响?更具体地说:您的目标指标是什么?以下是一些示例:性能指标、内存流量、内存占用。
在 BenchmarkDotNet Allocated 列中,您可以获得内存流量。string.Intern
在您的示例中无助于优化它,每次(i % 10).ToString()
调用都会分配一个新字符串。因此,预计 BenchmarkDotNet 在 Allocated 列中显示相同的数字。
但是,string.Intern
最终应该可以帮助您优化应用程序的内存占用(总托管堆大小,可以通过 获取GC.GetTotalMemory()
)。可以使用没有 BenchmarkDotNet 的简单控制台应用程序进行验证:
using System;
namespace ConsoleApp24
{
class Program
{
private const int Count = 100000;
private static string[] notInterned, interned;
static void Main(string[] args)
{
var memory1 = GC.GetTotalMemory(true);
notInterned = NotInterned();
var memory2 = GC.GetTotalMemory(true);
interned = Interned();
var memory3 = GC.GetTotalMemory(true);
Console.WriteLine(memory2 - memory1);
Console.WriteLine(memory3 - memory2);
Console.WriteLine((memory2 - memory1) - (memory3 - memory2));
}
public static string[] NotInterned()
{
var a = new string[Count];
for (var i = Count; i-- > 0;)
{
a[i] = GetString(i);
}
return a;
}
public static string[] Interned()
{
var a = new string[Count];
for (var i = Count; i-- > 0;)
{
a[i] = string.Intern(GetString(i));
}
return a;
}
private static string GetString(int i)
{
var result = (i % 10).ToString();
return result;
}
}
}
在我的机器(Linux、.NET Core 3.1)上,我得到了以下结果:
802408
800024
2384
第一个数字和第二个数字是这两种情况的内存占用影响。它非常大,因为字符串数组消耗大量内存来保存对所有字符串实例的引用。
第三个数字是 interned 和 not interned string 的足迹影响之间的足迹差异。你可能会问为什么它这么小。这很容易解释:Stephen Toub 在dotnet/coreclr#18383中为单个数字字符串实现了特殊缓存,在他的博客文章中有描述:
"0"
因此,在 .NET Core 上测量.."9"
字符串的实习是没有意义的。我们可以很容易地修改我们的程序来解决这个问题:
private static string GetString(int i)
{
var result = "x" + (i % 10).ToString();
return result;
}
以下是更新的结果:
4002432
800344
3202088
现在影响差异(第三个数字)非常大(3202088)。这意味着实习帮助我们在托管堆中节省了 3202088 个字节。
因此,对于您未来的实验,有最重要的建议:
- 仔细定义您实际想要衡量的指标。不要说“我要查找各种受影响的指标”,源代码的任何更改都可能影响数百个不同的指标;在每个实验中都很难测量所有这些。仔细考虑什么样的指标对你来说真的很重要。
- 尝试获取接近您实际工作场景的输入数据。使用一些“虚拟”数据进行基准测试可能会导致不正确的结果,因为运行时有太多棘手的优化可以很好地处理这种“虚拟”情况。