Thursday, November 12, 2009

Fixed used with anonymous delegates is tricky

I'm back (5 minutes later) to continue trying to understand a mystery with fixed. At work our problem didn't just involve fixed(), but also the usage of fixed pointers inside an anonymous delegate defined and called within the scope of the fixed block.

Andreas came up with a nice test that surprised us both when we saw the disassembly. Here is the code:


private delegate void Action();
private unsafe void FixedIsColdComfortToDelegates(int runNumber)
{
byte[] array = new byte[1];
fixed(byte *p = array)
{
Action action = () => { *p = 22; };
Thread.Sleep(10);
action();
}
if(22 != array[0])
{
Console.WriteLine("Experienced failure, run {0}!", runNumber);
}
}


The disassembly was interesting, basically the fixed() statement was ignored! The question is why. Ultimately I found out the bug:


You can cause a fixed statement to be ignored by the JIT compiler if you simply include an anonymous delegate in the fixed block which references the fixed pointer. There is no need to run the delegate. Any additional usages of the fixed pointer in the fixed block will not prevent the fixed block from being ignored.


Here is my test program:


using System;
using System.Threading;

namespace FixedTest
{
class FixedProblem
{
private unsafe void Changer(byte *p)
{
// Do some allocations
byte[] bigarray = new byte[1024*50];
bigarray[3] = 34;
GC.Collect();
*p = bigarray[3];
}

private unsafe void NormalFixedStatement(int runNumber)
{
byte[] array = new byte[100];
fixed(byte *p = array)
{
Changer(p + 1); // will change array[1] to 34
Thread.Sleep(10);
*p = 10;
}

if(array[1] != 34)
{
Console.WriteLine("Experienced failure, run {0}", runNumber);
}
}

private unsafe void NullifiedFixedStatement(int runNumber)
{
byte[] array = new byte[100];
fixed (byte* p = array)
{
Action action = () => { *p = 10; }; // This destroys the fixed statement
Changer(p + 1); // will change array[1] to 34
Thread.Sleep(10);
*p = 10; // but p isn't fixed!
}

if (array[1] != 34)
{
Console.WriteLine("Experienced failure, run {0}", runNumber);
}
}

static void Main(string[] args)
{
FixedProblem p = new FixedProblem();

int count = 500;

Console.WriteLine("First run a function with a fixed statement and no anonymous delegate that uses the fixed pointer");
for (int i = 0; i < count; i++)
{
p.NormalFixedStatement(i);
}

Console.WriteLine("Now run a function with a fixed statement and an anonymous delegate that uses the fixed pointer");
for (int i = 0; i < count; i++)
{
p.NullifiedFixedStatement(i);
}
}
}
}


Here is the output:

C:\FixedTest\bin\Release>fixedtest.exe
First run a function with a fixed statement and no anonymous delegate
that uses the fixed pointer.


Now run a function with a fixed statement and an anonymous delegate that
uses the fixed pointer.

Experienced failure, run 4
Experienced failure, run 8
Experienced failure, run 12
Experienced failure, run 16
Experienced failure, run 20
...


I wonder if the CLR team knows about this bug?

Update: My friend Josef points out that if you introduce a temporary variable and pass that in to the anonymous delegate then fixed protection is restored. Something like:


...
byte *pTemp = p;
Action action = () => { *pTemp = 10; }
...

Wednesday, November 11, 2009

Fixed statement not what you expect (or is it?)

Recently we ran into the problem at work that a fixed statement doesn't have the lifetime you expect. Consider:


...
fixed(byte *p = byteArray)
{
UnmanagedCode(p);
OtherStuff();
Console.WriteLine("finished");
}


You might expect that p is held fixed until after the Console.WriteLine statement, but it's only held fixed to the last usage of p, so the fixed protection terminates at the start of the OtherStuff() line. This is something I used to know as a CLR dev, but since forgot. Do you want to see proof? Let's break out SOS.

Here is a little test function:


