Contains () работает намного медленнее, чем .Any в этом случае, почему? - PullRequest
0 голосов
/ 17 сентября 2011

Следующие 3 примера кода идентичны, за исключением крошечных различий, которые прокомментированы Посмотрите здесь.Однако скорость сильно отличается.

Пример 1: На моем компьютере он работает 59 секунд.

namespace AoDtest
{
    class Program
    {
        static bool IsWin(IList<string[]> slotView)
        {
            string[] symbol = new string[3];
            for (int reelID = 0; reelID < 3; reelID++)
            {
                symbol[reelID] = slotView[reelID][1];
            }
            if (symbol.Contains("B")) return true; // Look at here
            // if (symbol.Any(x => x == "B")) return true;
            return false;
        }
        static void Main(string[] args)
        {
            Stopwatch watch = new Stopwatch();
            watch.Start();
            int count = 0;
            string reel1 = "J   K   10  S   R   10  K   Q   10  A   Q   K   10  Q   K   R   10  K   R   10  J   R   10  A   Q   R   A   10  J   Q   10  R   K   10  L   S   A   L   10  Q   A   S   Q   A   R   10  K   R   L   10  R   A   S   10  L   Q   A   L   10  S   R   10  Q";
            string reel2 = "L   K   J   B   A   10  Q   L   R   Q   J   L   Q   R   J   Q   10  J   R   L   Q   J   10  B   Q   K   10  L   Q   J   S   Q   10  L   A   Q   L   J   R   Q   10  S   A   10  Q   B   J   A   L   S   K   Q   S   J   10  Q   L   S   Q   L   K   10  R";
            string reel3 = "J   S   A   J   B   Q   K   J   S   2x  R   Q   S   J   R   L   J   S   K   L   J   K   L   S   J   10  B   K   Q   S   J   K   L   A   K   J   A   K   S   10  J   A   R   2x  L   K   J   A   B   K   J   R   K   J   A   K   J   A   L   R   J   K   R";
            string[] myreel1 = reel1.Split('\t');
            string[] myreel2 = reel2.Split('\t');
            string[] myreel3 = reel3.Split('\t');
            for (int n = 0; n < 200; n++)
            {
                for (int i = 0; i < myreel1.Length; i++)
                    for (int j = 0; j < myreel2.Length; j++)
                        for (int k = 0; k < myreel3.Length; k++)
                        {
                            string[][] slotView = new string[3][];
                            for (int m = 0; m < 3; m++)
                            {
                                slotView[m] = new string[2];
                            }
                            slotView[0][1] = myreel1[i];
                            slotView[1][1] = myreel2[j];
                            slotView[2][1] = myreel3[k];

                            if (IsWin(slotView)) count++;
                        }
            }
            watch.Stop();
            Console.WriteLine(count);
            Console.WriteLine((double)reel1.Length * reel2.Length * reel3.Length / count);
            Console.WriteLine(watch.Elapsed);
        }
    }
}

Пример 2: На моем компьютере он работает 20 секунд.

namespace AoDtest
{
    class Program
    {
        static bool IsWin(IList<string[]> slotView)
        {
            string[] symbol = new string[3];
            for (int reelID = 0; reelID < 3; reelID++)
            {
                symbol[reelID] = slotView[reelID][1];
            }
            // if (symbol.Contains("B")) return true;
            if (symbol.Any(x => x == "B")) return true; // Look at here  
            return false;
        }
        static void Main(string[] args)
        {
            Stopwatch watch = new Stopwatch();
            watch.Start();
            int count = 0;
            string reel1 = "J   K   10  S   R   10  K   Q   10  A   Q   K   10  Q   K   R   10  K   R   10  J   R   10  A   Q   R   A   10  J   Q   10  R   K   10  L   S   A   L   10  Q   A   S   Q   A   R   10  K   R   L   10  R   A   S   10  L   Q   A   L   10  S   R   10  Q";
            string reel2 = "L   K   J   B   A   10  Q   L   R   Q   J   L   Q   R   J   Q   10  J   R   L   Q   J   10  B   Q   K   10  L   Q   J   S   Q   10  L   A   Q   L   J   R   Q   10  S   A   10  Q   B   J   A   L   S   K   Q   S   J   10  Q   L   S   Q   L   K   10  R";
            string reel3 = "J   S   A   J   B   Q   K   J   S   2x  R   Q   S   J   R   L   J   S   K   L   J   K   L   S   J   10  B   K   Q   S   J   K   L   A   K   J   A   K   S   10  J   A   R   2x  L   K   J   A   B   K   J   R   K   J   A   K   J   A   L   R   J   K   R";
            string[] myreel1 = reel1.Split('\t');
            string[] myreel2 = reel2.Split('\t');
            string[] myreel3 = reel3.Split('\t');
            for (int n = 0; n < 200; n++)
            {
                for (int i = 0; i < myreel1.Length; i++)
                    for (int j = 0; j < myreel2.Length; j++)
                        for (int k = 0; k < myreel3.Length; k++)
                        {
                            string[][] slotView = new string[3][];
                            for (int m = 0; m < 3; m++)
                            {
                                slotView[m] = new string[2];
                            }
                            slotView[0][1] = myreel1[i];
                            slotView[1][1] = myreel2[j];
                            slotView[2][1] = myreel3[k];

                            if (IsWin(slotView)) count++;
                        }
            }
            watch.Stop();
            Console.WriteLine(count);
            Console.WriteLine((double)reel1.Length * reel2.Length * reel3.Length / count);
            Console.WriteLine(watch.Elapsed);
        }
    }
}

