日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > windows >内容正文

windows

为了性能,慎用递归

發(fā)布時(shí)間:2023/11/18 windows 43 coder
生活随笔 收集整理的這篇文章主要介紹了 为了性能,慎用递归 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

慎用遞歸

起因:

在學(xué)習(xí)Rust的時(shí)候,有一道語(yǔ)法練習(xí)題是計(jì)算斐波那契數(shù)列的第N項(xiàng)的值,這是一道非常簡(jiǎn)單的題,但是引發(fā)了一個(gè)使用遞歸性能問(wèn)題,考慮到用Rust的人不多,后面的代碼都是C#的,因?yàn)镃#的語(yǔ)法更大眾一些,更好看懂

第一次解

public static ulong FibonacciNumberRecursion(int n)
{
    if (n == 1)
        return 0;
    else if (n == 2)
        return 1;
    else
    {
        return FibonacciNumberRecursion(n - 1) + FibonacciNumberRecursion(n - 2);
    }
}

這個(gè)寫法非常的符合大腦思考,第一項(xiàng)返回0,第二項(xiàng)返回1,后面的返回前兩項(xiàng)之和,簡(jiǎn)單測(cè)試沒(méi)有任何問(wèn)題。但是,這個(gè)算法有非常嚴(yán)重的性能問(wèn)題,當(dāng)n到40的時(shí)候,計(jì)算速度已經(jīng)到了肉眼不可接受的地步,再往上就到了分鐘級(jí)的了,造成運(yùn)行緩慢的原因,就是遞歸會(huì)不停的壓棧,存儲(chǔ)當(dāng)前的狀態(tài),這是非常沒(méi)有必要的,于是我寫了第二種解法

第二次解

public static ulong FibonacciNumber(int n)
{
    if (n == 1)
        return 0;
    else if (n == 2)
        return 1;
    else
    {
        var x = 3;
        ulong xSub1 = 1;
        ulong xSub2 = 0;
        ulong value = 0;
        while (x <= n)
        {
            value = xSub1 + xSub2;
            xSub2 = xSub1;
            xSub1 = value;
            x += 1;
        }
        return value;
    }
}

這一次使用循環(huán)代替遞歸,它沒(méi)有頻繁的壓棧,性能非常好,計(jì)算第200項(xiàng)的值也在納秒級(jí)別,于是便有了思考,是否所有的遞歸都是這么不堪?經(jīng)過(guò)查閱資料,發(fā)現(xiàn)第一次的遞歸有很多是無(wú)效遞歸,于是進(jìn)行了改寫

第三次解

public static ulong FibonacciNumberRecursion2(int n, ulong a = 0, ulong b = 1)
{
    // 斐波那契數(shù)列是第N項(xiàng)等于前兩項(xiàng)的和
    if (n == 1)
    {
        return a;
    }
    else
    {
        return FibonacciNumberRecursion2(n - 1, b, a + b);
    }
}

這一次的遞歸使用了a和b兩個(gè)變量去緩存前兩項(xiàng)的值,這里看起來(lái)和實(shí)際情況是有差異的,它的計(jì)算過(guò)程更接近循環(huán),因?yàn)閍,b是從0,1開(kāi)始往上加出來(lái)的,雖然遞歸是n-1。這里的n-1更像是為了達(dá)到終止遞歸的條件

經(jīng)過(guò)修改的遞歸方法,性能和循環(huán)已經(jīng)很接近了,只差一點(diǎn)點(diǎn),那這個(gè)是不是遞歸已經(jīng)非常強(qiáng)了?也不是,經(jīng)過(guò)查閱資料,發(fā)現(xiàn)是編譯器針對(duì)尾遞歸進(jìn)行了優(yōu)化,會(huì)用類似循環(huán)的機(jī)制來(lái)運(yùn)行尾遞歸

尾遞歸:如果一個(gè)函數(shù)中所有遞歸形式的調(diào)用都出現(xiàn)在函數(shù)的末尾,我們稱這個(gè)遞歸函數(shù)是尾遞歸的。當(dāng)遞歸調(diào)用是整個(gè)函數(shù)體中最后執(zhí)行的語(yǔ)句且它的返回值不屬于表達(dá)式的一部分時(shí),這個(gè)遞歸調(diào)用就是尾遞歸。尾遞歸函數(shù)的特點(diǎn)是在回歸過(guò)程中不用做任何操作,這個(gè)特性很重要,因?yàn)榇蠖鄶?shù)現(xiàn)代的編譯器會(huì)利用這種特點(diǎn)自動(dòng)生成優(yōu)化的代碼。

第四次解

經(jīng)過(guò)上面的解法,經(jīng)過(guò)編譯器優(yōu)化的尾遞歸已經(jīng)很好了,但是還想看看如果沒(méi)有優(yōu)化的性能是什么樣子呢?因?yàn)榈谝淮谓獾乃俣嚷恢皇沁f歸的原因,還有很多無(wú)意義計(jì)算,那么拋開(kāi)無(wú)意義的計(jì)算,遞歸和循環(huán)有多少差距呢?

public static ulong FibonacciNumberRecursion3(int n, ulong a = 0, ulong b = 1)
{
    // 斐波那契數(shù)列是第N項(xiàng)等于前兩項(xiàng)的和
    if (n == 1)
    {
        return a;
    }
    else
    {
        var r = FibonacciNumberRecursion3(n - 1, b, a + b);
        var z = r + 1;
        
        return z-1;
    }
}

在這里使用了+1和-1,主要是為了破壞尾遞歸,那么最后的性能是怎樣的呢

BenchmarkDotNet v0.13.10, Windows 10 (10.0.19045.3570/22H2/2022Update)
AMD Ryzen 7 4800HS with Radeon Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK 8.0.100
  [Host]     : .NET 6.0.25 (6.0.2523.51912), X64 RyuJIT AVX2
  DefaultJob : .NET 6.0.25 (6.0.2523.51912), X64 RyuJIT AVX2
Method Mean Error StdDev
Loop 53.02 ns 0.111 ns 0.098 ns
Recursion2 52.98 ns 0.261 ns 0.232 ns
Recursion3 348.34 ns 4.367 ns 4.084 ns

求第200項(xiàng)的值,Loop使用循環(huán),Recursion2是尾遞歸,Recursion3是破環(huán)了尾遞歸的情況,從這上面來(lái)看,衛(wèi)隊(duì)貴對(duì)性能的影響還是很大的

結(jié)論

上面4中求斐波那契數(shù)列的第N項(xiàng)值的寫法,有不同的性能表現(xiàn),使用循環(huán)和尾遞歸相差無(wú)幾,如果是線性遞歸,那么性能就會(huì)差很多,因此

為了性能,優(yōu)先使用循環(huán)解決問(wèn)題,經(jīng)過(guò)編譯器優(yōu)化的尾遞歸性能也不差,盡量避免使用普通的遞歸

總結(jié)

以上是生活随笔為你收集整理的为了性能,慎用递归的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。