private unsafe void FixedToLastUsage() {
DebugBreak();

int size = 100;
byte[] byteArray = new byte[size];
byteArray[0] = 1;

fixed(byte *p = byteArray) {
Console.WriteLine("p = " + *p);
byte* p2 = p + 1;

// Now we won't reference p anymore but we'll do some other stuff.
// A look at the gc encoding should prove that p isn't protected beyond this point.
int sum = 0;
for(int i=0;i<10;i++) {
sum += i;
}
Console.WriteLine("sum = " + sum.ToString());
}
}


The DebugBreak() is a pinvoke call to the Windows DebugBreak() function. This is just to make it easier for me to disassemble the function and use SOS in the Windows Debugger.

Here is the disassembly of the method:


0:000> .loadby mscorwks sos
0:000> !clrstack
OS Thread Id: 0xfe8 (0)
ESP EIP
0012f410 7c90120e [NDirectMethodFrameStandalone: 0012f410] FixedTest.Program.DebugBreak()
0012f420 00d3014d FixedTest.Program.FixedToLastUsage()
0012f474 00d300b4 FixedTest.Program.Main(System.String[])
0012f69c 79e71b4c [GCFrame: 0012f69c]
0:000> !u d3014d
Normal JIT generated code
FixedTest.Program.FixedToLastUsage()
Begin 00d30108, size 159
00d30108 55 push ebp
00d30109 8bec mov ebp,esp
00d3010b 83ec4c sub esp,4Ch
00d3010e 33c0 xor eax,eax
00d30110 8945f4 mov dword ptr [ebp-0Ch],eax
00d30113 8945ec mov dword ptr [ebp-14h],eax
00d30116 894dfc mov dword ptr [ebp-4],ecx
00d30119 833de430930000 cmp dword ptr ds:[9330E4h],0
00d30120 7405 je 00d30127
00d30122 e89aa53979 call mscorwks!JIT_DbgIsJustMyCode (7a0ca6c1)
00d30127 33d2 xor edx,edx
00d30129 8955d4 mov dword ptr [ebp-2Ch],edx
00d3012c 33d2 xor edx,edx
00d3012e 8955e8 mov dword ptr [ebp-18h],edx
00d30131 33d2 xor edx,edx
00d30133 8955d8 mov dword ptr [ebp-28h],edx
00d30136 33d2 xor edx,edx
00d30138 8955f8 mov dword ptr [ebp-8],edx
00d3013b c745e400000000 mov dword ptr [ebp-1Ch],0
00d30142 33d2 xor edx,edx
00d30144 8955f0 mov dword ptr [ebp-10h],edx
00d30147 90 nop
00d30148 e8ffbec0ff call 0093c04c (FixedTest.Program.DebugBreak(), mdToken: 06000001)
>>> 00d3014d 90 nop
00d3014e c745f864000000 mov dword ptr [ebp-8],64h
00d30155 8b55f8 mov edx,dword ptr [ebp-8]
00d30158 b902410c79 mov ecx,offset mscorlib_ni+0x4102 (790c4102)
00d3015d e83620bfff call 00922198 (JitHelp: CORINFO_HELP_NEWARR_1_VC)
00d30162 8945d0 mov dword ptr [ebp-30h],eax
00d30165 8b45d0 mov eax,dword ptr [ebp-30h]
00d30168 8945d8 mov dword ptr [ebp-28h],eax
00d3016b 8b45d8 mov eax,dword ptr [ebp-28h]
00d3016e 83780400 cmp dword ptr [eax+4],0
00d30172 7705 ja 00d30179
00d30174 e883c13979 call mscorwks!JIT_RngChkFail (7a0cc2fc)
00d30179 c6400801 mov byte ptr [eax+8],1
00d3017d 8b45d8 mov eax,dword ptr [ebp-28h]
00d30180 8945d4 mov dword ptr [ebp-2Ch],eax
00d30183 837dd400 cmp dword ptr [ebp-2Ch],0
00d30187 7409 je 00d30192
00d30189 8b45d4 mov eax,dword ptr [ebp-2Ch]
00d3018c 83780400 cmp dword ptr [eax+4],0
00d30190 7508 jne 00d3019a
00d30192 33d2 xor edx,edx
00d30194 8955f4 mov dword ptr [ebp-0Ch],edx
00d30197 90 nop
00d30198 eb14 jmp 00d301ae
00d3019a 8b45d4 mov eax,dword ptr [ebp-2Ch]
00d3019d 83780400 cmp dword ptr [eax+4],0
00d301a1 7705 ja 00d301a8
00d301a3 e854c13979 call mscorwks!JIT_RngChkFail (7a0cc2fc)
00d301a8 8d4008 lea eax,[eax+8]
00d301ab 8945f4 mov dword ptr [ebp-0Ch],eax
00d301ae 90 nop
00d301af b920353379 mov ecx,offset mscorlib_ni+0x273520 (79333520) (MT: System.Byte)
00d301b4 e8631ebfff call 0092201c (JitHelp: CORINFO_HELP_NEWSFAST)
00d301b9 8945cc mov dword ptr [ebp-34h],eax
00d301bc 8b0530202a02 mov eax,dword ptr ds:[22A2030h] ("p = ")
00d301c2 8945b8 mov dword ptr [ebp-48h],eax
00d301c5 8b45cc mov eax,dword ptr [ebp-34h]
00d301c8 8b55f4 mov edx,dword ptr [ebp-0Ch]
00d301cb 8955e0 mov dword ptr [ebp-20h],edx
00d301ce 8b55e0 mov edx,dword ptr [ebp-20h]
00d301d1 8a12 mov dl,byte ptr [edx]
00d301d3 885004 mov byte ptr [eax+4],dl
00d301d6 8b45cc mov eax,dword ptr [ebp-34h]
00d301d9 8945b4 mov dword ptr [ebp-4Ch],eax
00d301dc 8b4db8 mov ecx,dword ptr [ebp-48h]
00d301df 8b55b4 mov edx,dword ptr [ebp-4Ch]
00d301e2 e869ca5978 call mscorlib_ni+0x20cc50 (792ccc50) (System.String.Concat(System.Object, System.Object), mdToken: 060001c5)
00d301e7 8945c8 mov dword ptr [ebp-38h],eax
00d301ea 8b4dc8 mov ecx,dword ptr [ebp-38h]
00d301ed e82637a678 call mscorlib_ni+0x6d3918 (79793918) (System.Console.WriteLine(System.String), mdToken: 060007c8)
00d301f2 90 nop
00d301f3 8b45f4 mov eax,dword ptr [ebp-0Ch]
00d301f6 8945dc mov dword ptr [ebp-24h],eax
00d301f9 8b45dc mov eax,dword ptr [ebp-24h]
00d301fc 40 inc eax
00d301fd 8945f0 mov dword ptr [ebp-10h],eax
00d30200 33d2 xor edx,edx
00d30202 8955ec mov dword ptr [ebp-14h],edx
00d30205 33d2 xor edx,edx
00d30207 8955e8 mov dword ptr [ebp-18h],edx
00d3020a 90 nop
00d3020b eb0b jmp 00d30218
00d3020d 90 nop
00d3020e 8b45e8 mov eax,dword ptr [ebp-18h]
00d30211 0145ec add dword ptr [ebp-14h],eax
00d30214 90 nop
00d30215 ff45e8 inc dword ptr [ebp-18h]
00d30218 837de80a cmp dword ptr [ebp-18h],0Ah
00d3021c 0f9cc0 setl al
00d3021f 0fb6c0 movzx eax,al
00d30222 8945e4 mov dword ptr [ebp-1Ch],eax
00d30225 837de400 cmp dword ptr [ebp-1Ch],0
00d30229 75e2 jne 00d3020d
00d3022b 8b0534202a02 mov eax,dword ptr ds:[22A2034h] ("sum = ")
00d30231 8945c4 mov dword ptr [ebp-3Ch],eax
00d30234 8d4dec lea ecx,[ebp-14h]
00d30237 e8d40a5b78 call mscorlib_ni+0x220d10 (792e0d10) (System.Int32.ToString(), mdToken: 06000b22)
00d3023c 8945c0 mov dword ptr [ebp-40h],eax
00d3023f 8b55c0 mov edx,dword ptr [ebp-40h]
00d30242 8b4dc4 mov ecx,dword ptr [ebp-3Ch]
00d30245 e806ea5478 call mscorlib_ni+0x1bec50 (7927ec50) (System.String.Concat(System.String, System.String), mdToken: 060001c9)
00d3024a 8945bc mov dword ptr [ebp-44h],eax
00d3024d 8b4dbc mov ecx,dword ptr [ebp-44h]
00d30250 e8c336a678 call mscorlib_ni+0x6d3918 (79793918) (System.Console.WriteLine(System.String), mdToken: 060007c8)
00d30255 90 nop
00d30256 90 nop
00d30257 33d2 xor edx,edx
00d30259 8955f4 mov dword ptr [ebp-0Ch],edx
00d3025c 90 nop
00d3025d 8be5 mov esp,ebp
00d3025f 5d pop ebp
00d30260 c3 ret


