17

Consider the following program, with all of HttpRequestMessage, and HttpResponseMessage, and HttpClient disposed properly. It always ends up with about 50MB memory at the end, after collection. Add a zero to the number of requests, and the un-reclaimed memory doubles.

   class Program
    {
        static void Main(string[] args)
        {
            var client = new HttpClient { 
                   BaseAddress = new Uri("http://localhost:5000/")};

            var t = Task.Run(async () =>
            {
                var resps = new List<Task<HttpResponseMessage>>();
                var postProcessing = new List<Task>();

                for (int i = 0; i < 10000; i++)
                {
                    Console.WriteLine("Firing..");
                    var req = new HttpRequestMessage(HttpMethod.Get,
                                                        "test/delay/5");
                    var tsk = client.SendAsync(req);
                    resps.Add(tsk);
                    postProcessing.Add(tsk.ContinueWith(async ts =>
                    {
                        req.Dispose();
                        var resp = ts.Result;
                        var content = await resp.Content.ReadAsStringAsync();
                        resp.Dispose();
                        Console.WriteLine(content);
                    }));
                }

                await Task.WhenAll(resps);
                resps.Clear();
                Console.WriteLine("All requests done.");
                await Task.WhenAll(postProcessing);
                postProcessing.Clear();
                Console.WriteLine("All postprocessing done.");
            });

            t.Wait();
            Console.Clear();

            var t2 = Task.Run(async () =>
            {
                var resps = new List<Task<HttpResponseMessage>>();
                var postProcessing = new List<Task>();

                for (int i = 0; i < 10000; i++)
                {
                    Console.WriteLine("Firing..");
                    var req = new HttpRequestMessage(HttpMethod.Get,
                                                        "test/delay/5");
                    var tsk = client.SendAsync(req);
                    resps.Add(tsk);
                    postProcessing.Add(tsk.ContinueWith(async ts =>
                    {
                        var resp = ts.Result;
                        var content = await resp.Content.ReadAsStringAsync();
                        Console.WriteLine(content);
                    }));
                }

                await Task.WhenAll(resps);
                resps.Clear();
                Console.WriteLine("All requests done.");
                await Task.WhenAll(postProcessing);
                postProcessing.Clear();
                Console.WriteLine("All postprocessing done.");
            });

            t2.Wait();
            Console.Clear();
            client.Dispose();

            GC.Collect();
            Console.WriteLine("Done");
            Console.ReadLine();
        }
    }

On a quick investigation with a memory profiler, it seems that the objects that take up the memory are all of the type Node<Object> inside mscorlib.

My initial though was that, it was some internal dictionary or a stack, since they are the types that uses Node as an internal structure, but I was unable to turn up any results for a generic Node<T> in the reference source since this is actually Node<object> type.

Is this a bug, or somekind of expected optimization (I wouldn't consider a proportional consumption of memory always retained to be a optimization in any way)? And purely academic, what is the Node<Object>.

Any help in understanding this would be much appreciated. Thanks :)

Update: To extrapolate the results for a much larger test set, I optimized it slightly by throttling it.

Here's the changed program. And now, it seems to stay consistent at 60-70MB, for a 1 million request set. I'm still baffled at what those Node<object>s really are, and its allowed to maintain such a high number of irreclaimable objects.

And the logical conclusion from the differences in these two results leads me to guess, this may not really be an issue in with HttpClient or WebRequest, rather something rooted directly with async - Since the real variant in these two test are the number of incomplete async tasks that exist at a given point in time. This is merely a speculation from the quick inspection.

static void Main(string[] args)
{

    Console.WriteLine("Ready to start.");
    Console.ReadLine();

    var client = new HttpClient { BaseAddress = 
                    new Uri("http://localhost:5000/") };

    var t = Task.Run(async () =>
    {
        var resps = new List<Task<HttpResponseMessage>>();
        var postProcessing = new List<Task>();

        for (int i = 0; i < 1000000; i++)
        {
            //Console.WriteLine("Firing..");
            var req = new HttpRequestMessage(HttpMethod.Get, "test/delay/5");
            var tsk = client.SendAsync(req);
            resps.Add(tsk);
            var n = i;
            postProcessing.Add(tsk.ContinueWith(async ts =>
            {
                var resp = ts.Result;
                var content = await resp.Content.ReadAsStringAsync();
                if (n%1000 == 0)
                {
                    Console.WriteLine("Requests processed: " + n);
                }

                //Console.WriteLine(content);
            }));

            if (n%20000 == 0)
            {
                await Task.WhenAll(resps);
                resps.Clear();
            }

        }

        await Task.WhenAll(resps);
        resps.Clear();
        Console.WriteLine("All requests done.");
        await Task.WhenAll(postProcessing);
        postProcessing.Clear();
        Console.WriteLine("All postprocessing done.");
    });

    t.Wait();
    Console.Clear();
    client.Dispose();

    GC.Collect();
    Console.WriteLine("Done");
    Console.ReadLine();
}

