Подтвердить что ты не робот

Math.Max ​​vs inline if - в чем отличия?

Я работал над проектом сегодня и обнаружил, что использую Math.Max ​​в нескольких местах и ​​встроенный, если заявления в других местах. Итак, мне было интересно, знает ли кто, что "лучше"... или, вернее, каковы реальные различия.

Например, в следующем, c1 = c2:

Random rand = new Random();
int a = rand.next(0,10000);
int b = rand.next(0,10000);

int c1 = Math.Max(a, b);
int c2 = a>b ? a : b;

Я спрашиваю конкретно о С#, но я полагаю, что ответ может отличаться на разных языках, хотя я не уверен, какие из них имеют схожие понятия.

4b9b3361

Ответ 1

Одно из основных различий, которое я бы заметил сразу, было бы для удобства чтения, насколько я знаю, для реализации/производительности, они были бы почти эквивалентны.

Math.Max(a,b) очень просто понять, независимо от предыдущих знаний о кодировании.

a>b ? a : b потребует, чтобы пользователь имел некоторые знания о тройном операторе, по крайней мере.

"Если у вас есть сомнения - пойдите для удобочитаемости"

Ответ 2

Я подумал, что было бы интересно вставить некоторые цифры в это обсуждение, поэтому я написал код для его профилирования. Как и ожидалось, они практически идентичны для всех практических целей.

Код выполняет миллиард циклов (1 миллиард). Вычитая накладные расходы цикла, вы получаете:

  • Math.Max ​​() занял 0,0044 секунды, чтобы запустить 1 миллиард раз
  • Inline, если занял 0,500 секунд для запуска 1 миллиард раз

Я вычитал накладные расходы, которые я вычислил, запустив пустой цикл 1 миллиард раз, накладные расходы составляли 1,2 секунды.

Я запускал это на ноутбуке, 64-разрядной версии Windows 7, 1.3 ГГц Intel Core i5 (U470). Код был скомпилирован в режиме выпуска и запускался без отладки.

Здесь код:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace TestMathMax {
    class Program {
        static int Main(string[] args) {
            var num1 = 10;
            var num2 = 100;
            var maxValue = 0;
            var LoopCount = 1000000000;
            double controlTotalSeconds;
            { 
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                for (var i = 0; i < LoopCount; i++) {
                    // do nothing
                }
                stopwatch.Stop();
                controlTotalSeconds = stopwatch.Elapsed.TotalSeconds;
                Console.WriteLine("Control - Empty Loop - " + controlTotalSeconds + " seconds");
            }
            Console.WriteLine();
            {
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                for (int i = 0; i < LoopCount; i++) {
                    maxValue = Math.Max(num1, num2);
                }
                stopwatch.Stop();
                Console.WriteLine("Math.Max() - " + stopwatch.Elapsed.TotalSeconds + " seconds");
                Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
            }
            Console.WriteLine();
            {
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                for (int i = 0; i < LoopCount; i++) {
                    maxValue = num1 > num2 ? num1 : num2;
                }
                stopwatch.Stop();
                Console.WriteLine("Inline Max: " + stopwatch.Elapsed.TotalSeconds + " seconds");
                Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
            }

            Console.ReadLine();

            return maxValue;
        }
    }
}

ОБНОВЛЕНО Результаты 2/7/2015

В Windows 8.1, Surface 3 Pro, i7 4650U 2.3Ghz Отправлено как консольное приложение в режиме освобождения без прикрепленного отладчика.

  • Math.Max ​​() - 0.3194749 секунд
  • Inline Max: 0.3465041 секунд

Ответ 3

если утверждение считается полезным

Резюме

утверждение формы if (a > max) max = a является самым быстрым способом определения максимального числа чисел. Однако сама инфраструктура цикла занимает большую часть процессорного времени, поэтому в конечном итоге эта оптимизация сомнительна.

Подробнее

Ответ на luisperezphd интересен тем, что он предоставляет числа, однако я считаю, что этот метод ошибочен: компилятор, скорее всего, перенесет сравнение из цикла, поэтому ответ не измеряет, что он хочет измерить. Это объясняет незначительную разницу во времени между контуром управления и петлями измерения.

Чтобы избежать этой оптимизации цикла, я добавил операцию, которая зависит от переменной цикла, с пустым контуром управления, а также ко всем циклам измерений. Я имитирую общий пример использования максимума в списке чисел и использовал три набора данных:

  • лучший случай: первое число является максимальным, все числа после него меньше
  • худший случай: каждое число больше предыдущего, поэтому max меняет каждую итерацию
  • средний случай: набор случайных чисел

Ниже приведен код.

Результат оказался для меня довольно неожиданным. На моем ноутбуке Core i5 2520M я получил следующее за 1 миллиард итераций (пустой контроль занял около 2,6 секунды во всех случаях):

  • max = Math.Max(max, a): самый лучший случай в 2 сек. /1,3 с наихудшего случая/средний случай 2.0 с.
  • max = Math.Max(a, max): 1,6 сек лучший случай /2,0 с наихудший случай/средний 1,5 с.
  • max = max > a ? max : a: наилучший случай 1,2 сек/наименьший случай 1.2 сек/средний случай 1.2 с
  • if (a > max) max = a: самый лучший случай 0,2 сек/наихудший случай 0,9 с/средний случай с 0,3 с.

