RE: All of the sudden, excessive gen 1 collections

Tech-Archive recommends: Repair Windows Errors & Optimize Windows Performance



I think I figured out what's causing the issue, though I don't understand
why. I captured a hang dump of the process when it got into this bad state.
Here is a snippet of the managed heap (using !dumpheap):

..
..
..
012bc010 7912d8f8 84
012bc064 7912d8f8 84
012bd9c4 00163e38 273996 Free
01300810 7911b288 60
0130084c 7911b4a4 28
01300868 7912dae8 268
01300974 7912dd40 524
01300b80 79191d38 16 ThinLock owner 1 (00161868) Recursive 0
02281000 00163e38 16 Free
02281010 7912d8f8 4096
02282010 00163e38 16 Free
02282020 7912d8f8 4096
02283020 00163e38 16 Free
02283030 7912d8f8 4096
02284030 00163e38 16 Free
02284040 7912d8f8 4096
02285040 00163e38 16 Free
02285050 7912d8f8 528
total 7515 objects

I then decided to look at the objects after the large free object thinking
that maybe they were causing problems with the collection process. I found
out that these objects were related to the Console.ReadLine() in my server
(code snippet below):

..
..
..
// Start server.

server.Start();

Console.WriteLine("Hit any key to stop...");
Console.ReadLine();

server.Stop();
..
..
..

This server will run as an NT service but for now it's easier to run it as a
console application while testing. I replaced the ReadLine()/WriteLine()
with a while (true) loop and the problem appears to be gone. Does anyone
have an explanation for this? As I said, it's not a big deal as this will
eventually run as an NT service, but I would like to understand it better in
case there are other cases which can cause the same issue.

--
Thanks,
Nick

nicknospamdu@xxxxxxxxxxxxxxxx
remove "nospam" change community. to msn.com


"nickdu" wrote:

I've written a .NET named pipe server using .NET 2.0. I'm accessing the
Win32 API's using interop (pinvoke) and also using overlapped IO. Everything
seemed to be working fine. However I just noticed today that after running
several tests the performance degraded significantly. What I noticed was the
following:

* When starting the server process everything looks good. Throughput is
good and the memory looks fine. The memory counters look stable, nothing
seems to be leaking. gen0 collections are occurring at about 700/second.
gen1 collections are occurring at about 1 every 8 - 10 seconds. gen0 is
524K, gen1 is 37K, and gen2 is 162K. % time is gc is about 5%.

* After the client application (generating the load on the server) is
running for a while all of the sudden the throughput (requests/second) drops
considerably, 40K/second to 30K/second. At this point gen1 collections are
roughly 250/second. I'm not sure what's causing the change though it appears
to be related to the heaps changing. At this time gen0 is 524K, gen1 is
162K, and gen2 is 12. The size of gen2 looks pretty odd, 12 bytes?! % time
in gc goes to 20%. It stays in this state from then on.

Any ideas what could be causing this? Could it have something to do with
the interop calls? By the way, I've also created my own chunking stream
which I write the overlapped data into. I wasn't sure if it was ok to seek
into an unmanaged buffer by simply adding byte offsets to the IntPtr, but it
appears to work. I wrote this Write() method on my ChunkingStream to copy
the unmanaged data:

public void Write(IntPtr buffer, int offset, int count)
{
int startIndex;
int startChunk;
int endIndex;
int endChunk;
int i;

if (this._chunks == null)
throw(new ObjectDisposedException("ChunkingStream"));
if (buffer == IntPtr.Zero)
throw(new ArgumentNullException("buffer"));
if (offset < 0)
throw(new ArgumentOutOfRangeException("offset"));
if (count < 0)
throw(new ArgumentOutOfRangeException("count"));

if (count != 0)
{

// First increase the stream if needed.

if (this._position + count > this._length)
SetLength(this._position + count);

// Now write to the stream. Note that this is the same
// logic as Read() except we're writing to our chunks
// instead of reading from them.

// Figure out beginning and ending info.

startIndex = (int) (this._position % this._chunkSize);
startChunk = (int) (this._position / this._chunkSize);
endIndex = (int) ((this._position + count - 1) % this._chunkSize);
endChunk = (int) ((this._position + count - 1) / this._chunkSize);

buffer = (IntPtr) ((long) buffer + offset);

// Within the same chunk?

if (startChunk == endChunk)
{
Marshal.Copy(buffer, this._chunks[startChunk], startIndex,
endIndex - startIndex + 1);
}

// Else, spanning chunks. In this scenario we break it up
// into three tasks. Copying to the first chunk, copying
// to the middle chunks, and copying to the last chunk.

else
{
Marshal.Copy(buffer, this._chunks[startChunk], startIndex,
this._chunkSize - startIndex);
for (i = startChunk + 1,
buffer = (IntPtr) ((long) buffer + (this._chunkSize -
startIndex));
i < endChunk; ++i, buffer = (IntPtr) ((long) buffer +
this._chunkSize))
{
Marshal.Copy(buffer, this._chunks[i], 0, this._chunkSize);
}
Marshal.Copy(buffer, this._chunks[endChunk], 0, endIndex + 1);
}

this._position += count;
}
}

Note that the ChunkingStream chunk size is the same as the unmanaged buffer
size so it should always be copying using a single Marshal.Copy(), though I
did test the other cases which appeared to work fine.
--
Thanks,
Nick

nicknospamdu@xxxxxxxxxxxxxxxx
remove "nospam" change community. to msn.com
.



Relevant Pages