C# 数组(结构类型)上的何处进行了优化以避免不必要地复制结构值?

C# 数组(结构类型)上的何处进行了优化以避免不必要地复制结构值?,c#,arrays,performance,linq,C#,Arrays,Performance,Linq,出于内存性能方面的原因,我有一个结构数组,因为项目的数量很大,而且项目会被定期抛出,从而影响GC堆。这不是我是否应该使用大型结构的问题;我已经确定GC垃圾导致了性能问题。我的问题是,当我需要处理这个结构数组时,我应该避免使用LINQ吗?由于结构不小,按值传递它是不明智的,我不知道LINQ代码生成器是否足够聪明,可以这样做。结构如下所示: public struct ManufacturerValue { public int ManufacturerID; public stri

出于内存性能方面的原因,我有一个结构数组,因为项目的数量很大,而且项目会被定期抛出,从而影响GC堆。这不是我是否应该使用大型结构的问题;我已经确定GC垃圾导致了性能问题。我的问题是,当我需要处理这个结构数组时,我应该避免使用LINQ吗?由于结构不小,按值传递它是不明智的,我不知道LINQ代码生成器是否足够聪明,可以这样做。结构如下所示:

public struct ManufacturerValue
{
    public int ManufacturerID;
    public string Name;
    public string CustomSlug;
    public string Title;
    public string Description;
    public string Image;
    public string SearchFilters;
    public int TopZoneProduction;
    public int TopZoneTesting;
    public int ActiveProducts;
}
var dict = new Dictionary<string, int>();
for (var i = 0; i < values.Length; i++) {
    if (!string.IsNullOrEmpty(values[i].CustomSlug)) {
        dict.Add(values[i].CustomSlug, values[i].ManufacturerID);
    }
}
假设我们有一个这些值的数组,我想提取一个自定义段塞的字典到制造商ID。在我将其更改为结构之前,它是一个类,因此原始代码是用一个简单的LINQ查询编写的:

ManufacturerValue[] = GetManufacturerValues();
var dict = values.Where(p => !string.IsNullOrEmpty(p.CustomSlug))
                 .ToDictionary(p => p.CustomSlug, p => p.ManufacturerID);
我关心的是我想了解LINQ将如何生成构建此字典的实际代码。我的怀疑是,LINQ代码在内部会以这种幼稚的实现而告终:

