C # метод в 100 раз медленнее с тремя возвратами против двух? - PullRequest
0 голосов
/ 09 сентября 2018

У меня немного странное поведение с методом, который я использовал, когда пытаюсь протестировать его, в основном, если я закомментирую / отключу одно из возвращений в одном из операторов if, оно переходит от 400 мс до 4 мс почти как если бы он был скомпилирован и фактически не выполнял код, это имело бы смысл, если бы после комментирования / отключения одного возврата было возвращено только значение 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);
    }
}

Ответы [ 3 ]

0 голосов
/ 09 сентября 2018

Просто добавьте (очевидный) отказ от ответственности в ответ от @Matthew Watson

Результаты зависят от версии .NET, версии JIT и т. Д. К вашему сведению, я не могу воспроизвести такую ​​разницу, и результаты возвращаются в моем окружении в значительной степени эквивалентно.

Я использую benchmarkDotNet с .NET Core 2.1.0, подробности см. Ниже

// * 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 *
0 голосов
/ 30 сентября 2018

Здесь происходит несколько интересных вещей.Как уже отмечали другие, когда вы комментируете одно из возвращаемых значений, метод RaySphereFast теперь становится достаточно маленьким, чтобы встроить его, и действительно Jit решает встроить его.А это, в свою очередь, включает все вспомогательные методы, которые он вызывает.В результате тело цикла заканчивается без вызовов.

Как только это происходит, jit затем "struct поддерживает" различные экземпляры 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, убедитесь, что вызываете ее с разными или непостоянными аргументами на каждой итерации, а также убедитесь, что используете результат каждой итерации.

0 голосов
/ 09 сентября 2018

Вероятно, это происходит потому, что когда вы закомментируете результаты, сложность метода падает ниже порога, при котором автоматическое встраивание отключено.

Эта вставка не видна в сгенерированном IL - она ​​выполняется компилятором JIT.

Мы можем проверить эту гипотезу, украсив данный метод атрибутом [MethodImpl(MethodImplOptions.AggressiveInlining)].

Когда я попробовал это с вашим кодом, я получил следующие результаты (выпуск, сборка x64):

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

Время с закомментированным первым возвратом совпадает с тем, что я получаю, когда декорирую метод с помощью AggressiveInlining (оставляя включенным первое возвращение).

Поэтому я прихожу к выводу, что гипотеза верна.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...