欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

.NET性能優(yōu)化之為結(jié)構(gòu)體數(shù)組使用StructLinq的問題解析

 更新時間:2022年05月11日 09:31:30   作者:InCerry  
這篇文章主要介紹了.NET性能優(yōu)化為結(jié)構(gòu)體數(shù)組使用StructLinq,本系列的主要目的是告訴大家在遇到性能問題時,有哪些方案可以去優(yōu)化;并不是要求大家一開始就使用這些方案來提升性能,需要的朋友可以參考下

前言

本系列的主要目的是告訴大家在遇到性能問題時,有哪些方案可以去優(yōu)化;并不是要求大家一開始就使用這些方案來提升性能。
在之前幾篇文章中,有很多網(wǎng)友就有一些非此即彼的觀念,在實際中,處處都是開發(fā)效率和性能之間取舍的藝術(shù)?!队嬎銠C編程藝術(shù)》一書中提到過早優(yōu)化是萬惡之源,在進行性能優(yōu)化時,你必須要問自己幾個問題,看需不要進行性能優(yōu)化。

  • 優(yōu)化的成本高么?
  • 如果立刻開始優(yōu)化會帶來什么影響?
  • 因為對任務(wù)目標的影響或是興趣等其他原因而關(guān)注這個問題?
  • 任務(wù)目標影響有多大?
  • 隨著硬件性能提升或者框架版本升級,優(yōu)化的結(jié)果會不會過時?
  • 如果不進行優(yōu)化或延遲優(yōu)化的進行會帶來什么負面的影響?
  • 如果不進行優(yōu)化或延遲優(yōu)化,相應(yīng)的時間或成本可以完成什么事情,是否更有價值?

如果評估下來,還是優(yōu)化的利大于弊,而且在合理的時間范圍內(nèi),那么就去做;如果覺得當前應(yīng)用的QPS不高、用戶體驗也還好、內(nèi)存和CPU都有空余,那么就放一放,主要放在二八法則中能為你創(chuàng)建80%價值的事情上。但是大家要記住過早優(yōu)化是萬惡之源不是寫垃圾代碼的借口。

回到正題,在上篇文章《使用結(jié)構(gòu)體替代類》中有寫在緩存和大數(shù)據(jù)量計算時使用結(jié)構(gòu)體有諸多的好處,最后關(guān)于計算性能的例子中,我使用的是簡單的for循環(huán)語句,但是在C#中我們使用LINQ多于使用for循環(huán)。有小伙伴就問了兩個問題:

  • 平時使用的LINQ對于結(jié)構(gòu)體是值傳遞還是引用傳遞?
  • 如果是值傳遞,那么有沒有辦法改為引用傳遞?達到更好性能?

針對這兩個問題特意寫一篇回答一下,字數(shù)不多,幾分鐘就能閱讀完。

Linq是值傳遞

在.NET平臺上,默認對于值類型的方法傳參都是值傳遞,除非在方法參數(shù)上指定ref,才能變?yōu)橐脗鬟f。
同樣,在LINQ實現(xiàn)的Where、Select、Take眾多方法中,也沒有加入ref關(guān)鍵字,所以在LINQ中全部都是值傳遞,如果結(jié)構(gòu)體Size大于8byte(當前平臺的指針大小),那么在調(diào)用方法時,結(jié)構(gòu)體的速度要慢于引用傳遞的類。
比如我們編寫如下代碼,使用常見的Linq API進行數(shù)據(jù)的結(jié)構(gòu)化查詢,分別使用結(jié)構(gòu)體和類,看看效果,數(shù)組數(shù)據(jù)量為1w。

