为什么访问静态数组引用比非静态数组引用慢

时间:2011-06-12 08:10:38

标签: c# optimization

看一下这个片段:

public class StringToggler
{
    static readonly bool[] ToggleableLatinChars = new[]
    {
        // 256 bools here
    };

    readonly bool[] LocalToggleableLatinChars = ToggleableLatinChars;

    public string Toggle(string s)
    {
        // blah blah

        if (LocalToggleableLatinChars[(byte) ch])
        {
            // blah blah
        }

        // blah blah
    }

    // blah blah
}

这个代码在测试中比我直接使用ToggleableLatinChars更快(7%ish)。 (在方法中使用对ToggleableLatinChars的本地引用也会更快)。

仅在编译.NET 4时才会注意到此效果。 在编译.NET 3.5时,我看到相反的效果 - 使用静态数组明显更快。 (我的机器是运行Windows 7 64位的Intel i5,正在为x86编译)

知道为什么吗?

更新: 这是一个完整的代码示例,更类似于Marc的测试示例。注意我现在使用静态和局部变量版本(不再是成员变量)。虽然我看到的差异比我原来的测试代码看到的要少,但是当编译为.NET 4时,本地版本总是更快。您可以交换正在运行的订单,但Local总是为我赢。 (为.NET 3.5编译不会这样做:它总体上比.NET 4快得多,静态更快或相同)

using System;
using System.Diagnostics;
using System.Globalization;

internal class Program
{
    const int RepeatCount = 500000;

    const string TestString1_Unicode =          @"?=3.1415926?!! ?a??!#!%# ÜBERGRößEN!!?????? ??????@!e=2.71828182?#!!$@\^i^/!@$";
    const string TestString2_Numbers =          @"p=3.14159265358979323846264338327950288419716939937510....!!!!";
    const string TestString3_LowerCase =        @"nevr un-den-erstimate ze pauer of stoopid piplz in larg grupp!\*^*/";
    const string TestString4_UpperCase =        @"DUDE, WHY U R HERE?? U SHOULDA BE IN THE MEETING (BLAH-BLAH) $\*o*/$!";

    static void Main()
    {

        RunTestsStaticAccess();
        RunTestsLocalAccess();

        Console.ReadLine();
    }

    public static void RunTestsLocalAccess()
    {
        StringToggler st = new StringToggler();

        var watch = Stopwatch.StartNew();
        for (int i = 0; i < RepeatCount; i++)
        {
            st.ToggleCase_LocalAccess(TestString1_Unicode);
            st.ToggleCase_LocalAccess(TestString2_Numbers);
            st.ToggleCase_LocalAccess(TestString3_LowerCase);
            st.ToggleCase_LocalAccess(TestString4_UpperCase);
        }
        watch.Stop();
        Console.WriteLine("{0}: {1}ms", "RunTestsLocalAccess", watch.ElapsedMilliseconds);
    }

    public static void RunTestsStaticAccess()
    {
        StringToggler st = new StringToggler();

        var watch = Stopwatch.StartNew();
        for (int i = 0; i < RepeatCount; i++)
        {
            st.ToggleCase_StaticAccess(TestString1_Unicode);
            st.ToggleCase_StaticAccess(TestString2_Numbers);
            st.ToggleCase_StaticAccess(TestString3_LowerCase);
            st.ToggleCase_StaticAccess(TestString4_UpperCase);
        }
        watch.Stop();
        Console.WriteLine("{0}: {1}ms", "RunTestsStaticAccess", watch.ElapsedMilliseconds);
    }

    public class StringToggler
    {
        static readonly bool[] ToggleableLatinChars = new[]
        {
            false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
            false,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,
             true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true, false, false, false, false, false,
            false,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,
             true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true, false, false, false, false, false,
            false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
            false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
             true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,
             true,  true,  true,  true,  true,  true,  true, false,  true,  true,  true,  true,  true,  true,  true, false,
             true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,  true,
             true,  true,  true,  true,  true,  true,  true, false,  true,  true,  true,  true,  true,  true,  true, false
        };

        readonly TextInfo textInfo;

        public StringToggler()
        {
            textInfo = CultureInfo.CurrentCulture.TextInfo;
        }

        public StringToggler(CultureInfo cultureInfo)
        {
            textInfo = cultureInfo.TextInfo;
        }