g.Raphael 0.51 error in IE

I'm trying to do generate a sample pie chart from raphael.min.js version 2.1.2

I'm using

raphael.min.js

and

 g.raphael.min.js

in my code.

ReferenceError: Raphael is not defined in corresponding to the line of code :-

--> Raphael.el.popup = function (d, a, b, f) {

Please help.

Thanx in advance.

4

3 回答 3

20

让我们用我们手头的所有工具来调查这个问题。

首先,让我们看看这些对象是什么,为了做到这一点,我将给定的代码放在 Visual Studio 中并创建了一个简单的控制台应用程序。我并排在 Node.js 上运行一个简单的 HTTP 服务器来处理请求。

将客户端运行到最后并开始将 WinDBG 附加到它,我检查托管堆并获得以下结果:

0:037> !dumpheap
Address       MT     Size
02471000 00779700       10 Free
0247100c 72482744       84     
...
Statistics:
      MT    Count    TotalSize Class Name
...
72450e88      847        13552 System.Collections.Concurrent.ConcurrentStack`1+Node[[System.Object, mscorlib]]
...

!dumpheap 命令将托管堆中的所有对象转储到那里。这可能包括应该被释放的对象(但还没有,因为 GC 还没有开始)。在我们的例子中,这应该很少见,因为我们只是在打印输出之前调用了 GC.Collect() 并且在打印输出之后不应该运行其他任何东西。

值得注意的是上面的特定行。那应该是您在问题中提到的 Node 对象。

接下来,让我们看一下该类型的各个对象,我们获取该对象的 MT 值,然后像这样再次调用!dumpheap,这将只过滤掉我们感兴趣的对象。

0:037> !dumpheap -mt 72450e88   
 Address       MT     Size
025b9234 72450e88       16     
025b93dc 72450e88       16     
...

现在在列表中随机抓取一个,然后通过调用 !gcroot 命令询问调试器为什么该对象仍在堆上,如下所示:

0:037> !gcroot 025bbc8c
Thread 6f24:
    0650f13c 79752354 System.Net.TimerThread.ThreadProc()
        edi:  (interior)
            ->  034734c8 System.Object[]
            ->  024915ec System.PinnableBufferCache
            ->  02491750 System.Collections.Concurrent.ConcurrentStack`1[[System.Object, mscorlib]]
            ->  09c2145c System.Collections.Concurrent.ConcurrentStack`1+Node[[System.Object, mscorlib]]
            ->  09c2144c System.Collections.Concurrent.ConcurrentStack`1+Node[[System.Object, mscorlib]]
            ->  025bbc8c System.Collections.Concurrent.ConcurrentStack`1+Node[[System.Object, mscorlib]]

Found 1 unique roots (run '!GCRoot -all' to see all roots).

现在很明显我们有一个缓存,并且该缓存维护一个堆栈,堆栈实现为链表。如果我们进一步思考,我们将在参考源中看到该列表是如何使用的。为此,我们首先使用 !DumpObj 检查缓存对象本身

0:037> !DumpObj 024915ec 
Name:        System.PinnableBufferCache
MethodTable: 797c2b44
EEClass:     795e5bc4
Size:        52(0x34) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
724825fc  40004f6        4        System.String  0 instance 024914a0 m_CacheName
7248c170  40004f7        8 ...bject, mscorlib]]  0 instance 0249162c m_factory
71fe994c  40004f8        c ...bject, mscorlib]]  0 instance 02491750 m_FreeList
71fed558  40004f9       10 ...bject, mscorlib]]  0 instance 025b93b8 m_NotGen2
72484544  40004fa       14         System.Int32  1 instance        0 m_gen1CountAtLastRestock
72484544  40004fb       18         System.Int32  1 instance 605289781 m_msecNoUseBeyondFreeListSinceThisTime
7248fc58  40004fc       2c       System.Boolean  1 instance        0 m_moreThanFreeListNeeded
72484544  40004fd       1c         System.Int32  1 instance      244 m_buffersUnderManagement
72484544  40004fe       20         System.Int32  1 instance      128 m_restockSize
7248fc58  40004ff       2d       System.Boolean  1 instance        1 m_trimmingExperimentInProgress
72484544  4000500       24         System.Int32  1 instance        0 m_minBufferCount
72484544  4000501       28         System.Int32  1 instance        0 m_numAllocCalls

现在我们看到了一些有趣的东西,堆栈实际上被用作缓存的空闲列表。源代码告诉我们如何使用空闲列表,特别是在下面显示的 Free() 方法中:

http://referencesource.microsoft.com/#mscorlib/parent/parent/parent/parent/InternalApis/NDP_Common/inc/PinnableBufferCache.cs

/// <summary>
/// Return a buffer back to the buffer manager.
/// </summary>
[System.Security.SecuritySafeCritical]
internal void Free(object buffer)
{
  ...
  m_FreeList.Push(buffer);
}

就是这样,当调用者处理完缓冲区后,它会返回缓存,缓存然后将其放入空闲列表中,然后将空闲列表用于分配目的

[System.Security.SecuritySafeCritical]
internal object Allocate()
{
  // Fast path, get it from our Gen2 aged m_FreeList.  
  object returnBuffer;
  if (!m_FreeList.TryPop(out returnBuffer))
    Restock(out returnBuffer);
  ...
}

最后但同样重要的是,让我们了解为什么当我们完成所有这些 HTTP 请求后缓存本身没有被释放?这就是为什么。通过在 mscorlib.dll!System.Collections.Concurrent.ConcurrentStack.Push() 上添加断点,我们看到以下调用堆栈(嗯,这可能只是缓存用例之一,但这是有代表性的)

mscorlib.dll!System.Collections.Concurrent.ConcurrentStack<object>.Push(object item)
System.dll!System.PinnableBufferCache.Free(object buffer)
System.dll!System.Net.HttpWebRequest.FreeWriteBuffer()
System.dll!System.Net.ConnectStream.WriteHeadersCallback(System.IAsyncResult ar)
System.dll!System.Net.LazyAsyncResult.Complete(System.IntPtr userToken)
System.dll!System.Net.ContextAwareResult.Complete(System.IntPtr userToken)
System.dll!System.Net.LazyAsyncResult.ProtectedInvokeCallback(object result, System.IntPtr userToken)
System.dll!System.Net.Sockets.BaseOverlappedAsyncResult.CompletionPortCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* nativeOverlapped)
mscorlib.dll!System.Threading._IOCompletionCallback.PerformIOCompletionCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* pOVERLAP)

在 WriteHeadersCallback 中,我们完成了标题的写入,因此我们将缓冲区返回到缓存中。此时缓冲区被推回空闲列表,因此我们分配了一个新的堆栈节点。需要注意的关键是缓存对象是 HttpWebRequest 的静态成员。

http://referencesource.microsoft.com/#System/net/System/Net/HttpWebRequest.cs

...
private static PinnableBufferCache _WriteBufferCache = new PinnableBufferCache("System.Net.HttpWebRequest", CachedWriteBufferSize);
...
// Return the buffer to the pinnable cache if it came from there.   
internal void FreeWriteBuffer()
{
  if (_WriteBufferFromPinnableCache)
  {
    _WriteBufferCache.FreeBuffer(_WriteBuffer);
    _WriteBufferFromPinnableCache = false;
  }
  _WriteBufferLength = 0;
  _WriteBuffer = null;
}
...

所以我们开始了,缓存在所有请求之间共享,并且在所有请求完成时不会释放。

于 2015-12-30T23:55:20.567 回答
2

当我们使用 System.Net.WebRequest 执行一些 http 请求时,我们遇到了同样的问题。w3wp 进程的大小范围为 4-8 Gb,因为我们没有恒定的负载。有时我们每秒有 10 个请求,其他时候有 1000 个请求。当然缓冲区不会在同一场景中重用。

在System.Net.Http.HttpClient上使用System.Net.WebRequest时,我们改变了所有地方,因为它没有任何缓冲池。

如果您通过 httpclient 有很多请求,请将其设为静态变量以避免Socket 泄漏

在此处输入图像描述

我认为更简单的方法来分析这个问题 - 使用PerfView。此应用程序可以显示参考树,因此您可以显示问题的根源。

在此处输入图像描述 在此处输入图像描述

于 2016-10-26T11:54:44.697 回答
0

我们遇到了类似的问题,即 PinnableBufferCache 变得太大并导致OutOfMemoryException's.

在此处输入图像描述

Andrew Au 的分析停止在缓存是静态的“并且在所有请求完成时不会释放”的点上。但更有趣的问题是“在什么条件下发布?” 仍然开放。

根据消息来源,它在 Gen2 GC 事件以及一些非常棘手的其他条件(例如不经常每 10 毫秒等)中被修剪: https ://referencesource.microsoft.com/#System/parent/parent/parent /InternalApis/NDP_Common/inc/PinnableBufferCache.cs,203

我的实验表明,如果进程能够在内存使用炒作中幸存下来,并且负载(即 HTTP 请求的数量)将会减少,那么缓存量也会随着时间的推移而减少。

在我们的案例中,我们发现我们可以极大地优化通过 HTTP 加载的内容量。

我认为替代解决方案可能是为进程提供更多可用的可用虚拟内存,或者在内存使用率过高时限制负载。

于 2018-03-23T10:52:23.433 回答