簡體   English   中英

C#方法100x慢,三次返回vs兩次?

[英]C# Method 100x slower with three returns vs two?

當我嘗試對其進行性能測試時,我對我所做的方法有一些奇怪的行為,基本上如果我注釋掉/禁用其中一個if語句中的一個返回它從400ms到4ms,幾乎像它被編譯掉了,並沒有實際運行代碼,如果在評論/禁用一個返回后,它只有返回true或false,所以它只有一個選項然后我可以看到編譯器如何優化它並始終將其設置為bool而不是運行代碼。

任何人都知道可能會發生什么或建議更好的方式來運行測試?

我的測試代碼:

Vec3 spherePos = new Vec3(43.7527, 75.9756, 0);
double sphereRadisSq = 50 * 50;
Vec3 rayPos = new Vec3(-5.32301, 5.97157, -112.983);
Vec3 rayDir = new Vec3(0.457841, 0.680324, 0.572312);

sw.Reset();
sw.Start();
bool res = false;
for (int i = 0; i < 10000000; i++)
{
   res = Intersect.RaySphereFast(rayPos, rayDir, spherePos, sphereRadisSq);
}      
sw.Stop();
Debug.Log($"testTime: {sw.ElapsedMilliseconds} ms");
Debug.Log(res);

和靜態方法:

public static bool RaySphereFast(Vec3 _rp, Vec3 _rd, Vec3 _sp, double _srsq) 
{
    double rs = Vec3.DistanceFast(_rp, _sp);
    if (rs < _srsq)
    {
        return (true); // <-- When I disable this one
    }
    Vec3 p = Vec3.ProjectFast(_sp, _rp, _rd);
    double pr = Vec3.Dot(_rd, (p - _rp));
    if (pr < 0)
    {
        return (false); // <--  Or when I disable this one
    }
    double ps = Vec3.DistanceFast(p, _sp);
    if (ps < _srsq) 
    {
        return (true); // <--  Or when I disable this one
    }
    return (false);
}

Vec3結構( 精簡

public struct Vec3
{
    public Vec3(double _x, double _y, double _z)
    {
        x = _x;
        y = _y;
        z = _z;
    }

    public double x { get; }
    public double y { get; }
    public double z { get; }

    public static double DistanceFast(Vec3 _v0, Vec3 _v1) 
    {
        double x = (_v1.x - _v0.x);
        double y = (_v1.y - _v0.y);
        double z = (_v1.z - _v0.z);
        return ((x * x) + (y * y) + (z * z));
    }

    public static double Dot(Vec3 _v0, Vec3 _v1)
    {
        return ((_v0.x * _v1.x) + (_v0.y * _v1.y) + (_v0.z * _v1.z));
    }

    public static Vec3 ProjectFast(Vec3 _p, Vec3 _a, Vec3 _d) 
    {
        Vec3 ap = _p - _a;
        return (_a + Vec3.Dot(ap, _d) * _d);
    }

    public static Vec3 operator +(Vec3 _v0, Vec3 _v1)
    {
        return (new Vec3(_v0.x + _v1.x, _v0.y + _v1.y, _v0.z + _v1.z));
    }

    public static Vec3 operator -(Vec3 _v0, Vec3 _v1)
    {
        return new Vec3(_v0.x - _v1.x, _v0.y - _v1.y, _v0.z - _v1.z);
    }

    public static Vec3 operator *(double _d1, Vec3 _v0)
    {
        return new Vec3(_d1 * _v0.x, _d1 * _v0.y, _d1 * _v0.z);
    }
}

這可能會發生,因為當您注釋掉返回時,方法的復雜性會低於禁用自動內聯的閾值。

這種內聯在生成的IL中不可見 - 它由JIT編譯器完成。

我們可以通過使用[MethodImpl(MethodImplOptions.AggressiveInlining)]屬性修飾相關方法來測試此假設。

當我用你的代碼嘗試這個時,我獲得了以下結果(發布,x64 build):

Original code:                      302 ms
First return commented out:           2 ms
Decorated with AggressiveInlining:    2 ms

注釋掉第一個返回的時間與使用AggressiveInlining裝飾方法時獲得的時間相同(啟用第一個返回)。

因此,我得出結論,假設是正確的。

這里有一些有趣的事情。 正如其他人指出當你注釋掉其中一個返回時, RaySphereFast方法現在變得足夠小RaySphereFast聯,並且jit確實決定內聯它。 而這反過來又概括了它調用的所有輔助方法。 結果,循環體最終沒有調用。

一旦發生這種情況,jit然后“struct promote”各種Vec3實例,並且由於你已經用常量初始化了所有字段,jit傳播這些常量並在各種操作中折疊它們。 因此,jit意識到調用的結果將始終為true

由於循環的每次迭代都返回相同的值,因此jit意識到循環中的這些計算都不是必需的(因為結果是已知的)並將它們全部刪除。 因此,在“快速”版本中,您將計算一個空循環:

G_M52940_IG04:
       BF01000000           mov      edi, 1
       FFC1                 inc      ecx
       81F980969800         cmp      ecx, 0x989680
       7CF1                 jl       SHORT G_M52940_IG04

而在“慢”版本中,調用沒有內聯,並且沒有一個優化開始:

G_M32193_IG04:
       488D4C2478           lea      rcx, bword ptr [rsp+78H]
       C4617B1109           vmovsd   qword ptr [rcx], xmm9
       C4617B115108         vmovsd   qword ptr [rcx+8], xmm10
       C4617B115910         vmovsd   qword ptr [rcx+16], xmm11
       488D4C2460           lea      rcx, bword ptr [rsp+60H]
       C4617B1121           vmovsd   qword ptr [rcx], xmm12
       C4617B116908         vmovsd   qword ptr [rcx+8], xmm13
       C4617B117110         vmovsd   qword ptr [rcx+16], xmm14
       488D4C2448           lea      rcx, bword ptr [rsp+48H]
       C4E17B1131           vmovsd   qword ptr [rcx], xmm6
       C4E17B117908         vmovsd   qword ptr [rcx+8], xmm7
       C4617B114110         vmovsd   qword ptr [rcx+16], xmm8
       488D4C2478           lea      rcx, bword ptr [rsp+78H]
       488D542460           lea      rdx, bword ptr [rsp+60H]
       4C8D442448           lea      r8, bword ptr [rsp+48H]
       C4E17B101D67010000   vmovsd   xmm3, qword ptr [reloc @RWD64]
       E8D2F8FFFF           call     X:RaySphereFast(struct,struct,struct,double):bool
       8BD8                 mov      ebx, eax
       FFC7                 inc      edi
       81FF80969800         cmp      edi, 0x989680
       7C95                 jl       SHORT G_M32193_IG04

如果您真的對基准測試RaySphereFast的速度感興趣,請確保在每次迭代時使用不同或非常量參數調用它,並確保使用每次迭代的結果。

只是為@Matthew Watson的答案添加一個(明顯的)免責聲明

結果取決於.NET版本,JIT版本等.FYI我無法重現這樣的差異,結果在我的環境中幾乎等同。

我正在使用帶有.NET Core 2.1.0的benchmarkDotNet ,請參閱下面的詳細信息

// * Summary *

BenchmarkDotNet=v0.11.1, OS=Windows 10.0.17134.228 (1803/April2018Update/Redstone4)
Intel Core i7-4700MQ CPU 2.40GHz (Max: 1.08GHz) (Haswell), 1 CPU, 8 logical and 4 physical cores
Frequency=2338346 Hz, Resolution=427.6527 ns, Timer=TSC
.NET Core SDK=2.2.100-preview1-009349
  [Host]     : .NET Core 2.1.0 (CoreCLR 4.6.26515.07, CoreFX 4.6.26515.06), 64bit RyuJIT
  DefaultJob : .NET Core 2.1.0 (CoreCLR 4.6.26515.07, CoreFX 4.6.26515.06), 64bit RyuJIT


                 Method |     Mean |     Error |    StdDev |
----------------------- |---------:|----------:|----------:|
 RaySphereFast_Original | 40.06 ns | 0.3693 ns | 0.3455 ns |
 RaySphereFast_NoReturn | 40.46 ns | 0.0860 ns | 0.0805 ns |

// * Legends *
  Mean   : Arithmetic mean of all measurements
  Error  : Half of 99.9% confidence interval
  StdDev : Standard deviation of all measurements
  1 ns   : 1 Nanosecond (0.000000001 sec)

// ***** BenchmarkRunner: End *****
Run time: 00:00:34 (34.86 sec), executed benchmarks: 2

// * Artifacts cleanup *

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM