C#: Почему вызов функции выполняется быстрее, чем ручное встраивание?

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

1) Встроенный

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;
    }
}

person Knasterbax    schedule 25.03.2013    source источник
comment
Вы запускали программу под отладчиком (F5)? В этом случае оптимизации подавлялись.   -  person usr    schedule 25.03.2013
comment
Вы запускали сгенерированный .exe или в VS в режиме выпуска? Кроме того, вы пробовали вызывать их в другом порядке? Я обнаружил, что это имеет тонкое значение   -  person DGibbs    schedule 25.03.2013
comment
Хорошо, но почему использование int в качестве двойника быстрее, чем просто использование двойника?   -  person Vercas    schedule 25.03.2013
comment
@Vercas: Скорее всего, потому что увеличение и сравнение двойника обходится дороже.   -  person Daniel Hilgarth    schedule 25.03.2013
comment
@DanielHilgarth А, хорошая мысль...   -  person Vercas    schedule 25.03.2013
comment
Вам нужно указать версию CLR (2 или 4), а также разрядность. Все 4 разные комбинации дадут разные результаты.   -  person leppie    schedule 25.03.2013


Ответы (3)


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

person Xavier Delamotte    schedule 25.03.2013
comment
Время работы первой функции всегда медленнее встроенной на моем компьютере (независимо от того, int или double). - person Chengxi Li; 25.03.2013
comment
Какие результаты после исправления? - person Robert Niestroj; 25.03.2013
comment
@RobertNiestroj: см. мой ответ о результатах. - person Daniel Hilgarth; 25.03.2013

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

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

person Daniel Hilgarth    schedule 25.03.2013
comment
Тоже полностью прав. Это была моя ошибка. я следил за - person Knasterbax; 25.03.2013

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

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();
    }
person realnero    schedule 25.03.2013
comment
Не вызовет ли этот код OverflowException, поскольку res в конечном итоге будет намного больше Int32.MaxValue в этих циклах? - person Drewman; 25.03.2013