(Edit 5 Jan 2011: New Compression results section and small crinkler x86 decompressor analysis)
If you are not familiar with 4k intros, you may wonder how things are organized at the executable level to achieve this kind of packing-performance. Probably the most important and essential aspect of 4k-64k intros is the compressor, and surprisingly, 4k intros have been well equipped for the past five years, as Crinkler is the best compressor developed so far for this category. It has been created by Blueberry (Loonies) and Mentor (tbc), two of the greatest demomakers around.
Last year, I started to learn a bit more about the compression technique used in Crinkler. It started from some pouet's comments that intrigued me, like "crinkler needs several hundred of mega-bytes to compress/decompress a 4k intros" (wow) or "when you want to compress an executable, It can take hours, depending on the compressor parameters"... I observed also bad comrpession result, while trying to convert some part of C++ code to asm code using crinkler... With this silly question, I realized that in order to achieve better compression ratio, you better need a code that is comrpession friendly but is not necessarily smaller. Or in other term, the smaller asm code is not always the best candidate for better compression under crinkler... so right, I needed to understand how crinkler was working in order to code crinkler-friendly code...
I just had a basic knowledge about compression, probably the last book I bought about compression was more than 15 years ago to make a presentation about jpeg compression for a physics courses (that was a way to talk about computer related things in a non-computer course!)... I remember that I didn't go further in the book, and stopped just before arithmetic encoding. Too bad, that's exactly one part of crinkler's compression technique, and has been widely used for the past few years (and studied for the past 40 years!), especially in compressors like H.264!
So wow, It took me a substantial amount of time to jump again on the compressor's train and to read all those complicated-statistical articles to understand how things are working... but that was worth it! In the same time, I spent a bit of my time to dissect crinkler's decompressor, extract the code decompressor in order to comment it and to compare its implementation with my little-own-test in this field... I had a great time to do this, although, in the end, I found that whatever I could do, under 4k, Crinkler is probably the best compressor ever.
You will find here an attempt to explain a little bit more what's behind Crinkler. I'm far from being a compressor expert, so if you are familiar with context-modeling, this post may sounds a bit light, but I'm sure It could be of some interest for people like me, that are discovering things like this and want to understand how they make 4k intros possible!
Update your bookmarks! This blog is now hosted on http://xoofx.com/blog
Wednesday, December 29, 2010
Wednesday, December 1, 2010
Official release of SharpDX 1.0
After three months of intense development, I'm really excited to announce the availability of SharpDX 1.0 , a new platform independent .Net managed DirectX API, directly generated from DirectX SDK headers.
This first version can be considered as stable. The Direct3D10 / Direct3D10.1 API has been entirely tested on a large 3D engine that was using previously SlimDX (thanks patapom!). Migration was quite straightforward, with tiny minor changes to the engine's code.
The key features and benefits of this new API are:
- API is generated from DirectX SDK headers : meaning a complete and reliable API and an easy support for future API.
- Full support for the following DirectX API:
- Direct3D10
- Direct3D10.1
- Direct3D11
- Direct2D1 (including custom rendering, tessellation callbacks)
- DirectWrite (including custom client callbacks)
- D3DCompiler
- DXGI
- DXGI 1.1
- DirectSound
- XAudio2
- XAPO
- An integrated math API directly ported from SlimMath
- Pure managed .NET API, platform independent : assemblies are compiled with AnyCpu target. You can run your code on a x64 or a x86 machine with the same assemblies, without recompiling your project.
- Lightweight individual assemblies : a core assembly - SharpDX - containing common classes and an assembly for each subgroup API (Direct3D10, Direct3D11, DXGI, D3DCompiler...etc.). Assemblies are also lightweight.
- C++/CLI Speed : the framework is using a genuine way to avoid any C++/CLI while still achieving comparable performance.
- API naming convention mostly compatible with SlimDX API.
- Raw DirectX object life management : No overhead of ObjectTable or RCW mechanism, the API is using direct native management with classic COM method "Release".
- Easily mergeable / obfuscatable : If you need to obfuscate SharpDX assemblies, they are easily obfusctable due to the fact the framework is not using any mixed assemblies. You can also merge SharpDX assemblies into a single exe using with tool like ILMerge.
Instead of providing a monolithic assembly, SharpDX is providing lightweight individual and interdependent assemblies. All SharpDX assemblies are dependent from the core SharpDX assembly. You just need to add the required assemblies to your project, without embedding the whole DirectX API stack. Here is a chart that explains SharpDX assembly dependencies:
Next versions will provide support for DirectInput, XInput, X3DAudio, XACT3.
About performance
Someone asked me how SharpDX compares to SlimDX in terms of performance. Here is a micro-benchmark on two methods, ID3D10Device1::GetFeatureLevel (alias Device.FeatureLevel) and ID3D10Device::CheckCounterInfo (alias Device.GetCounterCapabilities).The test consist of 100,000,000 calls on each methods (inside a for, with (10 calls to device.FeatureLevel) * 10,000,000 times) and is repeated 10 times and averaged. Repeated two times.
Method | SlimDX | SharpDX | SharpDX vs SlimDX |
device.FeatureLevel | 3700 | 3650 | 1,37% |
device.GetCounterCapabilities() | 4684 | 4259 | 9,98% |
For FeatureLevel, the test was sometimes around +/-0.5%.
For GetCounterCapabilities(), the main difference between SharpDX and SlimDX implementation is that SlimDX perform a copy from the native struct to .Net struct while SharpDX is directly passing a pointer to the .Net struct.
This test is of course a micro benchmark and doesn't reflect a real-world usage. Some part of the API could be in favor of SlimDX, but I'm pretty confident that SharpDX is much more consistent in the way structures are passed to the native functions, avoiding as much as possible marshaling structures that doesn't need any custom marshaling (unlike SlimDX that is performing most of a time a marshaling between .Net/Native structure, besides they are binary compatible).
Next?
Finally, I'm going to be able to use this project to make some demos with it! Next target is to develop a XNA like based framework based on SharpDX.Direct3D11.Stay tuned!
Thursday, November 18, 2010
SharpDX, a new managed .Net DirectX API available
If you have followed my previous work on a new .NET API for Direct3D 11, I proposed SlimDX team this solution for the v2 of their framework, joined their team around one month ago, and I was actively working to widen the coverage of the DirectX API. I have been able to extend the API coverage almost up to the whole API, being able to develop Direct2D samples, as well as XAudio2 and XAPO samples using it. But due to some incompatible directions that the SlimDX team wanted to follow, I have decided to release also my work under a separate project called SharpDX. Now, you may wonder why I'm releasing this new API under a separate project from SlimDX?
Well, I have been working really hard on this from the beginning of September, and I explained why in my previous post about Direct3D 11. I have checked-in lots of code under the v2 branch on SlimDX, while having lots of discussion with the team (mostly Josh which is mostly responsible for v2) on their devel mailing list. The reason I'm leaving SlimDX team is that It was in fact not clear for me that I was not enrolled as part of the decision for the v2 directions, although I was bringing a whole solution (by "whole", I mean a large proof of concept, not something robust, finished). At some point, Josh told me that Promit, Mike and himself, co-founders of SlimDX, were the technical leaders of this project and they would have the last word on the direction as well as for decisions on the v2 API.
Unfortunately, I was not expecting to work in such terms with them, considering that I had already made 100% of the whole engineering prototype for the next API. From the last few days, we had lots of -small- technical discussions, but for some of them, I clearly didn't agree about the decisions that were taken, whatever the arguments I was trying to give to them. This is a bit of disappointment for me, but well, that's life of open source projects. This is their project and they have other plans for it. So, I have decided to release the project on my own with SharpDX although you will see that the code is also currently exactly the same on the v2 branch of SlimDX (of course, because until yesterday, I was working on the SlimDX v2 branch).
But things are going to change for both projects : SlimDX is taking the robust way (for which I agree) but with some decisions that I don't agree (in terms of implementation and direction). Although, as It may sound weird, SharpDX is not intended to compete with SlimDX v2 : They have clearly a different scope (supporting for example Direct3D 9, which I don't really care in fact), different target and also different view on exposing the API and a large existing community already on SlimDX. So SharpDX is primarily intended for my own work on demomaking. Nothing more. I'm releasing it, because SlimDX v2 is not going to be available soon, even for an alpha version. On my side, I'm considering that the current state (although far to be as clean as It should be) of the SharpDX API is usable and I'm going to use it on my own, while improving the generator and parser, to make the code safer and more robust.
So, I did lots of work to bring new API into this system, including :
This simple example is producing the following ouput :
which is pretty cool, considering the amount of code (although the Direct3D 10 and D2D initialization part would give a larger code), I found this to be much simpler than the gluTessellation API.
You will find also some other samples, like the XAudio2 ones, generating a synthesized sound with the usage of the reverb, and even some custom XAPO sound processors!
You can grab those samples on SharpDX code repository (there is a SharpDXBinAndSamples.zip with a working solutions with all the samples I have been developing so far, with also MiniTris sample from SlimDX).
Well, I have been working really hard on this from the beginning of September, and I explained why in my previous post about Direct3D 11. I have checked-in lots of code under the v2 branch on SlimDX, while having lots of discussion with the team (mostly Josh which is mostly responsible for v2) on their devel mailing list. The reason I'm leaving SlimDX team is that It was in fact not clear for me that I was not enrolled as part of the decision for the v2 directions, although I was bringing a whole solution (by "whole", I mean a large proof of concept, not something robust, finished). At some point, Josh told me that Promit, Mike and himself, co-founders of SlimDX, were the technical leaders of this project and they would have the last word on the direction as well as for decisions on the v2 API.
Unfortunately, I was not expecting to work in such terms with them, considering that I had already made 100% of the whole engineering prototype for the next API. From the last few days, we had lots of -small- technical discussions, but for some of them, I clearly didn't agree about the decisions that were taken, whatever the arguments I was trying to give to them. This is a bit of disappointment for me, but well, that's life of open source projects. This is their project and they have other plans for it. So, I have decided to release the project on my own with SharpDX although you will see that the code is also currently exactly the same on the v2 branch of SlimDX (of course, because until yesterday, I was working on the SlimDX v2 branch).
But things are going to change for both projects : SlimDX is taking the robust way (for which I agree) but with some decisions that I don't agree (in terms of implementation and direction). Although, as It may sound weird, SharpDX is not intended to compete with SlimDX v2 : They have clearly a different scope (supporting for example Direct3D 9, which I don't really care in fact), different target and also different view on exposing the API and a large existing community already on SlimDX. So SharpDX is primarily intended for my own work on demomaking. Nothing more. I'm releasing it, because SlimDX v2 is not going to be available soon, even for an alpha version. On my side, I'm considering that the current state (although far to be as clean as It should be) of the SharpDX API is usable and I'm going to use it on my own, while improving the generator and parser, to make the code safer and more robust.
So, I did lots of work to bring new API into this system, including :
- Direct3D 10
- Direct3D 10.1
- Direct3D 11
- Direct2D 1
- DirectWrite
- DXGI
- DXGI 1.1
- D3DCompiler
- DirectSound
- XAudio2
- XAPO
using System; using System.Drawing; using SharpDX.Direct2D1; using SharpDX.Samples; namespace TessellateApp { ////// Direct2D1 Tessellate Demo. /// public class Program : Direct2D1DemoApp, TessellationSink { EllipseGeometry Ellipse { get; set; } PathGeometry TesselatedGeometry{ get; set; } GeometrySink GeometrySink { get; set; } protected override void Initialize(DemoConfiguration demoConfiguration) { base.Initialize(demoConfiguration); // Create an ellipse Ellipse = new EllipseGeometry(Factory2D, new Ellipse(new PointF(demoConfiguration.Width/2, demoConfiguration.Height/2), demoConfiguration.Width/2 - 100, demoConfiguration.Height/2 - 100)); // Populate a PathGeometry from Ellipse tessellation TesselatedGeometry = new PathGeometry(Factory2D); GeometrySink = TesselatedGeometry.Open(); // Force RoundLineJoin otherwise the tesselated looks buggy at line joins GeometrySink.SetSegmentFlags(PathSegment.ForceRoundLineJoin); // Tesselate the ellipse to our TessellationSink Ellipse.Tessellate(1, this); // Close the GeometrySink GeometrySink.Close(); } protected override void Draw(DemoTime time) { base.Draw(time); // Draw the TextLayout RenderTarget2D.DrawGeometry(TesselatedGeometry, SceneColorBrush, 1, null); } void TessellationSink.AddTriangles(Triangle[] triangles) { // Add Tessellated triangles to the opened GeometrySink foreach (var triangle in triangles) { GeometrySink.BeginFigure(triangle.Point1, FigureBegin.Filled); GeometrySink.AddLine(triangle.Point2); GeometrySink.AddLine(triangle.Point3); GeometrySink.EndFigure(FigureEnd.Closed); } } void TessellationSink.Close() { } [STAThread] static void Main(string[] args) { Program program = new Program(); program.Run(new DemoConfiguration("SharpDX Direct2D1 Tessellate Demo")); } } }
This simple example is producing the following ouput :
which is pretty cool, considering the amount of code (although the Direct3D 10 and D2D initialization part would give a larger code), I found this to be much simpler than the gluTessellation API.
You will find also some other samples, like the XAudio2 ones, generating a synthesized sound with the usage of the reverb, and even some custom XAPO sound processors!
You can grab those samples on SharpDX code repository (there is a SharpDXBinAndSamples.zip with a working solutions with all the samples I have been developing so far, with also MiniTris sample from SlimDX).
Wednesday, November 3, 2010
Hacking Direct2D to use directly Direct3D 11 instead of Direct3D 10.1 API
Disclaimer about this hack: This hack was nothing more than a proof of concept and I *really* don't have time to dig into any kind of bugs related to it.
[Edit]13 Jan 2011, After Windows Update KB2454826, this hack was not working. I have patched the sample to make it work again. Of course, you shouldn't consider this hack for anykind of production use. Use the standard DXGI shared sync keyed mutex instead. This hack is just for fun![/Edit]
If you know Direct3D 11 and Direct 2D - they were released almost at the same time - you already know that there is a huge drawback to use Direct 2D : It's in fact only working with Direct3D 10.1 API (although It's working with older hardware thanks to the new feature level capability of the API).
From a coding user point of view, this is really disappointing that such a good API doesn't rely on the latest Direct3D API... moreover when you know that the Direct3D 11 API is really close to the Direct3D 10.1 API... In the end, more work are required for a developer that would like to work with Direct3D 11, as It doesn't have any more Text API for example, meaning that in D3D11, you have to do it yourself, which isn't a huge task itself, if you go to the easy precalculated-texture-of-fonts generated by some GDI+ calls or whatever, but still... this is annoying specially when you need to display some information/FPS on the screen and you can't wait to build a nice font-texture-based system...
I'm not completely fair with Direct2D interoperability with Direct3D 11 : there is in fact a well known solution proposed by one guy from DirectX Team that imply the use of DXGI mutex to synchronized a surface shared between D3D10.1 and D3D11. I was expecting this issue to be solved in some DirectX SDK release this year, but It seems that there is no plan to release in the near future an update for Direct2D (see my question in the comments and the anwser...)... WP7 and XNA are probably getting much more attention here...
So last week, I took some time on the Direct2D API and found that It's in fact fairly easy to hack Direct2D and redirect all the D3D10.1 API calls to a real Direct3D 11 instance... and this is a pretty cool news! Here is the story of this little hack...
[Edit]13 Jan 2011, After Windows Update KB2454826, this hack was not working. I have patched the sample to make it work again. Of course, you shouldn't consider this hack for anykind of production use. Use the standard DXGI shared sync keyed mutex instead. This hack is just for fun![/Edit]
If you know Direct3D 11 and Direct 2D - they were released almost at the same time - you already know that there is a huge drawback to use Direct 2D : It's in fact only working with Direct3D 10.1 API (although It's working with older hardware thanks to the new feature level capability of the API).
From a coding user point of view, this is really disappointing that such a good API doesn't rely on the latest Direct3D API... moreover when you know that the Direct3D 11 API is really close to the Direct3D 10.1 API... In the end, more work are required for a developer that would like to work with Direct3D 11, as It doesn't have any more Text API for example, meaning that in D3D11, you have to do it yourself, which isn't a huge task itself, if you go to the easy precalculated-texture-of-fonts generated by some GDI+ calls or whatever, but still... this is annoying specially when you need to display some information/FPS on the screen and you can't wait to build a nice font-texture-based system...
I'm not completely fair with Direct2D interoperability with Direct3D 11 : there is in fact a well known solution proposed by one guy from DirectX Team that imply the use of DXGI mutex to synchronized a surface shared between D3D10.1 and D3D11. I was expecting this issue to be solved in some DirectX SDK release this year, but It seems that there is no plan to release in the near future an update for Direct2D (see my question in the comments and the anwser...)... WP7 and XNA are probably getting much more attention here...
So last week, I took some time on the Direct2D API and found that It's in fact fairly easy to hack Direct2D and redirect all the D3D10.1 API calls to a real Direct3D 11 instance... and this is a pretty cool news! Here is the story of this little hack...
Tuesday, October 26, 2010
Implementing an unmanaged C++ interface callback in C#/.Net
Ever wanted to implement a C++ interface callback in a managed C# application? Well, although that's not so hard, this is a solution that you will probably hardly find over the Internet... the most common answer you will get is that it's not possible to do it or you should use C++/CLI in order to achieve it... In fact, in C#, you can only implement a C function delegate through the use of
In my previous post about implementing a new DirectX fully managed API, I forgot to mention the case of interfaces callbacks. There are not so many cases in Direct3D 11 API where you need to implement a callback. You will more likely find more use-cases in audio APIs like XAudio2, but in Direct3D 11, afaik, you will only find 3 interfaces that are used for callback:
Marshal.GetFunctionPointerForDelegate
but you won't find anything like Marshal.GetInterfacePointerFromInterface
. You may wonder why do I need such a thing? In my previous post about implementing a new DirectX fully managed API, I forgot to mention the case of interfaces callbacks. There are not so many cases in Direct3D 11 API where you need to implement a callback. You will more likely find more use-cases in audio APIs like XAudio2, but in Direct3D 11, afaik, you will only find 3 interfaces that are used for callback:
- ID3DInclude which is used by D3DCompiler API in order to provide a callback for includes while using preprocessor or compiler API (see for example D3DCompile).
- ID3DX11DataLoader and ID3DX11DataProcessor, which are used by some D3DX functions in order to perform asynchronous loading/processing of texture resources. The nice thing about C# is that those interfaces are useless, as it is much easier and trivial to directly implement them in C# instead
Saturday, October 23, 2010
High performance memcpy gotchas in C#
(Edit 8 Jan 2011: Update protocol test with Buffer.BlockCopy)
(Edit 11 Oct 2012: Please vote for the x86 cpblk deficiency on Microsoft Connect)
Following my last post about an interesting use of the "cpblk" IL instruction as an unmanaged memcpy replacement, I have to admit that I didn't take the time to carefully verify that performance is actually better. Well, I was probably too optimistic... so I have made some tests and the results are very surprising and not expected to be like these...
When dealing with 3D calculations, large buffers of textures, audio synthesizing or whatever requires a memcpy and interaction with unmanaged world, you will most notably end up with a call to an unmanaged functions like this one:
In this test, I'm going to compare this implementation with 4 challengers :
The naive handmade memcpy is nothing more than this code (not to be the best implem ever but at least safe for any kind of buffer size):
For the x64 architecture:
Graph comparison only for cpblk, memcpy and CustomCopy:
Don't be afraid about the performance drop for most of the implem... It's mostly due to cache missing and copying around different 4k pages.
The most surprising result here is the poor performance of cpblk IL instruction in x86 mode compare to the best one in x64 which is... cpblk. So to summarize:
One important consequence of this is when you are developping a C++/CLI and calling a memcpy from a managed function... It will end up in a cpblk copy functions... which is almost the worst case on x86 platforms... so be careful if you are dealing with this kind of issue. To avoir this, you have to force the compiler to use the function from the MSVCRTxx.dll.
Of course, the memcpy is platform dependent, which would not be an option for all...
Also, I didn't perform this test on a CLR 2 runtime... we could be surprised as well... There is also one thing that I should try against a pure C++ memcpy using the optimized SSE2 version that is shipped with later msvcrt.
You can download the VS2010 project from here
(Edit 11 Oct 2012: Please vote for the x86 cpblk deficiency on Microsoft Connect)
Following my last post about an interesting use of the "cpblk" IL instruction as an unmanaged memcpy replacement, I have to admit that I didn't take the time to carefully verify that performance is actually better. Well, I was probably too optimistic... so I have made some tests and the results are very surprising and not expected to be like these...
The memcpy protocol test in C#
When dealing with 3D calculations, large buffers of textures, audio synthesizing or whatever requires a memcpy and interaction with unmanaged world, you will most notably end up with a call to an unmanaged functions like this one:
[DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false), SuppressUnmanagedCodeSecurity] public static unsafe extern void* CopyMemory(void* dest, void* src, ulong count);
In this test, I'm going to compare this implementation with 4 challengers :
- The cpblk IL instruction
- A handmade memcpy function
- Array.Copy, although It's not relevant because they don't have the same scope. Array.Copy is managed only for arrays only while memcpy is used to copy portion of datas between managed-unmanaged as well as unmanaged-unmanaged memory.
- Marshal.Copy, same as Array.Copy
- Buffer.BlockCopy, which is working on managed array but is working with a byte size block copy.
The naive handmade memcpy is nothing more than this code (not to be the best implem ever but at least safe for any kind of buffer size):
static unsafe void CustomCopy(void * dest, void* src, int count) { int block; block = count >> 3; long* pDest = (long*)dest; long* pSrc = (long*)src; for (int i = 0; i < block; i++) { *pDest = *pSrc; pDest++; pSrc++; } dest = pDest; src = pSrc; count = count - (block << 3); if (count > 0) { byte* pDestB = (byte*) dest; byte* pSrcB = (byte*) src; for (int i = 0; i < count; i++) { *pDestB = *pSrcB; pDestB++; pSrcB++; } } }
Results
For the x86 architecture, results are expressed as a throughput in Mo/s - higher is better, blocksize is in bytes :BlockSize | x86-cpblk | x86-memcpy | x86-CustomCopy | x86-Array.Copy | x86-Marshal.Copy | x86-BlockCopy |
4 | 146 | 458 | 470 | 85 | 81 | 150 |
8 | 294 | 843 | 1122 | 168 | 167 | 298 |
16 | 587 | 1628 | 1904 | 306 | 327 | 577 |
32 | 950 | 1876 | 3184 | 631 | 558 | 1079 |
64 | 1451 | 3316 | 4295 | 1205 | 1059 | 1981 |
128 | 2245 | 5161 | 4848 | 2176 | 1933 | 3386 |
256 | 4353 | 7032 | 5333 | 3699 | 3386 | 5333 |
512 | 8205 | 13617 | 5517 | 5663 | 6666 | 7441 |
1024 | 13617 | 20000 | 6666 | 7710 | 12075 | 9275 |
2048 | 18823 | 24615 | 7191 | 9142 | 16842 | 9552 |
4096 | 2922 | 7529 | 5663 | 10491 | 7032 | 11034 |
8192 | 2990 | 7804 | 5714 | 11228 | 7441 | 11636 |
16384 | 2857 | 7901 | 5614 | 9142 | 7619 | 10322 |
32768 | 2379 | 6736 | 5333 | 8101 | 6666 | 8205 |
65536 | 2379 | 6808 | 5470 | 8205 | 6808 | 8205 |
131072 | 2509 | 17777 | 5818 | 8101 | 17777 | 8101 |
262144 | 2500 | 11636 | 5423 | 7032 | 11428 | 7111 |
524288 | 2539 | 11428 | 5423 | 7111 | 11428 | 7111 |
1048576 | 2539 | 11428 | 5470 | 7032 | 11428 | 7111 |
2097152 | 2529 | 11428 | 5333 | 7032 | 11034 | 6881 |
For the x64 architecture:
BlockSize2 | x64-cpblk | x64-memcpy | x64-CustomCopy | x64-Array.Copy | x64-Marshal.Copy | x64-BlockCopy |
4 | 583 | 346 | 599 | 99 | 111 | 219 |
8 | 1509 | 770 | 1876 | 212 | 224 | 469 |
16 | 2689 | 1451 | 3316 | 417 | 422 | 903 |
32 | 4705 | 2666 | 5000 | 802 | 864 | 1739 |
64 | 8205 | 4812 | 7272 | 1568 | 1748 | 3350 |
128 | 13333 | 8101 | 9014 | 3004 | 3184 | 6037 |
256 | 18823 | 11428 | 10000 | 5470 | 5245 | 8648 |
512 | 22068 | 16000 | 10491 | 9014 | 9552 | 13913 |
1024 | 22857 | 19393 | 7356 | 13333 | 13617 | 16842 |
2048 | 23703 | 21333 | 7710 | 17297 | 17777 | 20645 |
4096 | 23703 | 22068 | 7804 | 19393 | 20000 | 21333 |
8192 | 23703 | 22857 | 7619 | 22068 | 22068 | 22857 |
16384 | 23703 | 22857 | 7804 | 17297 | 21333 | 18285 |
32768 | 16410 | 16410 | 7710 | 12800 | 16000 | 12800 |
65536 | 13061 | 14883 | 7710 | 13061 | 14545 | 13061 |
131072 | 14222 | 13913 | 7710 | 12800 | 13617 | 12800 |
262144 | 5000 | 5039 | 7032 | 7901 | 5000 | 7804 |
524288 | 5079 | 5000 | 7356 | 8205 | 5079 | 7804 |
1048576 | 4885 | 4885 | 7272 | 7441 | 4671 | 7529 |
2097152 | 5039 | 5079 | 7272 | 7619 | 5000 | 7710 |
Graph comparison only for cpblk, memcpy and CustomCopy:
Don't be afraid about the performance drop for most of the implem... It's mostly due to cache missing and copying around different 4k pages.
Conclusion
Don't trust your .NET VM, check your code on both x86 and x64. It's interesting to see how much the same task is implemented differently inside the CLR (see Marshal.Copy vs Array.Copy vs Buffer.Copy)The most surprising result here is the poor performance of cpblk IL instruction in x86 mode compare to the best one in x64 which is... cpblk. So to summarize:
- On x86, you should better use a memcpy function
- On x64, you should better use a cpblk function, which is performing better from small size (twice faster than memcpy) to large size.
One important consequence of this is when you are developping a C++/CLI and calling a memcpy from a managed function... It will end up in a cpblk copy functions... which is almost the worst case on x86 platforms... so be careful if you are dealing with this kind of issue. To avoir this, you have to force the compiler to use the function from the MSVCRTxx.dll.
Of course, the memcpy is platform dependent, which would not be an option for all...
Also, I didn't perform this test on a CLR 2 runtime... we could be surprised as well... There is also one thing that I should try against a pure C++ memcpy using the optimized SSE2 version that is shipped with later msvcrt.
You can download the VS2010 project from here
Subscribe to:
Posts (Atom)