Skip to content

Conversation

@ysharplanguage
Copy link

No description provided.

@waf
Copy link
Owner

waf commented Apr 19, 2019

This is awesome! Thanks for submitting it, and I'll be very interested in seeing the results. I'll review it this weekend. Thanks again!

@ysharplanguage
Copy link
Author

ysharplanguage commented Apr 19, 2019 via email

@ysharplanguage
Copy link
Author

@waf I'm curious too, to see the figures you get on whatever box you happen to run; here, on my snail cheap laptop, it looks like I consistently get runtime perf in the same ballpark as with the dynamic keyword, for shape sample set sizes of 1000, 10000, 100000, and up...

I wasn't expecting it to be faster than your boilerplate dictionary dispatch pattern anyway (since my impl itself uses dicos internally plus extra overhead).

Keep me posted and thank you for this neat little benchmark repo again.

CJ

@waf
Copy link
Owner

waf commented Apr 24, 2019

Thanks for your work on this! It's a very cool approach. I've run the benchmarks on a desktop machine, and included the results below. It's a little bit slower than the Dynamic Dispatch, but not much (less than 2x).

Summary

BenchmarkDotNet=v0.10.5, OS=Windows 10.0.17763
Processor=Intel Core i5-4440 CPU 3.10GHz (Haswell), ProcessorCount=4
Frequency=10000000 Hz, Resolution=100.0000 ns, Timer=UNKNOWN
dotnet cli version=3.0.100-preview3-010431
  [Host]     : .NET Core 4.6.26614.01, 64bit RyuJIT
  DefaultJob : .NET Core 4.6.26614.01, 64bit RyuJIT

Method Mean Error StdDev
DictionaryDispatch 50.78 us 0.6614 us 0.6187 us
DoubleDispatch 167.94 us 3.3153 us 5.3536 us
DynamicDispatch 95.55 us 1.5377 us 1.4384 us
IfElsePatternDispatch 17.16 us 0.3175 us 0.2969 us
SwitchCasePatternDispatch 18.23 us 0.3628 us 0.5648 us
VisitorDispatch 20.28 us 0.4034 us 0.3962 us

Details

DispatchBenchmark.DictionaryDispatch: DefaultJob
Runtime = .NET Core 4.6.26614.01, 64bit RyuJIT; GC = Concurrent Workstation
Mean = 50.7788 us, StdErr = 0.1597 us (0.31%); N = 15, StdDev = 0.6187 us
Min = 49.7234 us, Q1 = 50.2453 us, Median = 50.9514 us, Q3 = 51.2449 us, Max = 51.5330 us
IQR = 0.9996 us, LowerFence = 48.7459 us, UpperFence = 52.7444 us
ConfidenceInterval = [50.1174 us; 51.4402 us] (CI 99.9%), Margin = 0.6614 us (1.30% of Mean)
Skewness = -0.49, Kurtosis = 1.71

DispatchBenchmark.DoubleDispatch: DefaultJob
Runtime = .NET Core 4.6.26614.01, 64bit RyuJIT; GC = Concurrent Workstation
Mean = 167.9357 us, StdErr = 0.9181 us (0.55%); N = 34, StdDev = 5.3536 us
Min = 158.4995 us, Q1 = 163.8331 us, Median = 167.2446 us, Q3 = 171.9586 us, Max = 177.3887 us
IQR = 8.1255 us, LowerFence = 151.6448 us, UpperFence = 184.1469 us
ConfidenceInterval = [164.6204 us; 171.2510 us] (CI 99.9%), Margin = 3.3153 us (1.97% of Mean)
Skewness = 0.24, Kurtosis = 1.98

DispatchBenchmark.DynamicDispatch: DefaultJob
Runtime = .NET Core 4.6.26614.01, 64bit RyuJIT; GC = Concurrent Workstation
Mean = 95.5455 us, StdErr = 0.3714 us (0.39%); N = 15, StdDev = 1.4384 us
Min = 93.0045 us, Q1 = 94.8510 us, Median = 95.5781 us, Q3 = 96.8496 us, Max = 98.0467 us
IQR = 1.9985 us, LowerFence = 91.8533 us, UpperFence = 99.8473 us
ConfidenceInterval = [94.0078 us; 97.0832 us] (CI 99.9%), Margin = 1.5377 us (1.61% of Mean)
Skewness = -0.14, Kurtosis = 2.05