And here is the gc encoding for the method. What is gc encoding? I think the SOS help explains it best. (Depending on your feelings about SOS help you can thank or curse me...I wrote it over the Christmas holidays in 2002 or 2003, I forgot)


0:000> !help GCInfo
-------------------------------------------------------------------------------
!GCInfo ( | )

!GCInfo is especially useful for CLR Devs who are trying to determine if there
is a bug in the JIT Compiler. It parses the GCEncoding for a method, which is a
compressed stream of data indicating when registers or stack locations contain
managed objects. It is important to keep track of this information, because if
a garbage collection occurs, the collector needs to know where roots are so it
can update them with new object pointer values.


You should copy this gc info into a notepad on a second monitor and read it as you read the disassembly. It becomes clear pretty quick that EBP-0CH is our pointer p.


0:000> !GCInfo d3014d
entry point 00d30108
Normal JIT generated code
GC info 00931934
Method info block:
method size = 0159
prolog size = 17
epilog size = 4
epilog count = 1
epilog end = yes
callee-saved regs = EBP
ebp frame = yes
fully interruptible= yes
double align = no
arguments size = 0 DWORDs
stack frame size = 19 DWORDs
untracked count = 2
var ptr tab count = 10
epilog at 0155
argTabOffset = 22
82 59 D2 81 D3 |
B9 93 F1 40 0A |
22 |

