c# - Performance impact of static constructor -
update 2
found reason , different: enumexcore defined static constructor this:
public abstract class enumexcore<t> t : class { // ... static enumexcore() { if (typeof(t) != typeof(enum)) throw new invalidoperationexception($"{nameof(t)} must {typeof(enum).fullname}."); } // ... }
which removed when posting original question in attempt simplify question...
apparently static constructors have impact on performance when calling static methods: http://www.codetails.com/2014/10/18/c-static-constructors-and-performance/
im sorry wasting time..
update
in reaction usr's comments, put compiling code snippet (console app). in doing noticed, below mentioned effect cannot observed in console app snippet. in debug mode, enumex 2x slower direct call delegate, in release mode take same amount of time (i'd speculate on being due inlining here).
in original test case, helper classes (integer<t>
, integer
, enumex
) in seperate (portable class library) assembly , test code in wpf app's loaded
event. when putting code wpf app, results same console app. seems effect related using classes other assemblies. cause of this? why enumex method slow when defined in assembly , direct delegate call isn't? (both static field holds delegate , method invoked defined in same assembly enumex).
console app code snippet:
namespace testconsole { using system; using system.reflection; using system.diagnostics; using system.linq; class program { static void main(string[] args) { const int count = 10000000; stopwatch watch = new stopwatch(); int val = 0; foreach (int loop in enumerable.range(0, 4)) { val = 0; watch.restart(); (int = 0; < count; i++) { val += convert.toint32(consolekey.a); } watch.stop(); console.writeline($"convert.toint32: \t\t {watch.elapsedmilliseconds} ms"); val = 0; watch.restart(); (int = 0; < count; i++) { val += integer<consolekey>.toint32(consolekey.a); } watch.stop(); console.writeline($"integer<testenum>.toint32: \t {watch.elapsedmilliseconds} ms"); val = 0; watch.restart(); (int = 0; < count; i++) { val += enumex.toint32(consolekey.a); } watch.stop(); console.writeline($"enumex.toint32: \t\t {watch.elapsedmilliseconds} ms"); console.writeline(); } console.readkey(); } } static class integer { static int toint32(byte value) { return (int)value; } static int toint32(sbyte value) { return (int)value; } static int toint32(ushort value) { return (int)value; } static int toint32(short value) { return (int)value; } static int toint32(uint value) { return (int)value; } static int toint32(int value) { return (int)value; } static int toint32(ulong value) { return (int)value; } static int toint32(long value) { return (int)value; } static type gettype<t>() { type type = typeof(t); typeinfo info = type.gettypeinfo(); if (info.isprimitive) return type; if (info.isenum) return enum.getunderlyingtype(type); throw new notsupportedexception($"{nameof(t)} expected primitive integer type or enum."); } internal static func<t, int> gettoint32method<t>() { type type = gettype<t>(); methodinfo method; if (false) { } else if (type == typeof(byte)) method = new func<byte, int>(toint32).getmethodinfo(); else if (type == typeof(sbyte)) method = new func<sbyte, int>(toint32).getmethodinfo(); else if (type == typeof(ushort)) method = new func<ushort, int>(toint32).getmethodinfo(); else if (type == typeof(short)) method = new func<short, int>(toint32).getmethodinfo(); else if (type == typeof(uint)) method = new func<uint, int>(toint32).getmethodinfo(); else if (type == typeof(int)) method = new func<int, int>(toint32).getmethodinfo(); else if (type == typeof(ulong)) method = new func<ulong, int>(toint32).getmethodinfo(); else if (type == typeof(long)) method = new func<long, int>(toint32).getmethodinfo(); else throw new invalidoperationexception("t not supported"); return method.createdelegate(typeof(func<t, int>)) func<t, int>; } } static class integer<t> { public static readonly func<t, int> toint32 = integer.gettoint32method<t>(); } namespace internal { public abstract class enumexcore<t> t : class { internal enumexcore() { } public static int toint32<tenum>(tenum value) tenum : struct, t { return integer<tenum>.toint32(value); } } } public sealed class enumex : internal.enumexcore<enum> { enumex() { } } }
original question
have static field on helper class
public static class integer<t> { public static readonly func<t, int> toint32 = integer.gettoint32method<t>(); }
where toint32
points method similar to
static int toint32(uint value) { return (int)value; }
and helper class this
namespace internal { public abstract class enumexcore<t> t : class { internal enumexcore() { } public static int toint32<tenum>(tenum value) tenum : struct, t { return integer<tenum>.toint32(value); } } } public sealed class enumex : internal.enumexcore<enum> { enumex() { } }
now tried compare performance quick test put together:
const int count = 10000000; stopwatch watch = new stopwatch(); int val = 0; foreach (int loop in enumerable.range(0, 4)) { watch.restart(); (int = 0; < count; i++) { val += convert.toint32(testenum.one); } watch.stop(); this.tboutput.text += $"convert.toint32: \t\t\t {watch.elapsedmilliseconds} ms{system.environment.newline}"; watch.restart(); (int = 0; < count; i++) { val += integer<testenum>.toint32(testenum.one); } watch.stop(); this.tboutput.text += $"integer<testenum>.toint32: \t {watch.elapsedmilliseconds} ms{system.environment.newline}"; watch.restart(); (int = 0; < count; i++) { val += enumex.toint32(testenum.one); } watch.stop(); this.tboutput.text += $"enumex.toint32: \t\t\t {watch.elapsedmilliseconds} ms{system.environment.newline}{system.environment.newline}"; }
which produces output similar to
convert.toint32: 1041 ms integer<testenum>.toint32: 42 ms enumex.toint32: 1364 ms convert.toint32: 1010 ms integer<testenum>.toint32: 39 ms enumex.toint32: 1342 ms convert.toint32: 1010 ms integer<testenum>.toint32: 41 ms enumex.toint32: 1313 ms convert.toint32: 1020 ms integer<testenum>.toint32: 40 ms enumex.toint32: 1292 ms
release or debug build, or without debugger attached doesn't make difference.
can explain me, why enumex.toint32
slower direct call integer<testenum>.toint32
delegate? or there wrong test?
edit gettoint32method<t>
helper method returns delegate:
internal static func<t, int> gettoint32method<t>() { type type = gettype<t>(); methodinfo method; if (false) { } else if (type == typeof(byte)) method = new func<byte, int>(toint32).getmethodinfo(); else if (type == typeof(sbyte)) method = new func<sbyte, int>(toint32).getmethodinfo(); else if (type == typeof(ushort)) method = new func<ushort, int>(toint32).getmethodinfo(); else if (type == typeof(short)) method = new func<short, int>(toint32).getmethodinfo(); else if (type == typeof(uint)) method = new func<uint, int>(toint32).getmethodinfo(); else if (type == typeof(int)) method = new func<int, int>(toint32).getmethodinfo(); else if (type == typeof(ulong)) method = new func<ulong, int>(toint32).getmethodinfo(); else if (type == typeof(long)) method = new func<long, int>(toint32).getmethodinfo(); else throw new generictypeparameternotsupportetexception<t>(); return method.createdelegate(typeof(func<t, int>), target) func<t, int>; }
the call convert.toint32
making in first loop compiles call override receiving int32
, because enum
's underlying type `int32'. concrete implementation returns input argument output.
other methods not straight-forward, first have resolve function, , call it.
when comes compiling solution, compiler can short-circuit call convert.toint32
, remove entirely. i'm not sure whether has happened, having 40 or milliseconds ten million iterations looks method never called.
the other 2 loops rely on heavy lifting in terms of deciding function call in end, , placing dynamically dispatched call on resolved function. spending around second ten million calls looks pretty performance work.
however, still far far away performance can achieved static dispatching, in cases when code optimizer can inline method , avoid entire call, believe has happened here.
Comments
Post a Comment