Who says you can't beat the compiler? [part 1 of 2]
-
Umm ...
harold aptroot wrote:
The Just In Time compiler didn't even do such a bad job here...
... the Just in Time compiler takes as its input the IL code that is generated by the C# compiler or, in the case of the IL assembly language you hand crafted, the output of the IL assembler. So, actually, the JIT compiler hasn't done anything yet. :-\
-
I wrote it, the source is a bit long for here though since it includes a rudimentary assembler
What about writing an article? It would be interesting to see how you inject it in your application.
Giorgi Dalakishvili #region signature My Articles Browsing xkcd in a windows 7 way[^] #endregion
-
I haven't seen a compiler that can properly use SSE2 yet. Auto-vectorization often only works in trivial cases, in other cases packed instructions go unused. However, instead of dropping to assembler, you can to write C code using intrinsics - this way you only select the ASM instructions to use, and the compiler will pick the instruction ordering and register allocation for you. And unlike inline ASM code, intrinsics are portable between multiple C compilers and between x86 and x86-64. Also, your optimized code is not equivalent - it has a much lower floating point precision.
RSQRTPS
is (a lot) faster thanSQRTPS
, but has much less precision. You need to do an additional Newton-Raphson step to arrive somewhere close to normal float precision. Of course, the loss of precision may be acceptable in your case, but it's the reason compilers cannot do this optimization automatically. Moreover, the way your C# code is written, even thecvtss2sd
/cvtsd2ss
dance is mandatory for the compiler: you are dividing a double (1.0) by a double (Math.Sqrt result), so the compiler is not allowed to introduce additional rounding errors by rounding the intermediate result to float. You might have gotten more efficient code by writingfloat invLen = 1.0f / (float)Math.Sqrt(f.x * f.x + f.y * f.y + f.z * f.z);
Daniel Grunwald wrote:
Also, your optimized code is not equivalent - it has a much lower floating point precision.
I did make a note to that effect - the non-cheating version was still over 3 times as fast the C# version.
Daniel Grunwald wrote:
Moreover, the way your C# code is written, even the cvtss2sd/cvtsd2ss dance is mandatory for the compiler:
Fair enough. I made the change you suggested, and the result is:
movss xmm2,dword ptr [rsp+40h]
movss xmm0,dword ptr [rsp+40h]
mulss xmm2,xmm0
movss xmm1,dword ptr [rsp+44h]
movss xmm0,dword ptr [rsp+44h]
mulss xmm1,xmm0
addss xmm2,xmm1
movss xmm1,dword ptr [rsp+48h]
movss xmm0,dword ptr [rsp+48h]
mulss xmm1,xmm0
addss xmm2,xmm1
cvtss2sd xmm0,xmm2
sqrtsd xmm1,xmm0
cvtsd2ss xmm2,xmm1
movss xmm0,dword ptr [00000160h]
divss xmm0,xmm2
movss xmm1,dword ptr [rsp+40h]
mulss xmm1,xmm0
movss xmm2,dword ptr [rsp+44h]
mulss xmm2,xmm0
movss xmm3,dword ptr [rsp+48h]
mulss xmm3,xmm0The sqrt is still done in high precision. It got a tiny bit faster because of the lower precision divss instead of divsd. What it does now looks particularly silly to me though..
-
harold aptroot wrote:
ASM: 127836 // anyone? what happened here?
Instructions written into memory generated by VirtualAlloc will most likely cause a L1/L2 cache miss. The extra clock cycles were probably spent utilizing the TLB to find the physical memory offset. You can try using the prefetchnta instruction to move the memory into L1 if you want to avoid the initial cache miss. Keep in mind that prefetchnta is only a hint and will sometimes be ignored under certain conditions. Best Wishes, -David Delaune
-
What about writing an article? It would be interesting to see how you inject it in your application.
Giorgi Dalakishvili #region signature My Articles Browsing xkcd in a windows 7 way[^] #endregion
This would be interesting to see, and there are not enough articles about working with the assembler. :thumbsup: if not I think a more detailed article based off of this thread would be in order (or a tip/trick) :)
I'd blame it on the Brain farts.. But let's be honest, it really is more like a Methane factory between my ears some days then it is anything else... -"The conversations he was having with himself were becoming ominous."-.. On the radio...
-
What about writing an article? It would be interesting to see how you inject it in your application.
Giorgi Dalakishvili #region signature My Articles Browsing xkcd in a windows 7 way[^] #endregion
Would this be enough for an article? And it also got some bad reactions.. Anyway, I assemble the code in the string into an array of bytes, then I VirtualAlloc a big enough piece of memory (with EXECUTE_READWRITE), then I copy the bytes to that memory, and then I use Marshal.GetDelegateForFunctionPointer. All of that including importing VirtualAlloc and VirtualFree and the enums that go with them is just 104 Lines of Code, but that's still a bit long to just post here..
-
Daniel Grunwald wrote:
Also, your optimized code is not equivalent - it has a much lower floating point precision.
I did make a note to that effect - the non-cheating version was still over 3 times as fast the C# version.
Daniel Grunwald wrote:
Moreover, the way your C# code is written, even the cvtss2sd/cvtsd2ss dance is mandatory for the compiler:
Fair enough. I made the change you suggested, and the result is:
movss xmm2,dword ptr [rsp+40h]
movss xmm0,dword ptr [rsp+40h]
mulss xmm2,xmm0
movss xmm1,dword ptr [rsp+44h]
movss xmm0,dword ptr [rsp+44h]
mulss xmm1,xmm0
addss xmm2,xmm1
movss xmm1,dword ptr [rsp+48h]
movss xmm0,dword ptr [rsp+48h]
mulss xmm1,xmm0
addss xmm2,xmm1
cvtss2sd xmm0,xmm2
sqrtsd xmm1,xmm0
cvtsd2ss xmm2,xmm1
movss xmm0,dword ptr [00000160h]
divss xmm0,xmm2
movss xmm1,dword ptr [rsp+40h]
mulss xmm1,xmm0
movss xmm2,dword ptr [rsp+44h]
mulss xmm2,xmm0
movss xmm3,dword ptr [rsp+48h]
mulss xmm3,xmm0The sqrt is still done in high precision. It got a tiny bit faster because of the lower precision divss instead of divsd. What it does now looks particularly silly to me though..
harold aptroot wrote:
The sqrt is still done in high precision. It got a tiny bit faster because of the lower precision divss instead of divsd. What it does now looks particularly silly to me though..
Yes, that's pretty silly; unless there are some special cases related to NaNs/infinities, it should be possible to optimize
cvtss2sd/sqrtsd/cvtsd2ss
tosqrtss
. And if it's not possible, a float-overload should be added to Math.Sqrt. The double/triple loads also seem crazy, I thought the x64 JIT was optimizing those. At least I heard about redundant loads being eliminated on x64 (this affects multi-threaded semantics in some cases, e.g. unsynchronizedbool stop;
), so I wonder why it doesn't do that in your case. However no compiler will introduce packed instructions in this case - this is beyond what auto-vectorization can do for you. So in general, you can always beat a good compiler in floating point math. And beating the .NET JIT is even easier. -
More fool me for not looking closely enough to see that you were writing native assembly, not IL assembly, and jumping to completely invalid conclusions. My apologies for being so careless presumptuous as to think you didn't know what you were saying.
-
More fool me for not looking closely enough to see that you were writing native assembly, not IL assembly, and jumping to completely invalid conclusions. My apologies for being so careless presumptuous as to think you didn't know what you were saying.
-
harold aptroot wrote:
The sqrt is still done in high precision. It got a tiny bit faster because of the lower precision divss instead of divsd. What it does now looks particularly silly to me though..
Yes, that's pretty silly; unless there are some special cases related to NaNs/infinities, it should be possible to optimize
cvtss2sd/sqrtsd/cvtsd2ss
tosqrtss
. And if it's not possible, a float-overload should be added to Math.Sqrt. The double/triple loads also seem crazy, I thought the x64 JIT was optimizing those. At least I heard about redundant loads being eliminated on x64 (this affects multi-threaded semantics in some cases, e.g. unsynchronizedbool stop;
), so I wonder why it doesn't do that in your case. However no compiler will introduce packed instructions in this case - this is beyond what auto-vectorization can do for you. So in general, you can always beat a good compiler in floating point math. And beating the .NET JIT is even easier.I experimented a bit - the triple loads do not happen when Float3 is a class. The code then becomes:
movss xmm5,dword ptr [rax+8]
movaps xmm1,xmm5
mulss xmm1,xmm5
movss xmm4,dword ptr [rax+0Ch]
movaps xmm0,xmm4
mulss xmm0,xmm4
addss xmm1,xmm0
movss xmm3,dword ptr [rax+10h]
movss xmm0,xmm3
mulss xmm0,xmm3
addss xmm1,xmm0
cvtss2sd xmm0,xmm1
sqrtsd xmm1,xmm0
cvtsd2ss xmm2,xmm1
movss xmm8,dword ptr [00000128h]
divss xmm8,xmm2
movss xmm7,xmm8
mulss xmm7,xmm5
movss xmm6,xmm8
mulss xmm6,xmm4
mulss xmm8,xmm3However, in total the code gets a bit slower.
-
Impressive... but I call that someone needs to check around to see if there's a life somewhere they can grab!! :laugh:
I don't have ADHD, I have ADOS... Attention Deficit oooh SHINY!! If you like cars, check out the Booger Mobile blog | If you feel generous - make a donation to Camp Quality!!
I, for one, am surprised that anyone still talks about doing anything with assembler these days. Too many programmers I run into don't even know how to do arithmetic in hex or octal without a calculator.
-
Is it just my perception, or is this actually the first legitimate technical article posted in the lounge? I give it a 5, btw.
-
Would this be enough for an article? And it also got some bad reactions.. Anyway, I assemble the code in the string into an array of bytes, then I VirtualAlloc a big enough piece of memory (with EXECUTE_READWRITE), then I copy the bytes to that memory, and then I use Marshal.GetDelegateForFunctionPointer. All of that including importing VirtualAlloc and VirtualFree and the enums that go with them is just 104 Lines of Code, but that's still a bit long to just post here..
It would be interesting if you could write an article about this - I am already giving it a five ;P
Interested in Machine Learning in .NET? Check my article about Support Vector Machines in C# in Handwriting Recognition Revisited: Kernel Support Vector Machines using the Accord.NET Framework.
-
It would be interesting if you could write an article about this - I am already giving it a five ;P
Interested in Machine Learning in .NET? Check my article about Support Vector Machines in C# in Handwriting Recognition Revisited: Kernel Support Vector Machines using the Accord.NET Framework.
-
Of course you can beat the compiler, the question is whether it's worth the time and effort. (Of course if you were really fanatical, you'd write this in native assembly and load either the 32-bit or 64-bit [or ARM or whatever] DLL and use that.)
The output from his Asm class is dynamically generated native code. He marshals a delegate that points into his native method and calls it. He posted a brief description of his Asm class some posts up.