Pointer table:
04 | [EBP-04H] an untracked local
05 | [EBP-0CH] an untracked pinned byref local
2C 24 82 35 | 0024..0159 [EBP-2CH] a pointer
28 0A 82 2B | 002E..0159 [EBP-28H] a pointer
30 2F 0F | 005D..006C [EBP-30H] a pointer
34 57 26 | 00B4..00DA [EBP-34H] a pointer
48 09 1D | 00BD..00DA [EBP-48H] a pointer
4C 17 06 | 00D4..00DA [EBP-4CH] a pointer
38 0E 03 | 00E2..00E5 [EBP-38H] a pointer
3C 4A 11 | 012C..013D [EBP-3CH] a pointer
40 0B 06 | 0137..013D [EBP-40H] a pointer
44 0E 03 | 0145..0148 [EBP-44H] a pointer
B8 5A 40 | 005A reg EAX becoming live
F1 07 | 0071 reg EAX becoming dead
40 | 0071 reg EAX becoming live
F2 01 | 008A reg EAX becoming dead
F0 43 | 0095 reg EAX becoming live
F0 03 | 00A0 reg EAX becoming dead
40 | 00A0 reg EAX becoming live
03 | 00A3 reg EAX becoming dead
BF 40 | 00A3 reg EAX becoming live (iptr)
03 | 00A6 reg EAX becoming dead
F0 43 | 00B1 reg EAX becoming live
F1 BF 52 | 00C3 reg EDX becoming live (iptr)
16 | 00C9 reg EDX becoming dead
F0 4E | 00D7 reg ECX becoming live
53 | 00DA reg EDX becoming live
0D | 00DF reg ECX becoming dead
10 | 00DF reg EDX becoming dead
4E | 00E5 reg ECX becoming live
05 | 00EA reg EAX becoming dead
08 | 00EA reg ECX becoming dead
BF 44 | 00EE reg EAX becoming live (iptr)
06 | 00F4 reg EAX becoming dead
F5 45 | 0129 reg EAX becoming live
BF 4E | 012F reg ECX becoming live (iptr)
0D | 0134 reg ECX becoming dead
56 | 013A reg EDX becoming live
4B | 013D reg ECX becoming live
0D | 0142 reg ECX becoming dead
10 | 0142 reg EDX becoming dead
4E | 0148 reg ECX becoming live
05 | 014D reg EAX becoming dead
08 | 014D reg ECX becoming dead
FF |