Пример 3: Теперь я снова использую Contains(), но перенесу некоторый код из IsWin в Main, теперь он работает 14 секунд, почему он здесь быстрее?

Большое открытие:IList<string> symbol если я переключаюсь на string[] symbol, тогда он запускается через 57 секунд.

namespace AoDtest
{
    class Program
    {
        static bool IsWin(IList<string> symbol)
        {
            if (symbol.Contains("B")) return true;
            // if (symbol.Any(x => x == "B")) return true;
            return false;
        }
        static void Main(string[] args)
        {
            Stopwatch watch = new Stopwatch();
            watch.Start();
            int count = 0;
            string reel1 = "J   K   10  S   R   10  K   Q   10  A   Q   K   10  Q   K   R   10  K   R   10  J   R   10  A   Q   R   A   10  J   Q   10  R   K   10  L   S   A   L   10  Q   A   S   Q   A   R   10  K   R   L   10  R   A   S   10  L   Q   A   L   10  S   R   10  Q";
            string reel2 = "L   K   J   B   A   10  Q   L   R   Q   J   L   Q   R   J   Q   10  J   R   L   Q   J   10  B   Q   K   10  L   Q   J   S   Q   10  L   A   Q   L   J   R   Q   10  S   A   10  Q   B   J   A   L   S   K   Q   S   J   10  Q   L   S   Q   L   K   10  R";
            string reel3 = "J   S   A   J   B   Q   K   J   S   2x  R   Q   S   J   R   L   J   S   K   L   J   K   L   S   J   10  B   K   Q   S   J   K   L   A   K   J   A   K   S   10  J   A   R   2x  L   K   J   A   B   K   J   R   K   J   A   K   J   A   L   R   J   K   R";
            string[] myreel1 = reel1.Split('\t');
            string[] myreel2 = reel2.Split('\t');
            string[] myreel3 = reel3.Split('\t');
            for (int n = 0; n < 200; n++)
            {
                for (int i = 0; i < myreel1.Length; i++)
                    for (int j = 0; j < myreel2.Length; j++)
                        for (int k = 0; k < myreel3.Length; k++)
                        {
                            string[][] slotView = new string[3][];
                            for (int m = 0; m < 3; m++)
                            {
                                slotView[m] = new string[2];
                            }
                            slotView[0][1] = myreel1[i];
                            slotView[1][1] = myreel2[j];
                            slotView[2][1] = myreel3[k];

                            string[] symbol = new string[3];
                            for (int reelID = 0; reelID < 3; reelID++)
                            {
                                symbol[reelID] = slotView[reelID][1]; // Look at here
                            }

                            if (IsWin(symbol))
                            {
                                count++;
                            }
                        }
            }
            watch.Stop();
            Console.WriteLine(count);
            Console.WriteLine((double)reel1.Length * reel2.Length * reel3.Length / count);
            Console.WriteLine(watch.Elapsed);
        }
    }
}

Пример 4: аналогичен примеру 3, но вместо него используется Any, работает 17 секунд. Big Discovery: IList<string> symbol если я переключусь на string[] symbol, тогда он будет работать 18 секунд.

namespace AoDtest
{
    class Program
    {
        static bool IsWin(IList<string> symbol)
        {
            if (symbol.Contains("B")) return true;
            // if (symbol.Any(x => x == "B")) return true;
            return false;
        }
        static void Main(string[] args)
        {
            Stopwatch watch = new Stopwatch();
            watch.Start();
            int count = 0;
            string reel1 = "J   K   10  S   R   10  K   Q   10  A   Q   K   10  Q   K   R   10  K   R   10  J   R   10  A   Q   R   A   10  J   Q   10  R   K   10  L   S   A   L   10  Q   A   S   Q   A   R   10  K   R   L   10  R   A   S   10  L   Q   A   L   10  S   R   10  Q";
            string reel2 = "L   K   J   B   A   10  Q   L   R   Q   J   L   Q   R   J   Q   10  J   R   L   Q   J   10  B   Q   K   10  L   Q   J   S   Q   10  L   A   Q   L   J   R   Q   10  S   A   10  Q   B   J   A   L   S   K   Q   S   J   10  Q   L   S   Q   L   K   10  R";
            string reel3 = "J   S   A   J   B   Q   K   J   S   2x  R   Q   S   J   R   L   J   S   K   L   J   K   L   S   J   10  B   K   Q   S   J   K   L   A   K   J   A   K   S   10  J   A   R   2x  L   K   J   A   B   K   J   R   K   J   A   K   J   A   L   R   J   K   R";
            string[] myreel1 = reel1.Split('\t');
            string[] myreel2 = reel2.Split('\t');
            string[] myreel3 = reel3.Split('\t');
            for (int n = 0; n < 200; n++)
            {
                for (int i = 0; i < myreel1.Length; i++)
                    for (int j = 0; j < myreel2.Length; j++)
                        for (int k = 0; k < myreel3.Length; k++)
                        {
                            string[][] slotView = new string[3][];
                            for (int m = 0; m < 3; m++)
                            {
                                slotView[m] = new string[2];
                            }
                            slotView[0][1] = myreel1[i];
                            slotView[1][1] = myreel2[j];
                            slotView[2][1] = myreel3[k];

                            string[] symbol = new string[3];
                            for (int reelID = 0; reelID < 3; reelID++)
                            {
                                symbol[reelID] = slotView[reelID][1]; // Look at here
                            }

                            if (IsWin(symbol))
                            {
                                count++;
                            }
                        }
            }
            watch.Stop();
            Console.WriteLine(count);
            Console.WriteLine((double)reel1.Length * reel2.Length * reel3.Length / count);
            Console.WriteLine(watch.Elapsed);
        }
    }
}

Ответы [ 3 ]

3 голосов
/ 17 сентября 2011

Это довольно интересно.

Документация для IEnumerable<T>.Contains говорит, что если параметр источника реализует ICollection<T>, то он вызывает ICollection<T>.Contains. И поскольку string[] реализует ICollection<string>, это то, что называется.

Реализация Array ICollection<T>.Contains заканчивается вызовом Array.IndexOf.

Если вы измените свой код на:

if (Array.IndexOf(symbol, "B"))

Он выполняется так же быстро, как и Any. То же самое, если вы измените код на:

if ((symbol as ICollection<string>).Contains("B"))

В моих тестах вызов Array.IndexOf в два раза быстрее вызова symbol.Contains.

Я подозреваю, что все замедляется тем, что IEnumerable<T>.Contains должен решать с каждым вызовом, будет ли он вызывать ICollection<T>.Contains, или делать что-то еще. Это решение не нужно принимать, когда вызывается Any.

Вы можете заменить весь код в IsWin этим, который быстрее, чем любой из вышеперечисленных, и намного проще.

static bool IsWin(IList<string[]> slotView)
{
    return slotView.Any(s => s[1] == "B");
}

Тогда, конечно, вы можете просто полностью избавиться от метода IsWin и написать в своем внутреннем цикле:

if (slotView.Any(s => s[1] == "B")
    ++count;
1 голос
/ 17 сентября 2011

Вы измеряете, сколько времени потребуется компилятору JIT для компиляции метода IsWin (). Вот почему это имеет такое большое значение, когда вы перемещаете код в Main (), метод Main получает джит до того, как вы запустите свой секундомер.

Сделайте ваши измерения более значимыми, повторив текст теста по крайней мере 10 раз, чтобы устранить эффекты кеширования и кэширования. Что само по себе немного опасно, ваш реальный код, конечно, также увидит эти эффекты.

1 голос
/ 17 сентября 2011

Метод «Содержит» выполняет проверки перед применением оператора равенства (==).Поскольку вы запускаете цикл несколько раз, эти проверки повторяются для количества циклов.В случае 'Any' вы уже решили использовать ==, поэтому .Any - это быстро.см .: http://msdn.microsoft.com/en-us/library/ms132407.aspx

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

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