public class SomeClass  
{  
    public int Value1; public int Value2;  
    public float Value3; public double Value4;  
    public string? Value5; public decimal Value6;  
    public DateTime Value7; public TimeOnly Value8;  
    public DateOnly Value9;  
}  
public struct SomeStruct  
{  
    public int Value1; public int Value2;  
    public float Value3; public double Value4;  
    public string? Value5; public decimal Value6;  
    public DateTime Value7; public TimeOnly Value8;  
    public DateOnly Value9;
}
[MemoryDiagnoser]  
[Orderer(SummaryOrderPolicy.FastestToSlowest)]  
public class Benchmark  
{  
    private static readonly SomeClass[] ClassArray;  
    private static readonly SomeStruct[] StructArray;  
    static Benchmark()  
    {  
        var baseTime = DateTime.Now;  
        ClassArray = new SomeClass[10000];  
        StructArray = new SomeStruct[10000];  
        for (int i = 0; i < 10000; i++)  
        {  
            var item = new SomeStruct  
            {  
                Value1 = i, Value2 = i, Value3 = i,  
                Value4 = i, Value5 = i.ToString(),  
                Value6 = i, Value7 = baseTime.AddHours(i),  
                Value8 = TimeOnly.MinValue, Value9 = DateOnly.MaxValue  
            };  
            StructArray[i] = item;  
            ClassArray[i] = new SomeClass  
            {  
                Value1 = i, Value2 = i, Value3 = i,  
                Value4 = i, Value5 = i.ToString(),  
                Value6 = i, Value7 = baseTime.AddHours(i),  
                Value8 = TimeOnly.MinValue, Value9 = DateOnly.MaxValue  
            };  
        }  
    }  
    [Benchmark(Baseline = true)]  
    public decimal Class()  
    {  
        return ClassArray.Where(x => x.Value1 > 5000)  
            .Where(x => x.Value3 > 5000)  
            .Where(x => x.Value7 > DateTime.MinValue)  
            .Where(x => x.Value5 != string.Empty)  
            .Where(x => x.Value6 > 1)  
            .Where(x => x.Value8 > TimeOnly.MinValue)  
            .Where(x => x.Value9 > DateOnly.MinValue)  
            .Skip(100)  
            .Take(10000)  
            .Select(x => x.Value6)  
            .Sum();  
    }  
    [Benchmark]  
    public decimal Struct()  
    {  
        return StructArray.Where(x => x.Value1 > 5000)  
            .Where(x => x.Value3 > 5000)  
            .Where(x => x.Value7 > DateTime.MinValue)  
            .Where(x => x.Value5 != string.Empty)  
            .Where(x => x.Value6 > 1)  
            .Where(x => x.Value8 > TimeOnly.MinValue)  
            .Where(x => x.Value9 > DateOnly.MinValue)  
            .Skip(100)  
            .Take(10000)  
            .Select(x => x.Value6)  
            .Sum();  
    }  
}  

Benchmakr的結(jié)果如下,大家看到在速度上有5倍的差距,結(jié)構(gòu)體由于頻繁裝箱內(nèi)存分配的也更多。

那么注定沒辦開開心心的在結(jié)構(gòu)體上用LINQ了嗎?那當然不是,引入我們今天要給大家介紹的項目。

使用StructLinq

首先來介紹一下StructLinq,在C#中用結(jié)構(gòu)體實現(xiàn)LINQ,以大幅減少內(nèi)存分配并提高性能。引入IRefStructEnumerable,以提高元素為胖結(jié)構(gòu)體(胖結(jié)構(gòu)體是指結(jié)構(gòu)體大小大于16Byte)時的性能。

引入StructLinq

這個庫已經(jīng)分發(fā)在NuGet上??梢灾苯油ㄟ^下面的命令安裝StructLinq:

PM> Install-Package StructLinq

簡單使用

下方就是一個簡單的使用,用來求元素和。唯一不同的地方就是需要調(diào)用ToStructEnumerable方法。

using StructLinq;
int[] array = new [] {1, 2, 3, 4, 5};
int result = array
                .ToStructEnumerable()
                .Where(x => (x & 1) == 0, x=>x)
                .Select(x => x *2, x => x)
                .Sum();

x=>x用于避免裝箱(和分配內(nèi)存),并幫助泛型參數(shù)推斷。你也可以通過對WhereSelect函數(shù)使用結(jié)構(gòu)來提高性能。

性能