var dict = new Dictionary<string, int>();
for (var i = 0; i < values.Length; i++) {
    var value = values[i];
    if (!string.IsNullOrEmpty(value.CustomSlug)) {
        dict.Add(value.CustomSlug, value.ManufacturerID);
    }
}
var dict=newdictionary();
对于(变量i=0;i
这将是不好的,因为第三行将创建结构的本地副本,这将是缓慢的,因为结构很大,而会破坏内存总线。我们也不需要任何东西,只需要ID和来自它的定制slug,所以它会在每次迭代中复制大量无用的信息。如果我自己能有效地编码,我会这样写:

public struct ManufacturerValue
{
    public int ManufacturerID;
    public string Name;
    public string CustomSlug;
    public string Title;
    public string Description;
    public string Image;
    public string SearchFilters;
    public int TopZoneProduction;
    public int TopZoneTesting;
    public int ActiveProducts;
}
var dict = new Dictionary<string, int>();
for (var i = 0; i < values.Length; i++) {
    if (!string.IsNullOrEmpty(values[i].CustomSlug)) {
        dict.Add(values[i].CustomSlug, values[i].ManufacturerID);
    }
}
var dict=newdictionary();
对于(变量i=0;i
那么,是否有人知道代码生成器是否足够聪明,可以像第二个示例那样使用简单的数组索引,当生成器代码在结构数组上运行时,还是会实现更简单但更慢的第一个实现

反编译这类代码的最佳方法是什么,以了解代码生成器对此的实际操作

更新

这些变化现在正在生产中。事实证明,在重新编写代码和使用点内存分析器来确定使用了多少内存以及在哪里的过程中,我在Phalanger PHP编译器代码中发现了两个内存泄漏。这就是我们的进程使用的内存量不断增长的原因之一,其中一个内存泄漏非常严重,实际上是由Microsoft异步代码引起的(可能值得一个博客或一个堆栈溢出问题/答案来帮助其他人避免它)

无论如何,一旦我发现内存泄漏并修复了它们,我就在没有任何内存优化的情况下推动代码从类转换为结构,奇怪的是,这实际上导致了GC更大的波动。根据性能计数器,我看到GC将使用高达27%的CPU。最有可能的是,由于内存泄漏,这些大数据块以前没有得到GC'ed,所以它们只是挂起。一旦代码被修复,GC的表现就比以前更糟糕了


最后,我们完成了使用这个问题中的反馈将这些类转换为结构的代码,现在我们在峰值时的总内存使用量大约是原来的50%,当服务器上的负载消失时,它会迅速下降,更重要的是,我们看到只有0.05%的CPU用于GC,即使是这样。因此,如果有人想知道这些更改是否会对现实世界产生影响,他们真的会,特别是如果您的对象通常会挂起一段时间,因此被卡在第二代堆中,然后需要被丢弃和垃圾收集。

如果您需要稍微放松垃圾收集器,您可能希望使用app.config文件中的选项:

<configuration>
    <runtime>
        <gcServer enabled="true" />
    </runtime>
</configuration>

查看基于LINQ代码生成何种IL是一个很好的工具

不幸的是,我不知道如何对结构的枚举使用LINQ。我通常使用结构来保留少量的值类型


也许放松GC可以帮助您避免性能问题,并给类另一个机会?我还有一个应用程序,它可以进行大量的对象创建和处理,其中的性能受到GC狂热的困扰。使用GCServer=“true”解决了这个问题,交换使用的私有内存略有增加。

结构是[按值传递][1]-因此我相当确定,无论发生什么情况,只要为
ToDictionary
使用委托,就会产生两个副本

换句话说,考虑

.ToDictionary(p => p.CustomSlug, p => p.ManufacturerID);
相当于:

var key = GetKey(values[i]);
var value = GetValue(values[i]);

.ToDictionary(key, value);
这显然会创建结构的两个副本,以传递给
GetKey
GetValue

反编译这类代码的最佳方法是什么,以了解代码生成器对此的实际操作

不需要反编译代码。所有LINQ到对象方法的实现都可以在上看到

关于你的具体问题。使用LINQ时,您可能会遇到大量的
struct
复制操作(通常是基于
IEnumerable
Func
的方法)

例如,
IEnumerator
的当前元素通过如下定义的属性
current
访问

T Current { get; }
因此,访问至少涉及一个副本。但是枚举器实现通常在
MoveNext
方法期间将当前元素存储到字段中,因此我认为您可以安全地计算2次复制操作

当然,每个
Func
都会导致另一个副本,因为
T
是输入参数

public struct ManufacturerValue
{
    public int ManufacturerID;
    public string Name;
    public string CustomSlug;
    public string Title;
    public string Description;
    public string Image;
    public string SearchFilters;
    public int TopZoneProduction;
    public int TopZoneTesting;
    public int ActiveProducts;
}

public struct ManufacturerValueRef
{
    public readonly ManufacturerValue[] Source;
    public readonly int Index;
    public ManufacturerValueRef(ManufacturerValue[] source, int index) { Source = source; Index = index; }
    public int ManufacturerID => Source[Index].ManufacturerID;
    public string Name => Source[Index].Name;
    public string CustomSlug => Source[Index].CustomSlug;
    public string Title => Source[Index].Title;
    public string Description => Source[Index].Description;
    public string Image => Source[Index].Image;
    public string SearchFilters => Source[Index].SearchFilters;
    public int TopZoneProduction => Source[Index].TopZoneProduction;
    public int TopZoneTesting => Source[Index].TopZoneTesting;
    public int ActiveProducts => Source[Index].ActiveProducts;
}

public static partial class Utils
{
    public static IEnumerable<ManufacturerValueRef> AsRef(this ManufacturerValue[] values)
    {
        for (int i = 0; i < values.Length; i++)
            yield return new ManufacturerValueRef(values, i);
    }
}
var dict = values.AsRef()
    .Where(p => !string.IsNullOrEmpty(p.CustomSlug))
    .ToDictionary(p => p.CustomSlug, p => p.ManufacturerID);
p => !string.IsNullOrEmpty(p.CustomSlug)
p => p.CustomSlug
p => p.ManufacturerID
ManufacturerValue[] values = GetManufacturerValues();
var dict = Enumerate.Range(0, values.Length)
  .Where(i => !string.IsNullOrEmpty(values[i].CustomSlug))
  .ToDictionary(i => values[i].CustomSlug, i => values[i].ManufacturerID);