Таким образом, несмотря на длинные CPU-конвейеры и возникающие в результате штрафы за ветвление, старый добрый оператор if является явным победителем для всех симулированных наборов данных; в лучшем случае он в 10 раз быстрее, чем Math.Max, а в худшем случае - более чем на 30% быстрее.

Еще одно удивление заключается в том, что порядок аргументов Math.Max имеет значение. Предположительно, это связано с тем, что логика прогнозирования ветвления процессора работает по-разному для двух случаев и неверно предсказывает ветки более или менее в зависимости от порядка аргументов.

Однако большая часть времени процессора расходуется в инфраструктуре цикла, поэтому в конечном итоге эта оптимизация в лучшем случае сомнительна. Он обеспечивает измеримое, но незначительное сокращение общего времени выполнения.

ОБНОВЛЕНО luisperezphd

Я не мог поместить это в качестве комментария, и было более целесообразно написать его здесь, а не как часть моего ответа, чтобы он был в контексте.

Ваша теория имеет смысл, но я не смог воспроизвести результаты. Сначала по какой-то причине с использованием кода мой контур управления занимал больше времени, чем циклы, содержащие работу.

По этой причине я сделал числа здесь относительно самого низкого времени вместо контура управления. Секунд в результатах - это больше времени, чем самое быстрое время. Например, в результатах, непосредственно ниже, самое быстрое время было для Math.Max ​​(a, max) наилучшего случая, поэтому каждый другой результат показывает, сколько времени они занимали.

Ниже приведены результаты, полученные мной:

  • max = Math.Max(max, a): самый лучший случай 0,012 с/наименьший случай 0,007 сек/средний случай 0,028 с.
  • max = Math.Max(a, max): 0.000 лучших случаев /0.021 наихудший случай/средний 0.019 с.
  • max = max > a ? max : a: 0.022 сек лучший случай /0,02 сек наихудший случай/средний показатель 0,01 с
  • if (a > max) max = a: 0,015 сек лучший случай /0,024 сек наихудший случай/средний показатель 0,019 с

Во второй раз, когда я его запустил, я получил:

  • max = Math.Max(max, a): 0,024 сек лучший случай /0,010 сек наихудший случай/средний показатель 0,009 с
  • max = Math.Max(a, max): самый лучший случай 0,001 сек /0,000 с наихудший случай/средний показатель 0,018 с
  • max = max > a ? max : a: 0,011 сек лучший случай /0,005 сек наихудший случай/средний 0,018 с.
  • if (a > max) max = a: 0,000 сек лучший случай /0,005 сек наихудший случай/средний 0,039 с.

В этих тестах достаточно объема, чтобы любые аномалии были уничтожены. Однако, несмотря на это, результаты довольно разные. Возможно, большое выделение памяти для массива имеет к этому какое-то отношение. Или, возможно, разница настолько мала, что все, что происходит на компьютере в то время, является истинной причиной изменения.

Обратите внимание, что самое быстрое время, представленное в результатах выше, на 0,000 составляет около 8 секунд. Поэтому, если вы считаете, что самый длинный пробег тогда составлял 8.039, изменение во времени составляет около половины процента (0,5%), что слишком мало для материи.

Компьютер

Код выполнялся в Windows 8.1, i7 4810MQ 2.8Ghz и скомпилирован в .NET 4.0.

Изменения кода

Я немного изменил ваш код, чтобы вывести результаты в формате, показанном выше. Я также добавил дополнительный код, чтобы подождать 1 секунду после начала учета любого дополнительного времени загрузки, которое может понадобиться .NET при запуске сборки.

Кроме того, я дважды проверил все тесты, чтобы учесть любые оптимизации ЦП. Наконец, я изменил int на i на unit, чтобы я мог запустить цикл в 4 миллиарда раз вместо 1 миллиарда, чтобы получить более длительный промежуток времени.

Это, вероятно, все излишнее, но все, чтобы как можно больше убедиться, что на тесты не влияет какой-либо из этих факторов.

Код можно найти по адресу: http://pastebin.com/84qi2cbD

код

using System;
using System.Diagnostics;

namespace ProfileMathMax
{
  class Program
  {
    static double controlTotalSeconds;
    const int InnerLoopCount = 100000;
    const int OuterLoopCount = 1000000000 / InnerLoopCount;
    static int[] values = new int[InnerLoopCount];
    static int total = 0;

