|
i looked at your code wrong, didn't notice until i tried writing one myself. that's not template specialization, but simply method overloading - and i'm doing it already
class CharFA<taccept> : FA<char,taccept> {
..
}
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
Yeah, I was wondering, isn't that good enough?!
But then I realised one could accidentally instantiate new FA<char, T>() instead of the desired new CharFA<T>()
But then what of this other syntax?
While it's slightly more wordy, I bet the end compiled result is just as you desired
class A<T>
{
public void Do(T value)
{
if (value is int intV) Do(intV);
else DoDefault(value);
}
void DoDefault(T value)
{
Console.WriteLine("Value: " + value);
}
void Do(int value)
{
Console.WriteLine("Int: " + value);
}
}
class Program
{
static void Main(string[] args)
{
A<int> a = new A<int>();
a.Do(1);
}
}
I mean personally I am happy to solve that using subclasses or interfaces, but since you really didn't want to....
|
|
|
|
|
Yeah I can't do that in this code because this code is inner loop critical and the "is" comparison is just a dog. I think "as" is faster, but still, it should be resolved at compile time.
I know it seems a minor quibble but this code may be used as part of a lexer. The lexing itself needs to be balls quick to be feasible.
also i'd be concerned about bugs this could introduce since i have to repeat a lot of code, but that's not a showstopper. it's just irritating
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
I think you probably underestimate the compiler here
generic code are some sort of IL that is used to generate code on demand (when a concrete type is used)
when, say A<int> type is created the compiler will see that
void Do<int>(int value) {
if (value is double d) {
}
if (value is int ii) Do (ii);
} some path will never happen and it will removed them from the concrete implementation created when the concrete type is created.
and the compiled code will become
void Do<int>(int value) {
Do (value);
}
|
|
|
|
|
The reason I underestimate the compiler maybe is the last the time I really examined IL was in the .NET 2.0-3.0 days and the compiler didn't do hardly anything for program optimization.
Microsoft's rationale seemed to be that JIT would take care of it, but JIT doesn't do whole program optimization. It can only do peephole optimization, so I don't know what they were thinking. My guess is it was an excuse due to deadlines.
So I don't trust the compiler that much. Maybe my information is old. The compiler certainly has been revamped since then.
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
I mean it's such an obvious static type check optimisation.. I haven't explicitly checked for it, but I would wage $5 on it!
void Do<int>(int value) {
if (value is int ii) {
}
if (value is double dd) {
}
}
|
|
|
|
|
based on my experience, I'd take that bet.
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
well.. my assembly is a bit rusty (or almost non existent) I will let you judge...
But here my test C# code
class A<T>
{
public void Do(T value)
{
if (value is int intV) Do(intV);
else DoDefault(value);
}
void DoDefault(T value)
{
Console.WriteLine("Value: " + value);
}
void Do(int value)
{
Console.WriteLine("Int: " + value);
}
}
class Program
{
static void Main(string[] args)
{
A<int> a = new A<int>();
a.Do(1);
A<double> b = new A<double>();
b.Do(1.0);
}
}
here is the code for a.Do(1) and b.Do(1.0) using go to disassembly in visual studio
a.Do(1);
00F70898 mov ecx,dword ptr [ebp-40h]<br />
00F7089B mov edx,1<br />
00F708A0 cmp dword ptr [ecx],ecx<br />
00F708A2 call 00F70478<br />
00F708A7 nop
b.Do(1.0);
00F708C3 fld qword ptr ds:[0F708E8h]<br />
00F708C9 sub esp,8<br />
00F708CC fstp qword ptr [esp]<br />
00F708CF mov ecx,dword ptr [ebp-44h]<br />
00F708D2 cmp dword ptr [ecx],ecx<br />
00F708D4 call 00F704A0<br />
00F708D9 nop
I think there is no (assembly) if statement and direct execution of the relevant if (type) branch....
|
|
|
|
|
there's a call in there that looks suspicious.
I'd need to see the IL, not the asm.
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
release code atrocious too...
|
|
|
|
|
yep. The jitter can only do so much with peephole optimization. That's the ugly truth. Still, the IL will tell the tale. ILDASM or visual studio's decompile option should be able to grab it.
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
well..
1. I was saying the compiler will remove obvious dead end code, make sense to check the assembly
2. what interesting is not the generic method's IL, but the concrete A<int> or A<double> code, which I have no clue where to see
at any rate, assembly code looks bad....
|
|
|
|
|
if i can see where that CALL in the asm leads i'd know, but viewing the IL is the only realistic way to tell where it leads
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
Generic Do<T>() method's IL:
.method public hidebysig instance void Do(!T 'value') cil managed
{
// Code size 58 (0x3a)
.maxstack 2
.locals init ([0] int32 intV,
[1] bool V_1)
IL_0000: nop
IL_0001: ldarg.1
IL_0002: box !T
IL_0007: isinst [mscorlib]System.Int32
IL_000c: brfalse.s IL_0022
IL_000e: ldarg.1
IL_000f: box !T
IL_0014: isinst [mscorlib]System.Int32
IL_0019: unbox.any [mscorlib]System.Int32
IL_001e: stloc.0
IL_001f: ldc.i4.1
IL_0020: br.s IL_0023
IL_0022: ldc.i4.0
IL_0023: stloc.1
IL_0024: ldloc.1
IL_0025: brfalse.s IL_0031
IL_0027: ldarg.0
IL_0028: ldloc.0
IL_0029: call instance void class ConsoleApp1.A`1<!T>::Do(int32)
IL_002e: nop
IL_002f: br.s IL_0039
IL_0031: ldarg.0
IL_0032: ldarg.1
IL_0033: call instance void class ConsoleApp1.A`1<!T>::DoDefault(!0)
IL_0038: nop
IL_0039: ret
} // end of method A`1::Do
|
|
|
|
|
yep, that's a call to the runtime to do a type check. See isinst?
i'll consider our bet a gentleman's bet =D
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
adding ugh - it's not only doing that, it has to box the value first!
i forgot about that. Ugh. It makes a copy of the int on the heap just to do a type check
No. Just no. Moral of this story is do not trust the C# compiler to significantly optimize your code.
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
I have to say I am a little confused..
They have numerous intelligent blog about performance...
They have fair performance comparison against C++ code with well know perf test with many close call and sometimes better performance...
Though when one occasionally check the IL or assembly it seems not really good...
All of that leave me quite bewildered....
|
|
|
|
|
last i checked it's about 30% slower and that's according to their own benchmarks. I expect real world performance to be somewhat worse, if only because of the unconscious tendency to want to test the fast parts of the code.
occasionally you get better performance because of the JITs ability to do smart register allocation but the performance difference between that and C++ is barely significant in virtually all cases, and it doesn't crop up as regularly as MS would perhaps suggest.
There used to be some really good in depth articles about .NET performance that covered a lot of this stuff but as .NET has matured, it seems there are less of these today.
I don't normally care about cycle counting, but I do when the code has to be tight. Here it does, in my case.
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
In general I've found a lot of my old habits I picked up in the 80s and 90s before i had access to really smart C++ compilers have served me well under .NET.
They say you don't have to be careful about heap allocation, and that's kind of true because of the way their garbage collected heap works, but it still costs. They claim the cost is "incrementing a pointer" - in reality that pointer gets incremented enough if forces the .NET host to do a garbage collection and reallocation which costs significantly. So basically all it's really doing is pushing the costs of each heap allocation down the road - and then "batches" the individual costs together when it does the mark, sweep and allocate.
They do too much heap allocation in .NET IMO. the IEnumerator pattern is a big culprit but not as much as boxing. Boxing/Unboxing just floors me. That slams the heap.
Fortunately the new reference types and stackalloc can alleviate this somewhat, but not nearly enough.
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
you know what? I'm still kind of curious about this but I think I'm scrapping the specialization.
I'm not sure what I'm going to do about maintenance though. =( This will almost double the code size.
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
|
Oops forget my previous example, I was confused.. the debug assembly code does indeed look atrocious...
Gotta try to check the release version
|
|
|
|
|
if there's a call in the resulting asm I'd need to see the IL to find out where it leads. I think the "is" comparison would result in a call to the CLR to type check
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
adding, my solution to the object creation was to have a method in the base class called CreateFA() that could be overloaded in order to force the base class to create the derived class. Unfortunately to get it to work I had to remove every static method that created objects from the base class =(
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
This has always worked.
You can't do that with static and/or non virtual method though, maybe that's what mislead you?!
|
|
|
|