Contains()运行速度比.Any慢得多,为什么呢?

时间:2011-09-17 15:28:09

标签: c# .net performance linq

以下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 个答案:

答案 0 :(得分:3)

这很有趣。

IEnumerable<T>.Contains的文档说,如果source参数实现了ICollection<T>,那么它会调用ICollection<T>.Contains。由于string[]实现了ICollection<string>,所以这就是所谓的。

Array的{​​{1}}实施最终会调用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,或者做其他事情。 <{1}}被调用时,不必做出这个决定。

您可以使用此替换ICollection<T>.Contains中的所有代码,这比上述任何一种代码更快,并且更加简单。

Any

然后,当然,您可以完全摆脱IsWin方法并写入内循环:

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

答案 1 :(得分:1)

方法“Contains”在应用相等运算符(==)之前进行检查。当您多次运行循环时,将对循环计数重复此检查。 如果是'Any',你已经决定使用==所以.Any很快。 见:http://msdn.microsoft.com/en-us/library/ms132407.aspx

在第三种情况下,编译器必须优化代码而不是进行方法调用(而是在条件时写入签入)。

答案 2 :(得分:1)

您正在测量JIT编译器编译IsWin()方法所需的时间。这就是为什么当你将代码移到Main()中时它会产生如此大的差异,Main方法在开始你的秒表之前就会被引入。

通过重复测试体至少10次,使测量更有意义,这样您就可以消除抖动和缓存效果。这本身就有点危险,你的实际代码当然也会看到这些效果。