所有的跑分結(jié)果你可以在這里找到. 舉一個例子,下方代碼的Linq查詢:

 list
     .Where(x => (x & 1) == 0)
     .Select(x => x * 2)
     .Sum();

可以被替換為下面的代碼:

list
    .ToStructEnumerable()
    .Where(x => (x & 1) == 0)
    .Select(x => x * 2)
    .Sum();

或者你想零分配內(nèi)存,可以像下面一樣寫(類型推斷出來,沒有裝箱):

list
   .ToStructEnumerable()
   .Where(x => (x & 1) == 0, x=>x)
   .Select(x => x * 2, x=>x)
   .Sum(x=>x);

如果想要零分配和更好的性能,可以像下面一樣寫:

 var where = new WherePredicate();
  var select = new SelectFunction();
  list
    .ToStructEnumerable()
    .Where(ref @where, x => x)
    .Select(ref @select, x => x, x => x)
    .Sum(x => x);

上方各個代碼的Benchmark結(jié)果如下所示:

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-8750H CPU 2.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.101
  [Host]     : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
  DefaultJob : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT

MethodMeanErrorStdDevRatioGen 0Gen 1Gen 2Allocated
LINQ65.116 μs0.6153 μs0.5756 μs1.00---152 B
StructLinqWithDelegate26.146 μs0.2402 μs0.2247 μs0.40---96 B
StructLinqWithDelegateZeroAlloc27.854 μs0.0938 μs0.0783 μs0.43----
StructLinqZeroAlloc6.872 μs0.0155 μs0.0137 μs0.11----

StructLinq在這些場景里比默認的LINQ實現(xiàn)快很多。

在上文場景中使用

我們也把上面的示例代碼使用StructLinq改寫一下。

