С#: Почему вызов функции быстрее, чем ручной встраивание?

Я измерил время выполнения для двух способов вычисления мощности 2:

1) Inline

result = b * b;

2) С помощью простого вызова функции

result = Power(b);

При работе в режиме отладки все выглядит так, как ожидалось: вызов функции значительно дороже, чем вычисление в строке (385 мс в строке против вызова функции 570 мс).

В режиме выпуска я ожидаю, что компилятор значительно ускорит время выполнения вызова функции, потому что компилятор встроил внутри себя очень маленькую функцию Power(). Но я бы не ожидал, что вызов функции будет более быстрым, чем ручной расчет.

Самое удивительное, что это так: в сборке релизов для первого запуска требуется 109 мс, а для второго запуска с вызовом Power() требуется только 62 мс.

Как вызов функции быстрее, чем ручной встраивание?

Вот программа для воспроизведения:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Starting Test");

        // 1. Calculating inline without function call
        Stopwatch sw = Stopwatch.StartNew();

        for (double d = 0; d < 100000000; d++)
        {
            double res = d * d;
        }

        sw.Stop();
        Console.WriteLine("Checked: " + sw.ElapsedMilliseconds);

        // 2. Calulating power with function call
        Stopwatch sw2 = Stopwatch.StartNew();

        for (int d = 0; d < 100000000; d++)
        {
            double res = Power(d);
        }

        sw2.Stop();
        Console.WriteLine("Function: " + sw2.ElapsedMilliseconds);

        Console.ReadKey();
    }

    static double Power(double d)
    {
        return d * d;
    }
}

Ответы

Ответ 1

Ваш тест неверен. Во второй части вместо используется int d. Возможно, это объясняет разницу во времени.

Ответ 2

Как правильно заметил Xavier, вы используете double в одном цикле и int в другом. Изменение одного и того же типа сделает результаты одинаковыми - я его протестировал.

Кроме того: то, что вы на самом деле измеряете здесь, - это продолжительность добавлений и сравнений. Вы не измеряете продолжительность квадрата d, потому что его просто не происходит: в сборке релиза оптимизатор полностью удаляет тело цикла, потому что результат не используется. Вы можете подтвердить это, комментируя тело цикла. Продолжительность будет одинаковой.

Ответ 3

Даниэль Хильгарт прав, расчет не происходит вообще, поскольку результат не используется (что, вероятно, не так в режиме отладки). Попробуйте следующий пример, и вы получите правильные результаты:

static void Main(string[] args)
    {
        Console.WriteLine("Starting Test");
        var list = new List<int>();
        // 1. Calculating inline without function call
        Stopwatch sw = Stopwatch.StartNew();

        for (int d = 0; d < 100000000; d++)
        {
            int res = d * d;
            list.Add(res);
        }

        sw.Stop();
        Console.WriteLine("Checked: " + sw.ElapsedMilliseconds);
        // 2. Calulating power with function call

        list = new List<int>();
        Stopwatch sw2 = Stopwatch.StartNew();

        for (int d = 0; d < 100000000; d++)
        {
            int res = Power(d);
            list.Add(res);
        }

        sw2.Stop();
        Console.WriteLine("Function: " + sw2.ElapsedMilliseconds);

        Console.ReadKey();
    }