First up, you can pause the GC by calling System.GC.TryStartNoGCRegion
and unpause it with System.GC.EndNoGCRegion
.
For only knowing how many bytes got allocated, there is System.GC.GetAllocatedBytesForCurrentThread
which returns the total bytes allocated for the current thread. Call it before and after the code to measure and the difference is the allocation size.
Counting the number of allocations is a little bit tricky. There are possibly quite a few ways to do it which are all sub-optimal in some way today. I can think of one idea:
Modifying the default GC
Starting with .NET Core 2.1 there is the possibility to use a custom GC, a so called local GC. It's said that the development experience, documentation and usefulness is not the best, but depending on the details of your problem it can be helpful for you.
Every time an object is allocated the runtime calls Object* IGCHeap::Alloc(gc_alloc_context * acontext, size_t size, uint32_t flags)
. IGCHeap
is defined here with the default GC implementation here (GCHeap::Alloc implemented in line 37292).
The guy to talk to here would be Konrad Kokosa with two presentations on that topic: #1, #2, slides.
We can take the default GC implementation as is and modify the Alloc
-method to increment a counter on each call.
Exposing the counter in managed code
Next up to make use of the new counter, we need a way to consume it from managed code. For that we need to modify the runtime. Here I'll describe on how to do that by expanding the GC interface (exposed by System.GC
).
Note: I do not have practical experience in doing this and there are probably some problems to encounter when going this route. I just want to be precise with my idea.
By taking a look at ulong GC.GetGenerationSize(int)
we are able to find out how to add a method which results in an internal CLR call.
Open \runtime\src\coreclr\src\System.Private.CoreLib\src\System\GC.cs#112 and declare a new method:
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern ulong GetAllocationCount();
Next, we need to define that method on the native GCInterface. For that, got to runtime\src\coreclr\src\vm\comutilnative.h#112 and add:
static FCDECL0(UINT64, GetAllocationCount);
To link these two methods, we need to list them in runtime\src\coreclr\src\vm\ecalllist.h#745:
FCFuncElement("GetAllocationCount", GCInterface::GetAllocationCount)
And lastly, actually implementing the method at runtime\src\coreclr\src\vm\comutilnative.cpp#938:
FCIMPL0(UINT64, GCInterface::GetAllocationCount)
{
FCALL_CONTRACT;
return (UINT64)(GCHeapUtilities::GetGCHeap()->GetAllocationCount());
}
FCIMPLEND
That would get a pointer to the GCHeap where our allocation counter lives. The method GetAllocationCount
that exposes this on it does not exists yet, so let's create it:
runtime\src\coreclr\src\gc\gcimpl.h#313
size_t GetAllocationCount();
runtime\src\coreclr\src\gc\gcinterface.h#680
virtual size_t GetAllocationCount() = 0;
runtime\src\coreclr\src\gc\gcee.cpp#239
size_t GCHeap::GetAllocationCount()
{
return m_ourAllocationCounter;
}
For our new method System.GC.GetAllocationCount()
to be usable in managed code we need to compile against a custom BCL. Maybe a custom NuGet package will work here too (which defines System.GC.GetAllocationCount()
as an internal call as seen above).
Closing
Admittedly, this would be quite a bit of work if not done before and a custom GC + CLR might be a bit overkill here, but I thought I should throw it out there as a possibility.
Also, I have not tested this. You should take it as a concept.