// 引用類型使用StructLinq
[Benchmark]  
public double ClassStructLinq()  
{  
    return ClassArray  
        .ToStructEnumerable()  
        .Where(x => x.Value1 > 5000)  
        .Where(x => x.Value3 > 5000)  
        .Where(x => x.Value7 > DateTime.MinValue)  
        .Where(x => x.Value5 != string.Empty)  
        .Where(x => x.Value6 > 1)  
        .Where(x => x.Value8 > TimeOnly.MinValue)  
        .Where(x => x.Value9 > DateOnly.MinValue)  
        .Skip(100)  
        .Take(10000)  
        .Select(x => x.Value4)  
        .Sum(x => x);  
}  
// 結(jié)構(gòu)體類型使用StructLinq
[Benchmark]  
public double StructLinq()  
{  
    return StructArray  
        .ToStructEnumerable()  
        .Where(x => x.Value1 > 5000)  
        .Where(x => x.Value3 > 5000)  
        .Where(x => x.Value7 > DateTime.MinValue)  
        .Where(x => x.Value5 != string.Empty)  
        .Where(x => x.Value6 > 1)  
        .Where(x => x.Value8 > TimeOnly.MinValue)  
        .Where(x => x.Value9 > DateOnly.MinValue)  
        .Skip(100)  
        .Take(10000)  
        .Select(x => x.Value4)  
        .Sum(x => x);  
}  
// 結(jié)構(gòu)體類型 StructLinq 零分配
[Benchmark]  
public double StructLinqZeroAlloc()  
{  
    return StructArray  
        .ToStructEnumerable()  
        .Where(x => x.Value1 > 5000, x=> x)  
        .Where(x => x.Value3 > 5000, x => x)  
        .Where(x => x.Value7 > DateTime.MinValue, x => x)  
        .Where(x => x.Value5 != string.Empty, x => x)  
        .Where(x => x.Value6 > 1, x => x)  
        .Where(x => x.Value8 > TimeOnly.MinValue, x => x)  
        .Where(x => x.Value9 > DateOnly.MinValue, x => x)  
        .Skip(100)  
        .Take(10000)  
        .Select(x => x.Value4, x => x)  
        .Sum(x => x);  
}  
// 結(jié)構(gòu)體類型 StructLinq 引用傳遞
[Benchmark]  
public double StructLinqRef()  
{  
    return StructArray  
        .ToRefStructEnumerable()  // 這里使用的是ToRefStructEnumerable
        .Where((in SomeStruct x) => x.Value1 > 5000)  
        .Where((in SomeStruct x) => x.Value3 > 5000)  
        .Where((in SomeStruct x) => x.Value7 > DateTime.MinValue)  
        .Where((in SomeStruct x) => x.Value5 != string.Empty)  
        .Where((in SomeStruct x) => x.Value6 > 1)  
        .Where((in SomeStruct x) => x.Value8 > TimeOnly.MinValue)  
        .Where((in SomeStruct x) => x.Value9 > DateOnly.MinValue)  
        .Skip(100)  
        .Take(10000)  
        .Select((in SomeStruct x) => x.Value4)  
        .Sum(x => x);  
}  
// 結(jié)構(gòu)體類型 StructLinq 引用傳遞 零分配
[Benchmark]  
public double StructLinqRefZeroAlloc()  
{  
    return StructArray  
        .ToRefStructEnumerable()  
        .Where((in SomeStruct x) => x.Value1 > 5000, x=> x)  
        .Where((in SomeStruct x) => x.Value3 > 5000, x=> x)  
        .Where((in SomeStruct x) => x.Value7 > DateTime.MinValue, x=> x)  
        .Where((in SomeStruct x) => x.Value5 != string.Empty, x=> x)  
        .Where((in SomeStruct x) => x.Value6 > 1, x => x)  
        .Where((in SomeStruct x) => x.Value8 > TimeOnly.MinValue, x=> x)  
        .Where((in SomeStruct x) => x.Value9 > DateOnly.MinValue, x=> x)  
        .Skip(100, x => x)  
        .Take(10000, x => x)  
        .Select((in SomeStruct x) => x.Value4, x=> x)  
        .Sum(x => x, x=>x);  
}  
// 結(jié)構(gòu)體 直接for循環(huán)
[Benchmark]  
public double StructFor()  
{  
    double sum = 0;  
    int skip = 100;  
    int take = 10000;  
    for (int i = 0; i < StructArray.Length; i++)  
    {  
        ref var x = ref StructArray[i];  
        if(x.Value1 <= 5000) continue;  
        if(x.Value3 <= 5000) continue;  
        if(x.Value7 <= DateTime.MinValue) continue;  
        if(x.Value5 == string.Empty) continue;  
        if(x.Value6 <= 1) continue;  
        if(x.Value8 <= TimeOnly.MinValue) continue;  
        if(x.Value9 <= DateOnly.MinValue) continue;  
        if(i < skip) continue;  
        if(i >= skip + take) break;  
        sum += x.Value4;  
    }  
  
    return sum;  
}

最后的Benchmark結(jié)果如下所示。

從以上Benchmark結(jié)果可以得出以下結(jié)論:

  • 類和結(jié)構(gòu)體都可以使用StructLinq來減少內(nèi)存分配。
  • 類和結(jié)構(gòu)體使用StructLinq都會導(dǎo)致代碼跑的更慢。
  • 結(jié)構(gòu)體類型使用StructLinq的引用傳遞模式可以獲得5倍的性能提升,比引用類型更快。
  • 無論是LINQ還是StructLinq由于本身的復(fù)雜性,性能都沒有For循環(huán)來得快。

總結(jié)

在已經(jīng)用上結(jié)構(gòu)體的高性能場景,其實不建議使用LINQ了,因為LINQ本身它性能就存在瓶頸,它主要就是為了提升開發(fā)效率。建議直接使用普通循環(huán)。
如果一定要使用,那么建議大于8byte的結(jié)構(gòu)體使用StructLinq的引用傳遞模式(ToRefStructEnumerable),這樣可以把普通LINQ結(jié)構(gòu)體的性能提升5倍以上,也能幾乎不分配額外的空間。

到此這篇關(guān)于.NET性能優(yōu)化-為結(jié)構(gòu)體數(shù)組使用StructLinq的文章就介紹到這了,更多相關(guān).NET結(jié)構(gòu)體內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論