CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Greg Young [MVP]

August 2007 - Posts

  • The File Carnie

     

    Well if I were working in a carnival and this thing stepped up to the booth to have me guess its weight, it would have won the coveted stuffed animal for its dollar. 

    This is really obvious though it might be more of an optimization than your think, caught me by surprise anyways I was expecting a 7.5-15% gain from it which is why I had put it off as a "minor" optimization to do when I had a few free minutes.

     

    If you have a guess at the size of the file you would like to write, size it before hand truncate after if need be ... its pretty much free.

    namespace BigFileWriterTest
    {
        class Program
        {
            static FileStream CreateAndOpenFile(string _Name)
            {
                FileStream ret = File.Open(_Name, FileMode.Create);
                return ret;
            }
            static void TestFile(string _Name, bool _InitSize, int Megs) {
                Stopwatch sw = new Stopwatch();
                byte[] data = new byte[1024]; //1 kb buffer
                for (int i = 0; i < data.Length; i++)
                {
                    dataIdea = (byte) (i % 255);
                }
                sw.Start();
                using (FileStream fs = CreateAndOpenFile(_Name))
                {
                    sw.Stop();
                    Console.WriteLine("Open: " + sw.Elapsed);
                    sw.Reset();
                    sw.Start();
                    if (_InitSize)
                    {
                        long length = 1024;
                        length = length * 1024;
                        length = length * Megs;
                        fs.SetLength(length);
                    }
                    sw.Stop();
                    Console.WriteLine ("Length Increase: " + sw.Elapsed);
                    sw.Reset();
                    sw.Start();
                    for (int i = 0; i < (Megs * 1024); i++)
                    {
                        fs.Write (data, 0, data.Length);
                        fs.Flush();
                    }
                    sw.Stop();
                    Console.WriteLine("Write: " + sw.Elapsed);
                    fs.Close();
                }
                File.Delete(_Name);
            }
            static void Main(string[] args)
            {
                string Filename = "C:\\shitbird.tmp";
                TestFile(Filename, false, 1000);
                TestFile(Filename, true, 1000); 
            }
        }

    Output: 

     

    Open: 00:00:00.0013021
    Length Increase: 00:00:00.0000013
    Write: 00:01:17.4923097
    Open: 00:00:00.0013376
    Length Increase: 00:00:00.0220189
    Write: 00:00:13.8646638
    Press any key to continue . . .

    WOW!

     77
    ---- = almost 6 times faster?!
     13

    Go try it on yours and show your boss how you made that big dump file 5x faster, it should at least get you a beer.

    p.s. no comments on my temp file names :) they are really easy to find/delete.

  • Pop Quiz Pins: Answers

    Wow guys I am impressed with some of the answers put up. Here is a summary...

     

    What are the various ways that an object can be pinned?

    People pretty much got this one right but they missed one. GCHandles, Interop will "auto-magically" generate pins on the stac, the "fixed" statement in C#, and Overlapped IO (There may be more here, in particular I didn't have time to look into how reverse pinvoke was working) but these are the main ones ... I like to think of it as 3 methods Stack (fixed, interop), GCHandle, and Overlapped.

    I won't go too much into GCHandles ... they well work and are pretty straight forward. If you are interested in how they work, reflector and the SSCLI are a pretty good start. To get you started take a look at comdelegate.cs, take a look at InternalAlloc as a good starting point.

    Fixed (which is most of the time how the marshaller works) is a bit more interesting. It never creates a handle. If you look at reflector you will see that the variable is created with pinned marked on it. Basically the variable is defined as being pinned for the life of the method, the GC recognizes by the call stack that the object is pinned. You can see this with the !objsize command. From what I can tell in Rotor it is not maintained in a table like gcinfo or exinfo but instead is considerred pinned for the duration of the method. It is however set to null when it is no longer in range so it can be collected. My guess is that the commercial JIT works the same way.

    Overlapped IO is a special type of Pin. If you start in Overlapped.Pack and continue down the call chain you will eventually end up in OverlappedData.AllocateNativeOverlapped.

    [MethodImpl(MethodImplOptions.InternalCall)]
    private extern unsafe NativeOverlapped* AllocateNativeOverlapped();

    This is a "magic" method which allocates a NativeOverlapped struct from the CLR.

    If we quickly jump into the SSCLI we can find this demonic little internal call (yes I think 'internal calls are evil') and in NativeOverlapped.cpp we will find...

    if (userObject != NULL) {
            if (
    overlapped->m_isArray == 1)
            {
               
    BASEARRAYREF asArray = (BASEARRAYREF) userObject;
               
    OBJECTREF *pObj = (OBJECTREF*)(asArray->GetDataPtr());
               
    DWORD num = asArray->GetNumComponents();
               
    DWORD i;
                for (
    i = 0; i < num; i ++)
                {
                   
    GCHandleValidatePinnedObject(pObj[i]);
                }
                for (
    i = 0; i < num; i ++)
                {
                   
    asArray = (BASEARRAYREF) userObject;
                   
    AddMTForPinHandle(pObj[i]);
                }
            }
            else
            {
               
    GCHandleValidatePinnedObject(userObject);
               
    AddMTForPinHandle(userObject);
            }
           
        }

    So if m_IsArray is set it will treat all of the objects in m_userObject as an object[] otherwise it will treat it as an object ... but whatever is in there is implicitly treated as pinned.
    When you further get into objecthandle.cpp you can really see just how much of a tax this type of pin is on the CLR code base ex:
        if (!HndIsNullOrDestroyedHandle(pPinnedObj) && pPinnedObj->GetGCSafeMethodTable() == g_pOverlappedDataClass)
    {
    // reporting the pinned user objects
    OverlappedDataObject *pOverlapped = (OverlappedDataObject *)pPinnedObj;
    if (pOverlapped->m_userObject != NULL)
    {
    //callback(OBJECTREF_TO_UNCHECKED_OBJECTREF(pOverlapped->m_userObject), (ScanContext *)lp1, GC_CALL_PINNED);
    if (pOverlapped->m_isArray)
    {
    pOverlapped->m_userObjectInternal = static_cast<void*>(OBJECTREFToObject(pOverlapped->m_userObject));
    ArrayBase* pUserObject = (ArrayBase*)OBJECTREFToObject(pOverlapped->m_userObject);
    Object **pObj = (Object**)pUserObject->GetDataPtr(TRUE);
    DWORD num = pUserObject->GetNumComponents();
    DWORD i;
    for (i = 0; i < num; i ++)
    {
    callback(pObj[i], (ScanContext *)lp1, GC_CALL_PINNED);
    }
    }
    else
    {
    callback(OBJECTREF_TO_UNCHECKED_OBJECTREF(pOverlapped->m_userObject), (ScanContext *)lp1, GC_CALL_PINNED);
    }
    }

    if (pOverlapped->GetAppDomainId() != DefaultADID && pOverlapped->GetAppDomainIndex().m_dwIndex == DefaultADID)
    {
    OverlappedDataObject::MarkCleanupNeededFromGC();
    }
    }
    Good for performance but yuck ...

    What is heap fragmentation and why is it bad?

    Heap fragmentation has been the subject of conversation here for a little while. Yeah I know it was a it of a softball question but I needed to have 5 questions and it came to mind :P A full answer can be found here on Yun Jin's blog

    When is heap fragmentation not important?

    I think all of the answers here are valid. The one I was particularly looking for was ...

    "When you are dealing with the LOH (Large Object Heap), its not compacted"

    JD brings up another good point as the CLR is much better in 2.0 at filling in gaps, and I think Craig brings up the best point of all

    "I'd say heap fragmentation is important for some incredibly low number of Windows apps...like less than 5%.".

    This is a very true statement.

    The last two questions were really tricky

    How can you tell if an object is pinned?

    Tess aka Debugging Goddess (who if you are reading this you should need no introduction) on who's blog I usually find solutions to tricky SOS issues like this says here...

    If you are talking about managed heap fragmentation, there are two kinds, 1) heap fragmentation caused by pinned objects, in which case you can run !objsize (without parameters) to see what your pinned objects are and where they are located.

    This is really close to correct but not completely correct .. !objsize will show you objects which are pinned through the stack or gchandles but it won't show you those nasty little overlapped pins. For those you just need to know that items in m_userObject are pinned and hit them one by one :(

    !GCHandles will also show you all of your GCHandles that are out which should (except in the case of IO) show you what you are interested in.

    Can a pinned object be promoted?

    It sure can! Rico M has a great explanation ... Steve also alluded to this.

    Segment reuse is relatively straightforward. It’s to take advantage of the existing gen2 segments that have a lot free space but not yet empty (because if they were empty they would have been deleted). Slide 27 demonstrates the problem without segment reuse. So before GC we are running out of space in the ephemeral segment and we need to expand the heap. We have 2 pinned objects. And I didn’t mark their generations because it’s not significant to indicate their generations - they can not be moved so they will stay in this segment and become gen2 objects by definition. There might be pinning on the gen2 segments as well but since that doesn’t affect illustrating the point I preferred to keep the picture simple and left them out.

    So after GC we allocate a new segment. The old gen1 in the old ephemeral segment was promoted to gen2 and the old gen0 now lives in the new ephemeral segment as the new gen1 and we will start allocating after gen1 on this segment. The pinned objects are left in the old ephemeral seg since they can not be moved. They are part of gen2 now because they live in a gen2 segment.

    You might be asking yourself how on earth I came accoss these Overlapped pins, well in working with the BufferManager in previous posts I ran SOS alot to look at my heap fragmentation. I saw something quite odd; my buffer manager version had the exact same pins reported in !GCHandles (OverlappedDatas) as the non-bufferred version yet it had way less heap fragmentation. The pins on my actual buffers were never being shown so in trying to track them down to prove it was doing what I claimed it did I came accross all of this ... what a night that was! Nothing better than a good bottle of Sancerre and SOS outputs that make no sense (sometimes the Sancerre helps:))

    So what to do? Well for the pinned objects in the debugger I talked with Steve about adding support to his excellent SOSEX tool for this.

     

  • T - 1 (off topic)

    Ok I only have 365 days left until I turn the big 3-0. I guess that means that I have to really accomplish something this year.

    But to save myself ... I realized that I am only 23 and have plenty of time ... in fact I can be 23 forever.

     

    I guess its all about picking the right number system.

     

    My birthday present to myself will be a new laptop. Anyone got any good recommendations?

     

    Anyways, I need a beer.

  • Bread Crumb Trail

    Had someone ask an interesting question the other day ... they wanted to serialize call stacks with data in an object, obviously this can't be done. My initial response was to tell them to use a state machine to get around this but it turned out they were doing a depth first search. Since it was a large depth first search they wanted to remember where they were and survive process shut down. I hacked together something in a few minutes as an example.

     

    I am just throwing this code up so I don't lose it and it may be of use to someone else in the future, it is a resumable depth first search. You could do this on an object graph etc so long as the traversal maintains a reference to the graph (so when serialized/deserialized the external objects came back the same). Code is just a simple example of using a crumb trail to remember where you were in recursion so you can re-enter where you left.

     Note this is not thread safe and will only work for one thread. You could make this much more generic, its a quick example on the key feature.

        public class PositionSavingTraversal<T>
        {
           
            private List<T> m_Data;
            private int[] m_Positions;
            public PositionSavingTraversal(IEnumerable<T> _Data)
            {
                m_Data = new List<T>(_Data); //copy to our local list
                m_Positions = new int[m_Data.Count];
            }

            public void Recurse()
            {
                RecurseInternal(0);
                Console.WriteLine("Recursion completed");
            }

            public void RecurseInternal(int _CurrentDepth)
            {
                if (_CurrentDepth == m_Data.Count)
                {
                    return;
                }
                if (m_Positions[_CurrentDepth] == m_Data.Count)
                {
                    m_Positions[_CurrentDepth] = 0;
                }
                while (m_Positions[_CurrentDepth] < m_Data.Count)
                {
                    if (m_Data.Count - _CurrentDepth > 7)
                    {
                        Console.WriteLine(new string('\t', _CurrentDepth) + m_Positions[_CurrentDepth]);
                    }
                    RecurseInternal(_CurrentDepth + 1);
                    m_Positions[_CurrentDepth]++;
                }
            }
        }

        class Program
        {
            static void Main(string[] args)
            {
                int[] test = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
                PositionSavingTraversal<int> foo = new PositionSavingTraversal<int>(test);
                while (true)
                {
                    Thread t = new Thread(new ThreadStart(foo.Recurse));
                    t.Start();
                    Console.WriteLine("Thread started, press enter to kill thread");
                    Console.ReadLine();
                    t.Abort();
                    Console.WriteLine("press enter to start new thread and resume");
                    Console.ReadLine();
                }
            }
        }
     

    p.s. answers are written up for quiz and will be posted tonight so if you want to wager a quick guess today is the time to do it. 

  • What generation?

    Today I see ...

    [17:02] Chris Mullins: Alright, quick question:
    [17:02] Chris Mullins: I've got my dump up, and I'm lookingn through it with SOS.
    [17:02] Chris Mullins: I see a number of objects in the heap, that when I run !do on then (DumpObject)
    [17:02] Chris Mullins: I get no roots.
    [17:03] Chris Mullins: Is there a way to tell, looking at an object, what generation it's in?

     

    Think to myself for a minute about a really cool new tool I saw recently from Steve Johnson called SOSEX

     

    This tool is just slickness ... lots of useful things additions to SOS.

     

    A list of commands and full descriptions is on his site for the question above see gcgen and dumpgen ... so cool! 

     

  • Pop Quiz: Pinning

    OK you guys are good. I imagine that the average codebetter.com reader is outside the high side first standard deviation of the average .NET developers ...

    And yes I guess I am in a way stealing this idea from Rico 

    I warn you this is a tough one. 

    So lets start, answers will be posted tomorrow or Saturday if I still see a lot of responses.

    Pop Quiz:

    We know what pinning is (it keeps the garbage collector from moving an object in memory during a collection as something may have its address that the GC can't control). All questions apply to the current 2.0 release of the CLR on an x86 platform.

     

    Question 1:

    What are the various ways that an object can be pinned?

    Question 2:

    What is heap fragmentation and why is it bad?

    Question 3: 

    When is heap fragmentation not important?

    Question 4:

    How can you tell if an object is pinned?

    Question 5:

    Can a pinned object be promoted?

     

    Leave answers in comments unless you know for a FACT you are right on them all ... then email them to me at gregoryyoung1@ that google email service and I will use them tomorrow.

More Posts