Enum.HasFlag를 그렇게 느리게 만드는 것은 무엇입니까?
몇 가지 속도 테스트를했는데 Enum.HasFlag가 비트 연산을 사용하는 것보다 약 16 배 느리다는 것을 알았습니다.
누구든지 Enum.HasFlag의 내부를 알고 있으며 왜 그렇게 느린가요? 두 배 느려도 나쁘지는 않지만 16 배 느리면 기능을 사용할 수 없게됩니다.
누구든지 궁금해하는 경우 속도를 테스트하는 데 사용하는 코드가 있습니다.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace app
{
public class Program
{
[Flags]
public enum Test
{
Flag1 = 1,
Flag2 = 2,
Flag3 = 4,
Flag4 = 8
}
static int num = 0;
static Random rand;
static void Main(string[] args)
{
int seed = (int)DateTime.UtcNow.Ticks;
var st1 = new SpeedTest(delegate
{
Test t = Test.Flag1;
t |= (Test)rand.Next(1, 9);
if (t.HasFlag(Test.Flag4))
num++;
});
var st2 = new SpeedTest(delegate
{
Test t = Test.Flag1;
t |= (Test)rand.Next(1, 9);
if (HasFlag(t , Test.Flag4))
num++;
});
rand = new Random(seed);
st1.Test();
rand = new Random(seed);
st2.Test();
Console.WriteLine("Random to prevent optimizing out things {0}", num);
Console.WriteLine("HasFlag: {0}ms {1}ms {2}ms", st1.Min, st1.Average, st1.Max);
Console.WriteLine("Bitwise: {0}ms {1}ms {2}ms", st2.Min, st2.Average, st2.Max);
Console.ReadLine();
}
static bool HasFlag(Test flags, Test flag)
{
return (flags & flag) != 0;
}
}
[DebuggerDisplay("Average = {Average}")]
class SpeedTest
{
public int Iterations { get; set; }
public int Times { get; set; }
public List<Stopwatch> Watches { get; set; }
public Action Function { get; set; }
public long Min { get { return Watches.Min(s => s.ElapsedMilliseconds); } }
public long Max { get { return Watches.Max(s => s.ElapsedMilliseconds); } }
public double Average { get { return Watches.Average(s => s.ElapsedMilliseconds); } }
public SpeedTest(Action func)
{
Times = 10;
Iterations = 100000;
Function = func;
Watches = new List<Stopwatch>();
}
public void Test()
{
Watches.Clear();
for (int i = 0; i < Times; i++)
{
var sw = Stopwatch.StartNew();
for (int o = 0; o < Iterations; o++)
{
Function();
}
sw.Stop();
Watches.Add(sw);
}
}
}
}
결과 : HasFlag : 52ms 53.6ms 55ms 비트 단위 : 3ms 3ms 3ms
누구든지 Enum.HasFlag의 내부를 알고 있으며 왜 그렇게 느린가요?
실제 확인은 간단한 비트 확인 일뿐 Enum.HasFlag
입니다. 여기서 문제가되지 않습니다. 즉, 자신의 비트 검사보다 느립니다 ...
이러한 속도 저하에는 몇 가지 이유가 있습니다.
먼저 Enum.HasFlag
열거 형 유형과 플래그 유형이 동일한 유형이고 동일한 열거 형인지 확인하기 위해 명시적인 검사를 수행합니다. 이 수표에는 약간의 비용이 있습니다.
둘째, .NET UInt64
내부에서 발생 하는로 변환하는 동안 값의 불행한 상자와 상자 가 없습니다 HasFlag
. 이것은 Enum.HasFlag
기본 스토리지 유형에 관계없이 모든 열거 형에서 작동 하는 요구 사항 때문이라고 생각 합니다.
즉, Enum.HasFlag
신뢰할 수 있고 깨끗하며 코드를 매우 명확하고 표현력있게 만드는 데 큰 이점이 있습니다. 대부분의 경우 이것이 비용의 가치가 있다고 생각하지만 성능이 매우 중요한 루프에서 이것을 사용하는 경우 자체 검사를 수행 할 가치가있을 수 있습니다.
디 컴파일 된 코드는 Enum.HasFlags()
다음과 같습니다.
public bool HasFlag(Enum flag)
{
if (!base.GetType().IsEquivalentTo(flag.GetType()))
{
throw new ArgumentException(Environment.GetResourceString("Argument_EnumTypeDoesNotMatch", new object[] { flag.GetType(), base.GetType() }));
}
ulong num = ToUInt64(flag.GetValue());
return ((ToUInt64(this.GetValue()) & num) == num);
}
내가 추측한다면 유형을 확인하는 것이 가장 속도를 늦추는 것이라고 말할 수 있습니다.
JITter는 이것을 간단한 비트 연산으로 인라인해야합니다. JITter는 특정 프레임 워크 메서드 (MethodImplOptions.InternalCall을 통해)도 사용자 정의 할 수있을만큼 충분히 인식하고 있지만 HasFlag는 Microsoft의 심각한 관심을 피한 것 같습니다.
이 페이지에서 설명 하는 권투 로 인한 성능 저하 는 각각 전달되는 및 각각에 전달되는 공용 .NET 함수 Enum.GetValues
및에 영향을줍니다 .Enum.GetNames
(Runtime)Type.GetEnumValues
(Runtime)Type.GetEnumNames
이러한 모든 함수 Array
는 반환 유형으로 (비 제네릭) 을 사용합니다 ( String
참조 유형 이기 때문에 이름에 그렇게 나쁘지는 않습니다 ). 그러나 ulong[]
값 에는 상당히 부적절 합니다.
다음은 문제가되는 코드 (.NET 4.7)를 살짝 보여줍니다.
public override Array /* RuntimeType.*/ GetEnumValues()
{
if (!this.IsEnum)
throw new ArgumentException();
ulong[] values = Enum.InternalGetValues(this);
Array array = Array.UnsafeCreateInstance(this, values.Length);
for (int i = 0; i < values.Length; i++)
{
var obj = Enum.ToObject(this, values[i]); // ew. boxing.
array.SetValue(obj, i); // yuck
}
return array; // Array of object references, bleh.
}
We can see that prior to doing the copy, RuntimeType
goes back again to System.Enum
to get an internal array, a singleton which is cached, on demand, for each specific Enum
. Notice also that this version of the values array does use the proper strong signature, ulong[]
.
Here's the .NET function (again we're back in System.Enum
now). There's a similar function for getting the names (not shown).
internal static ulong[] InternalGetValues(RuntimeType enumType) =>
GetCachedValuesAndNames(enumType, false).Values;
See the return type? This looks like a function we'd like to use... But first consider that a second reason that .NET re-copys the array each time (as you saw above) is that .NET must ensure that each caller gets an unaltered copy of the original data, given that a malevolent coder could change her copy of the returned Array
, introducing a persistent corruption. Thus, the re-copying precaution is especially intended to protect the cached internal master copy.
If you aren't worried about that risk, perhaps because you feel confident you won't accidentally change the array, or maybe just to eke-out a few cycles of (what's surely premature) optimization, it's simple to fetch the internal cached array copy of the names or values for any Enum
:
→ The following two functions comprise the sum contribution of this article ←
→ (but see edit below for improved version) ←
static ulong[] GetEnumValues<T>() where T : struct =>
(ulong[])typeof(System.Enum)
.GetMethod("InternalGetValues", BindingFlags.Static | BindingFlags.NonPublic)
.Invoke(null, new[] { typeof(T) });
static String[] GetEnumNames<T>() where T : struct =>
(String[])typeof(System.Enum)
.GetMethod("InternalGetNames", BindingFlags.Static | BindingFlags.NonPublic)
.Invoke(null, new[] { typeof(T) });
Note that the generic constraint on T
isn't fully sufficient for guaranteeing Enum
. For simplicity, I left off checking any further beyond struct
, but you might want to improve on that. Also for simplicity, this (ref-fetches and) reflects directly off the MethodInfo
every time rather than trying to build and cache a Delegate
. The reason for this is that creating the proper delegate with a first argument of non-public type RuntimeType
is tedious. A bit more on this below.
First, I'll wrap up with usage examples:
var values = GetEnumValues<DayOfWeek>();
var names = GetEnumNames<DayOfWeek>();
and debugger results:
'values' ulong[7]
[0] 0
[1] 1
[2] 2
[3] 3
[4] 4
[5] 5
[6] 6
'names' string[7]
[0] "Sunday"
[1] "Monday"
[2] "Tuesday"
[3] "Wednesday"
[4] "Thursday"
[5] "Friday"
[6] "Saturday"
So I mentioned that the "first argument" of Func<RuntimeType,ulong[]>
is annoying to reflect over. However, because this "problem" arg happens to be first, there's a cute workaround where you can bind each specific Enum
type as a Target
of its own delegate, where each is then reduced to Func<ulong[]>
.)
Clearly, its pointless to make any of those delegates, since each would just be a function that always return the same value... but the same logic seems to apply, perhaps less obviously, to the original situation as well (i.e., Func<RuntimeType,ulong[]>
). Although we do get by with a just one delegate here, you'd never really want to call it more than once per Enum type. Anyway, all of this leads to a much better solution, which is included in the edit below.
[edit:]
Here's a slightly more elegant version of the same thing. If you will be calling the functions repeatedly for the same Enum
type, the version shown here will only use reflection one time per Enum type. It saves the results in a locally-accessible cache for extremely rapid access subsequently.
static class enum_info_cache<T> where T : struct
{
static _enum_info_cache()
{
values = (ulong[])typeof(System.Enum)
.GetMethod("InternalGetValues", BindingFlags.Static | BindingFlags.NonPublic)
.Invoke(null, new[] { typeof(T) });
names = (String[])typeof(System.Enum)
.GetMethod("InternalGetNames", BindingFlags.Static | BindingFlags.NonPublic)
.Invoke(null, new[] { typeof(T) });
}
public static readonly ulong[] values;
public static readonly String[] names;
};
The two functions become trivial:
static ulong[] GetEnumValues<T>() where T : struct => enum_info_cache<T>.values;
static String[] GetEnumNames<T>() where T : struct => enum_info_cache<T>.names;
The code shown here illustrates a pattern of combining three specific tricks that seem to mutually result in an unusualy elegant lazy caching scheme. I've found the particular technique to have surprisingly wide application.
using a generic static class to cache independent copies of the arrays for each distinct
Enum
. Notably, this happens automatically and on demand;related to this, the loader lock guarantees unique atomic initialization and does this without the clutter of conditional checking constructs. We can also protect static fields with
readonly
(which, for obvious reasons, typically can't be used with other lazy/deferred/demand methods);finally, we can capitalize on C# type inference to automatically map the generic function (entry point) into its respective generic static class, so that the demand caching is ultimately even driven implicitly (viz., the best code is the code that isn't there--since it can never have bugs)
You probably noticed that the particular example shown here doesn't really illustrate point (3) very well. Rather than relying on type inference, the void
-taking function has to manually propagate forward the type argument T
. I didn't choose to expose these simple functions such that there would be an opportunity to show how C# type inference makes the overall technique shine...
However, you can imagine that when you do combine a static generic function that can infer its type argument(s)--i.e., so you don't even have to provide them at the call site--then it gets quite powerful.
The key insight is that, while generic functions have the full type-inference capability, generic classes do not, that is, the compiler will never infer T
if you try to call the first of the following lines. But we can still get fully inferred access to a generic class, and all the benefits that entails, by traversing into them via generic function implicit typing (last line):
int t = 4;
typed_cache<int>.MyTypedCachedFunc(t); // no inference from 't', explicit type required
MyTypedCacheFunc<int>(t); // ok, (but redundant)
MyTypedCacheFunc(t); // ok, full inference
Designed well, inferred typing can effortlessly launch you into the appropriate automatically demand-cached data and behaviors, customized for each type (recall points 1. and 2). As noted, I find the approach useful, especially considering its simplicity.
참고URL : https://stackoverflow.com/questions/7368652/what-is-it-that-makes-enum-hasflag-so-slow
'Development Tip' 카테고리의 다른 글
모든 셀러리 작업의 로그 메시지를 단일 파일로 전송 (0) | 2020.11.21 |
---|---|
div가 나머지 높이를 차지하도록 만드는 방법은 무엇입니까? (0) | 2020.11.21 |
JavaScript 객체를 "잠금"하면 성능상의 이점이 있습니까? (0) | 2020.11.21 |
Log.wtf ()는 Log.e ()와 어떻게 다릅니 까? (0) | 2020.11.21 |
"Hello, World!"는 무엇입니까? (0) | 2020.11.21 |