If you look for usages of EBP-0CH in the code, you'll find first the initialization to 0:


00d3010e 33c0 xor eax,eax
00d30110 8945f4 mov dword ptr [ebp-0Ch],eax


then it's population with the address of the first element in byteArray (itself stored as an object reference at location EBP-2CH).


00d3019a 8b45d4 mov eax,dword ptr [ebp-2Ch]
00d3019d 83780400 cmp dword ptr [eax+4],0
00d301a1 7705 ja 00d301a8
00d301a3 e854c13979 call mscorwks!JIT_RngChkFail (7a0cc2fc)
00d301a8 8d4008 lea eax,[eax+8] (here is the fixed(byte *p = byteArray) statement)
00d301ab 8945f4 mov dword ptr [ebp-0Ch],eax


The last reference is interesting, and seems to disprove my assertion that the pointer is only protected to the last reference. At around the place where the closing curly brace for the fixed statement is, we have:


00d30257 33d2 xor edx,edx
00d30259 8955f4 mov dword ptr [ebp-0Ch],edx
00d3025c 90 nop


Wow. I am surprised and shocked! It looks like indeed the pointer is held live until the end of the scope. This raises more questions...

Managed C++ IS good :-)

After my ill-advised rant about managed C++ I learned something interesting. Managed C++ is a great bridge between a C# model and unmanaged C++ code, especially if your method signatures are kind of complex. Managed C++ does a better (quicker) job of marshaling between it's module and the unmanaged C++ module than PINVOKE does.

Also, you don't have to learn those fancy PInvoke attributes and duplicate structures in C# and C++.

Therefore, managed C++ is the most elegant solution to bridge between C# and unmanaged C++.

Thursday, September 24, 2009

IDisposable tutorial

Recently at work we had to review our understanding of IDisposable. I wrote this little article to help me and everyone else get it nailed. For me, it took visualizing what happens at the time of an explicit call to Dispose() and what happens when a destructor is run. The difference between those two scenarios motivates and explains the code.

Why is it so complicated?


Beyond knowing that we might have something to cleanup, it's hard to understand why the mechanics of doing it appear so complex. The best way to understand it is to visualize the two very different contexts your cleanup code can be called in.

The first case, somebody called Dispose() on your object: This is easy to understand. Somebody has your object pointer, they make that explicit call, and then (hopefully) they set the pointer to null or let it fall out of scope. Only a nimrod would keep making calls on your object, right? In this case you get to do your cleanup. You can do anything you want, in fact! Do you want to send mail to somebody? No problem! Your object is alive, all of it's children objects are alive...go crazy!

The second case, the Finalizer Thread called your destructor: Here is where things go off the rails. You can't do anything you want. If you try to make calls on some of your member objects, you may find to your dismay that they've already been finalized, and won't respond the way you hoped. In effect, they are dead or dying. You are wandering around in a chaotic battlefield! The only thing you can do safely is release unmanaged resources. These are file handles or pointers to memory that the Garbage Collector knows nothing about. And you have to hurry. If you block the Finalizer Thread, you end up blocking the whole runtime. So in this case, you definitely have to restrict yourself. Here are the rules:


  • Don't make blocking calls

  • Don't examine/change/even-think-about member variables that are objects



Why can't I touch those object member variables, you ask? The reason is that the finalizer thread operates on a list of objects ready for finalization. The objects are in that list in no particular order. The fact that your object refers to the child object carries no weight anymore. Once your object was marked for finalization, your objects opinion about what objects are important no longer matters. That is why it's dangerous to try to use any object member fields.

But I don't have a destructor, you say? Okay. In that case, when your object goes out of scope nothing will happen. Of course, if you actually have the responsibility to clean something up this is not so good...

Enter Dispose(bool disposing)



Microsoft has established a convention to make these two different cases easy to distinguish in code. In a nutshell:


public class A : IDisposable {
~A() {
Dispose(false);
}

protected virtual void Dispose(bool disposing) {
if(disposing) {
// Somebody actually called Dispose()!
// Let me send mail and open a web browser here
}

// Put code here that needs to run even if we are just called by the finalizer
// Remember, it should only clean up unmanaged resources. No referring to member objects!
}

public virtual void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
}


The "disposing" parameter is confusing. Sometimes, people write it as "finalizing" and reverse the true and false constants in the code above. Stick with disposing though, it seems to be winning the war of convention. Just remember: if disposing is true, then I came in here from an explicit call to Dispose(), and not from a finalizer.

I recommend to always create a protected virtual void Dispose(bool disposing) method in your class that implements dispose. Then, make your implementation of Dispose() look exactly like the code above. Make your destructor only do one thing...call Dispose(false).

More enhancements



This is purely syntactic sugar, but lots of folks like to do this:


public class A : IDisposable {
~A() {
Dispose(false);
}

protected virtual void DisposeManagedResources() {
// Send mail and open a web browser here!
}

protected virtual void DisposeUnmanagedResources() {
// Close file handles here. Be careful what you do!
}

protected virtual void Dispose(bool disposing) {
if(disposing) {
// Somebody actually called Dispose()!
DisposeManagedResources();
}

DisposeUnmanagedResources();
}

public virtual void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
}


If you follow this pattern you can forget about the Dispose() method alltogether. You can forget about remembering what to do if disposing is true or false. You just put your two kinds of cleanup code in the provided methods. We do this pretty often in our codebase.

Another enhancement is a disposed boolean variable. This ensures that cleanup is only done once. I think that if you set your fields to null, and do a null test on the way in then you don't need this, but perhaps I'm saving a penny to lose a pound. :-). Here is the code above modified to use a disposing variable. NOTE: this doesn't make the code any more thread safe. It just frees you from having to be careful in your cleanup code when it comes to null checks.


public class A : IDisposable {

private bool disposed;

~A() {
Dispose(false);
}

protected virtual void DisposeManagedResources() {
// Send mail and open a web browser here!
}

protected virtual void DisposeUnmanagedResources() {
// Close file handles here. Be careful what you do!
}

protected virtual void Dispose(bool disposing) {
if(!disposed) {
if(disposing) {
// Somebody actually called Dispose()!
DisposeManagedResources();
}

DisposeUnmanagedResources();
disposed = true;
}
}

public virtual void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
}


Hey, this is getting hard!



Stay with me man, breath! We are almost there. Now, for a simple class the code we have above is perfect, and absolutely correct. If you are doing a code review, and you can't map the dispose logic of the class one-for-one to the logic above, then there is a bug in that code.

But... what about class hierarchies? This is actually a big problem. Because if your understanding of IDisposable for the one class case is a bit fuzzy, it will really fall apart when you consider a class hierarchy. But if you really understand the pattern above, and the reasons behind it, then you'll make correct and logical decisions when faced with creating, altering or debugging the dispose logic in a class hierarchy.

The implementer of IDisposable has responsibilities