    static void ProfileBase()
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        int maxValue;
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                // baseline
                total += values[i];
            }
        }
        stopwatch.Stop();
        controlTotalSeconds = stopwatch.Elapsed.TotalSeconds;
        Console.WriteLine("Control - Empty Loop - " + controlTotalSeconds + " seconds");
    }

    static void ProfileMathMax()
    {
        int maxValue;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                maxValue = Math.Max(values[i], maxValue);
                total += values[i];
            }
        }
        stopwatch.Stop();
        Console.WriteLine("Math.Max(a, max) - " + stopwatch.Elapsed.TotalSeconds + " seconds");
        Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
    }

    static void ProfileMathMaxReverse()
    {
        int maxValue;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                maxValue = Math.Max(maxValue, values[i]);
                total += values[i];
            }
        }
        stopwatch.Stop();
        Console.WriteLine("Math.Max(max, a) - " + stopwatch.Elapsed.TotalSeconds + " seconds");
        Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
    }

    static void ProfileInline()
    {
        int maxValue = 0;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                maxValue = maxValue > values[i] ? values[i] : maxValue;
                total += values[i];
            }
        }
        stopwatch.Stop();
        Console.WriteLine("max = max > a ? a : max: " + stopwatch.Elapsed.TotalSeconds + " seconds");
        Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
    }

    static void ProfileIf()
    {
        int maxValue = 0;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                if (values[i] > maxValue)
                    maxValue = values[i];
                total += values[i];
            }
        }
        stopwatch.Stop();
        Console.WriteLine("if (a > max) max = a: " + stopwatch.Elapsed.TotalSeconds + " seconds");
        Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
    }

    static void Main(string[] args)
    {
        Random rnd = new Random();
        for (int i = 0; i < InnerLoopCount; i++)
        {
            //values[i] = i;  // worst case: every new number biggest than the previous
            //values[i] = i == 0 ? 1 : 0;  // best case: first number is the maximum
            values[i] = rnd.Next(int.MaxValue);  // average case: random numbers
        }

        ProfileBase();
        Console.WriteLine();
        ProfileMathMax();
        Console.WriteLine();
        ProfileMathMaxReverse();
        Console.WriteLine();
        ProfileInline();
        Console.WriteLine();
        ProfileIf();
        Console.ReadLine();
    }
  }
}

Ответ 4

Если JITer выбирает встроенную функцию Math.Max, исполняемый код будет идентичен оператору if. Если Math.Max ​​не встроен, он будет выполняться как вызов функции с накладными расходами на возврат и возврат, не присутствующими в инструкции if. Таким образом, оператор if даст идентичную производительность Math.Max ​​() в случае вложения, или оператор if может быть несколько тактов в ускоренном порядке в неинлайн-случае, но разница не будет заметна, если вы не используете десятки миллионов сравнений.

Так как разница в производительности между ними достаточно мала, чтобы в большинстве ситуаций была пренебрежимо малой, я бы предпочел Math.Max ​​(a, b), потому что ее легче читать.

Ответ 5

Я бы сказал, что быстрее понять, что делает Math.Max, и это действительно должен быть единственным решающим фактором здесь.

Но в качестве индульгенции интересно рассмотреть, что Math.Max(a,b) оценивает аргументы один раз, а a > b ? a : b оценивает один из них дважды. Не проблема с локальными переменными, но для свойств с побочными эффектами побочный эффект может произойти дважды.

Ответ 6

Что касается производительности, современные CPU имеют внутренний конвейер команд, так что каждая команда сборки выполняется несколькими внутренними шагами. (например, выборка, интерпретация, вычисление, хранение)

В большинстве случаев процессор достаточно умен, чтобы параллельно выполнять эти шаги для последовательных команд, поэтому общая пропускная способность очень высока.

Это нормально, пока не наступит ветка (if, ?: и т.д.). Филиал может сломать последовательность и заставить процессор тратить конвейер. Это требует большого количества тактовых циклов.

Теоретически, если компилятор достаточно умный, Math.Max может быть реализован с использованием встроенной его команды ЦП и разветвления можно избежать.

В этом случае Math.Max будет на самом деле быстрее, чем if, но это зависит от компилятора.

В случае более сложного Max-like, работающего на векторах, double []v; v.Max() компилятор может использовать высоко оптимизированный библиотечный код, который может быть намного быстрее, чем обычный скомпилированный код.

Так что лучше всего идти с Math.Max, но также рекомендуется проверить вашу целевую систему и компилятор, если это достаточно важно.

Ответ 7

Math.max (а, б)

НЕ эквивалентно a > b? a: b a > b? a: b во всех случаях.

Math.Max возвращает большее значение двух аргументов, то есть:

if (a == b) return a; // or b, doesn't matter since they're identical
else if (a > b && b < a) return a;
else if (b > a && a < b) return b;
else return undefined;

Значение undefined отображается в double.NaN в случае двойной перегрузки Math.Max.

а> б? а: б

оценивается как a, если a больше, чем b, что не обязательно означает, что b меньше, чем a.

Простой пример, который демонстрирует, что они не эквивалентны:

var a = 0.0/0.0; // or double.NaN
var b = 1.0;
a > b ? a : b // evaluates to 1.0
Math.Max(a, b) // returns double.NaN

Ответ 8

Принять операцию; N должно быть> = 0

Общие решения:

A) N = Math.Max(0, N)
B) if(N < 0){N = 0}

Сортировка по скорости:

Slow: Math.Max(A) <(B) оператор if-then: быстро (на 3% быстрее, чем решение 'A')

Но мое решение на 4% быстрее, чем решение "B":

N *= Math.Sign(1 + Math.Sign(N));