Why is accessing a static array reference slower than a non-static array reference
Have a look at this snippet:
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
}
This code is demonstrably quicker (7% ish) in testing than if I used ToggleableLatinChars directly. (Using a local reference to ToggleableLatinChars in the method is also quicker by the same amount).
This effect is noticed only when compiling for .NET 4. When compiling for .NET 3.5, I see the opposite effect - using the static array is noticeably quicker. (My machine is an Intel i5 running Windows 7 64-bit and is compiling for x86)
Any idea why?
Update: Here is a complete code sample which is more akin to Marc's testing sample. Note I am now using static and local variable versions (not member variable any more). Although the difference I see with this is less than I was seeing with my original test code, when compiled for .NET 4, the local version is always faster. You can swap the running order around but Local always wins for me. (Compiling for .NET 3.5 does not do this: it is much faster overall than .NET 4 and static is either faster or the same)
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])
{
开发者_StackOverflow社区 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;
}
}
}
Simply: it isn't. I don't trust your (not provided) test:
My results:
InstanceField: 6035ms
LocalVariable: 5373ms
StaticFieldStaticInitializer: 5364ms
StaticFieldNoInitializer: 5388ms
which ties in to what I would expect from additional ldarg0 and ldfld (to get the value from an instance field) rather than the simpler ldsfld (to get the value from a static field) or ldloc0 (to get the value from a local variable).
My code:
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);
}
}
}
精彩评论