In a class hierarchy, the first class (call it B) who chooses to implement the IDisposable interface has a very concrete responsibility:


  • Implement public sealed void Dispose()

  • Be nice and implement protected virtual void Dispose(bool disposing)

  • Implement a destructor that calls Dispose(false)



Now, if you have class C, which derives from B, then you only have to do one thing in C to carry on the tradition of correct dispose behavior:


class C : B {

protected override void Dispose(bool disposing) {
if(disposing) {
// Dispose of C managed resources
}
// Dispose of C unmanaged resources here

base.Dispose(disposing); // Give B a chance to do it's work.
}
}


What if C doesn't have any special work to do for dispose? Neither managed nor unmanaged resources to clean up? In that case C doesn't have to implement Dispose(bool disposing). Yay.

Note the call to the base class Dispose(disposing) method. That is very important. Note that this call is at the end of the Dispose(disposing) method. This reflects that C's resources are created after B's resources, and therefore C's resources should be cleaned up before B's resources are.

I don't have to implement IDisposable do I?



One case where you should implement IDisposable but is not very obvious is when your member variables implement IDisposable. The problem is what if your member variable is a FileStream. Now, it's true, when your object disappears, eventually the finalizer will run and find the FileStream object, call it's destructor, which will call Dispose(false) and the unmanaged resources will ultimately be cleaned up. But it's kind of rude.

You see, the FileStream advertised that he is ready and willing to clean up the moment you snap your fingers and call Dispose() on him! But if you don't implement IDisposable yourself so that a user of your class (who can't see the FileStream) can take advantage of that behavior, well then...a baby cries somewhere. Very loudly.

Of course, if you make sure to clean up that member variable before your object falls out of scope (maybe you have a "Close()" method or some convention like that), then you can sleep well again. However, it's pretty easy to implement IDisposable, now that you know what's going on!

Thursday, September 10, 2009

Managed C++

I don't understand why to use this. First bad: arcane new syntax for managed work (okay, not new, but I've been out of the .NET world for quite a while). But secondly, you don't get the kind of performance profile you'd expect for C/C++.

At our company we recently did a test and discovered that native C++ is 20 times faster than managed C++. This led me to coin a phrase of which I remain inordinately proud:


Managed C++ has all the simplicity and clarity of the C++ language and the stunning performance profile of C#.


Needless to say, we are converting those managed C++ libraries to native C++...

Wednesday, August 12, 2009

vshost and /3GB problem

Yesterday I found that if you debug a process in Visual Studio 2008 on a machine with /3GB enabled, then the Visual Studio Hosting Process (on by default in the debug properties pane) will annoyingly reserve a 1gig chunk of memory for itself. This cancels out any benefit you hope to acquire by running with a 3gig address space!

I don’t know why. But in the meantime, I’ve turned off this hosting option in the Prost8 vcproject file.

Just because it’s fun to see:

From output !sos.vmmap:

...
7fffe000-7fffefff 00001000 RdWr RdWr Commit Private
7ffff000-7fffffff 00001000 RdWr RdWr Commit Private
80000000-bffdffff 3ffe0000 Rd Reserve Private (that is a lot of reserved mem!)
bffe0000-bffeffff 00010000 Rd NA Reserve Private


With hosting turned off:


...
7ffe1000-7ffeffff 0000f000 NA Free
7fff0000-7fffffff 00010000 NA Reserve Private
80000000-8000ffff 00010000 NA Reserve Private
80010000-bff47fff 3ff38000 NA Free (I like this much better, it gets used by us over time)
bff48000-bff48fff 00001000 RdWr RdWr Commit Private
bff49000-bff49fff 00001000 RdWr RdWr Commit Private
bff4a000-bff4afff 00001000 RdWr RdWr Commit Private
bff4b000-bff4bfff 00001000 RdWr RdWr Commit Private
...

Welcome!

Here is my blog for technical notes. Maybe something I find interesting about Visual Studio, or MATLAB, or iTunes, or just about anything.