        public unsafe string ToggleCase_StaticAccess(string s)
        {
            s = string.Copy(s);

            fixed(char* p = s)
            {
                for (int i = 0; i < s.Length; i++)
                {
                    char ch = p[i];

                    if (ch <= 0xff)
                    {
                        if (ToggleableLatinChars[(byte) ch])
                        {
                            p[i] = (char) (ch ^ 0x20);
                        }
                    }
                    else
                    {
                        switch (CharUnicodeInfo.GetUnicodeCategory(ch))
                        {
                            case UnicodeCategory.UppercaseLetter:
                                p[i] = textInfo.ToLower(ch);
                                break;

                            case UnicodeCategory.LowercaseLetter:
                                p[i] = textInfo.ToUpper(ch);
                                break;
                        }
                    }
                }
            }

            return s;
        }

        public unsafe string ToggleCase_LocalAccess(string s)
        {
            s = string.Copy(s);

            var toggleableLatinChars = ToggleableLatinChars;

            fixed(char* p = s)
            {
                for (int i = 0; i < s.Length; i++)
                {
                    char ch = p[i];

                    if (ch <= 0xff)
                    {
                        if (toggleableLatinChars[(byte) ch])
                        {
                            p[i] = (char) (ch ^ 0x20);
                        }
                    }
                    else
                    {
                        switch (CharUnicodeInfo.GetUnicodeCategory(ch))
                        {
                            case UnicodeCategory.UppercaseLetter:
                                p[i] = textInfo.ToLower(ch);
                                break;

                            case UnicodeCategory.LowercaseLetter:
                                p[i] = textInfo.ToUpper(ch);
                                break;
                        }
                    }
                }
            }

            return s;
        }
    }
}

1 个答案:

答案 0 :(得分:6)

简单地说:事实并非如此。我不相信你的(未提供)测试:

我的结果:

InstanceField: 6035ms
LocalVariable: 5373ms
StaticFieldStaticInitializer: 5364ms
StaticFieldNoInitializer: 5388ms

它与我期望从额外的ldarg0和ldfld(从实例字段获取值)而不是更简单的ldsfld(从静态字段获取值)或ldloc0(从中获取值)局部变量)。

我的代码:

class Program
{
    static void Main()
    {
        new InstanceField().RunTests();
        new LocalVariable().RunTests();
        new StaticFieldStaticInitializer().RunTests();
        new StaticFieldNoInitializer().RunTests();
        Console.ReadLine();
    }
    class InstanceField
    {
        public bool[] arr= new bool[1024];
        public void RunTests()
        {
            var watch = Stopwatch.StartNew();
            int count = 0;
            for (int i = 0; i < 500000; i++)
            {
                for (int j = 0; j < arr.Length; j++)
                {
                    if (arr[j]) count++;
                }
            }
            watch.Stop();
            Console.WriteLine("{0}: {1}ms", GetType().Name, watch.ElapsedMilliseconds);
        }
    }
    class LocalVariable
    {
        public void RunTests()
        {
            bool[] arr = new bool[1024];
            var watch = Stopwatch.StartNew();
            int count = 0;
            for (int i = 0; i < 500000; i++)
            {
                for (int j = 0; j < arr.Length; j++)
                {
                    if (arr[j]) count++;
                }
            }
            watch.Stop();
            Console.WriteLine("{0}: {1}ms", GetType().Name, watch.ElapsedMilliseconds);
        }
    }
    class StaticFieldStaticInitializer
    {
        public static bool[] arr = new bool[1024];
        public void RunTests()
        {
            var watch = Stopwatch.StartNew();
            int count = 0;
            for (int i = 0; i < 500000; i++)
            {
                for (int j = 0; j < arr.Length; j++)
                {
                    if (arr[j]) count++;
                }
            }
            watch.Stop();
            Console.WriteLine("{0}: {1}ms", GetType().Name, watch.ElapsedMilliseconds);
        }
    }
    class StaticFieldNoInitializer
    {
        public static bool[] arr;
        public void RunTests()
        {                
            arr = new bool[1024];
            var watch = Stopwatch.StartNew();
            int count = 0;
            for (int i = 0; i < 500000; i++)
            {
                for (int j = 0; j < arr.Length; j++)
                {
                    if (arr[j]) count++;
                }
            }
            watch.Stop();
            Console.WriteLine("{0}: {1}ms", GetType().Name, watch.ElapsedMilliseconds);
        }
    }
}