DispatchBenchmark.IfElsePatternDispatch: DefaultJob
Runtime = .NET Core 4.6.26614.01, 64bit RyuJIT; GC = Concurrent Workstation
Mean = 17.1626 us, StdErr = 0.0767 us (0.45%); N = 15, StdDev = 0.2969 us
Min = 16.7695 us, Q1 = 16.8991 us, Median = 17.1342 us, Q3 = 17.3110 us, Max = 17.6739 us
IQR = 0.4119 us, LowerFence = 16.2812 us, UpperFence = 17.9288 us
ConfidenceInterval = [16.8452 us; 17.4801 us] (CI 99.9%), Margin = 0.3175 us (1.85% of Mean)
Skewness = 0.49, Kurtosis = 1.92

DispatchBenchmark.SwitchCasePatternDispatch: DefaultJob
Runtime = .NET Core 4.6.26614.01, 64bit RyuJIT; GC = Concurrent Workstation
Mean = 18.2285 us, StdErr = 0.0998 us (0.55%); N = 32, StdDev = 0.5648 us
Min = 17.0220 us, Q1 = 17.8195 us, Median = 18.1454 us, Q3 = 18.6187 us, Max = 19.3200 us
IQR = 0.7992 us, LowerFence = 16.6206 us, UpperFence = 19.8175 us
ConfidenceInterval = [17.8657 us; 18.5912 us] (CI 99.9%), Margin = 0.3628 us (1.99% of Mean)
Skewness = 0.25, Kurtosis = 2.32

DispatchBenchmark.VisitorDispatch: DefaultJob
Runtime = .NET Core 4.6.26614.01, 64bit RyuJIT; GC = Concurrent Workstation
Mean = 20.2826 us, StdErr = 0.0991 us (0.49%); N = 16, StdDev = 0.3962 us
Min = 19.6451 us, Q1 = 20.0352 us, Median = 20.2013 us, Q3 = 20.5714 us, Max = 21.1188 us
IQR = 0.5362 us, LowerFence = 19.2308 us, UpperFence = 21.3757 us
ConfidenceInterval = [19.8792 us; 20.6860 us] (CI 99.9%), Margin = 0.4034 us (1.99% of Mean)
Skewness = 0.46, Kurtosis = 2.24

Total time: 00:02:40 (160.33 sec)

@ysharplanguage
Copy link
Author

ysharplanguage commented Apr 24, 2019 via email

@ysharplanguage
Copy link
Author

ysharplanguage commented Apr 25, 2019

@waf On this:

"Haven't tested the contra / covariance compatibility rules on parameters that are delegate types (that may or may not work, dunno yet)"

eventually did last night (and good news, it works) but couldn't get the time to prepare a worthy commit..

Will do soon, with added corresponding unit tests.

I'm also considering at least one possibly meaningful use case to support static methods in combination with surrogates, or if only those bound to functions :

eg, sth along the lines of (hypothetical),

var absoluteValue = default(object).CreateSurrogate(typeof(Math), nameof(Math.Abs), default(object));

var a1 = (int)absoluteValue(-123); // valid cast, as it returns 123 (System.Int32)

var a2 = (decimal)absoluteValue(-499.99m); // valid cast, as it returns 499.99m (System.Decimal)

or we might even be able to avoid the boxing of at least the result's value type, if with enough facility provided by CreateSurrogate, eg,

var absoluteValue = default(decimal).CreateSurrogate(typeof(Math), nameof(Math.Abs), default(object)); // (let the generic extension method give the type hint about TResult at compile time)

decimal a3 = absoluteValue(-499.99m);

overloaded static functions of that sort alike Math.Abs can be frequent in some APIs / frameworks / libraries.

And although it seems like a moot point to use surrogates instead of binding early (compile time) to the best overload of the static method, the idea is... it becomes relevant when one wants to find that best overload (depending on the actual argument's runtime type) among inherited or declared or bargained static methods of a class type only discovered at runtime, eg,

var mathFunctions = useV2 ? typeof(OurLib.MathV2) : typeof(System.Math);

var absoluteValue = default(object).CreateSurrogate(mathFunctions, nameof(Math.Abs), ...);

"Hey, I just want to get the absolute value of a numeric type I can't know in advance, using one out of a bunch of static methods of a math-related class I can't know in advance either - but I also want the best overload that matches the number I will actually be passing in, at runtime, thanks"

etc, etc.

I'd probably go for double dispatching only to the declared or inherited public static ones, though, not the protected or protected internal's.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants