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

本文关键字:引用 静态 数组 访问 为什么 | 更新日期: 2023-09-27 17:58:53

看看这个片段:

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%)。(在该方法中使用对ToggleableLatinChars的本地引用也同样快)。

只有在为.NET 4进行编译时才会注意到这种效果。当编译.NET 3.5时,我看到了相反的效果——使用静态数组明显更快。(我的机器是一台运行Windows7 64位的英特尔i5,正在为x86进行编译)

知道为什么吗?

更新:下面是一个完整的代码示例,它更类似于Marc的测试示例。注意,我现在使用的是静态和局部变量版本(不再是成员变量)。虽然我看到的差异比我在原始测试代码中看到的要小,但当为.NET4编译时,本地版本总是更快。你可以交换运行顺序,但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;
        }
    }
}

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

简单地说:不是。我不相信你(未提供)的测试:

我的结果:

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