在分析项目时,我注意到我们有一个用于检查空
IEnumerable<T>
的扩展方法,并且它被意外地用在 string
上,而不是特定于字符串的扩展方法。我编写了一个小程序来确定 IEnumerable<T>
版本是否可能正在分配,而且似乎是这样,但是当我发现基本上我编写的用于检查字符串是否为空或空的任何方法都在分配时,我遇到了意想不到的结果。仅使用 string.IsNullOrEmpty(str)
才能阻止分配。有谁知道为什么我的所有方法都将内存分配到堆?仅仅是因为使用了函数吗?如果我避免调用函数,它不会分配内存。
这是演示所有这些的程序:
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
string myStr = "Blah";
long startMemory = GC.GetTotalMemory(true);
bool empty = IEnumerableExtensions.IsNullOrEmpty(myStr);
long memoryUsed = GC.GetTotalMemory(false) - startMemory;
// memory used: 8192
Console.WriteLine($"IEnumerableExtensions.IsNullOrEmpty, memory used: {memoryUsed}");
startMemory = GC.GetTotalMemory(true);
empty = StringExtensions.IsNullOrEmpty(myStr);
memoryUsed = GC.GetTotalMemory(false) - startMemory;
// memory used: 4040
Console.WriteLine($"StringExtensions.IsNullOrEmpty, memory used: {memoryUsed}");
startMemory = GC.GetTotalMemory(true);
empty = string.IsNullOrEmpty(myStr);
memoryUsed = GC.GetTotalMemory(false) - startMemory;
// memory used: 0
Console.WriteLine($"string.IsNullOrEmpty, memory used: {memoryUsed}");
startMemory = GC.GetTotalMemory(true);
empty = IsNullOrEmpty(myStr);
memoryUsed = GC.GetTotalMemory(false) - startMemory;
// memory used: 4040
Console.WriteLine($"IsNullOrEmpty, memory used: {memoryUsed}");
startMemory = GC.GetTotalMemory(true);
empty = (myStr == null || myStr.Length == 0);
memoryUsed = GC.GetTotalMemory(false) - startMemory;
// memory used: 0
Console.WriteLine($"Direct (no function call), memory used: {memoryUsed}");
}
// Non-extension method
private static bool IsNullOrEmpty(string str)
{
return str == null || str.Length == 0;
}
}
public static class IEnumerableExtensions
{
public static bool IsNullOrEmpty<T>(this IEnumerable<T> enumerable)
{
if (enumerable != null)
{
return !enumerable.Any();
}
return true;
}
}
public static class StringExtensions
{
public static bool IsNullOrEmpty(this string str)
{
return str == null || str.Length == 0;
}
}
如果您想亲自查看该程序,可以运行以下 DotNetFiddle 版本:https://dotnetfiddle.net/2Zf0co
GC.GetTotalMemory
在这里使用并不是一个很好的方法。请注意,当第一次调用某些函数时,JIT 会将其编译为本机代码,因此应用程序内存使用量将会增加。将您的代码移动到某个方法中并调用它几次,您就会看到差异。
或者切换到
GC.GetTotalAllocatedBytes(true)
而不是 GetTotalMemory
。
例如,对我来说,第二次调用的结果是:
IEnumerableExtensions.IsNullOrEmpty, memory used: 16384
string.IsNullOrEmpty, memory used: 0
StringExtensions.IsNullOrEmpty, memory used: 0
IsNullOrEmpty, memory used: 0
附注
一般来说(如评论中所述),这不是评估分配内存的完全可靠的方法。您也可以考虑使用
BenchmarkDotNet
与 MemoryDiagnoser