Oren Eini

CEO of RavenDB

a NoSQL Open Source Document Database

Get in touch with me:

oren@ravendb.net +972 52-548-6969

Posts: 7,495
|
Comments: 51,046
Privacy Policy · Terms
filter by tags archive
time to read 4 min | 612 words

Here is the full method that we refactored:

 public void ReturnMemory(byte* pointer)
 {
     var memoryDataForPointer = GetMemoryDataForPointer(pointer);

     _freeSegments.AddOrUpdate(memoryDataForPointer.SizeInBytes, x =>
     {
         var newQueue = new ConcurrentStack<AllocatedMemoryData>();
         newQueue.Push(memoryDataForPointer);
         return newQueue;
     }, (x, queue) =>
     {
         queue.Push(memoryDataForPointer);
         return queue;
     });
 }

And here is the allocation map for this method:

public unsafe void ReturnMemory(byte* pointer)
{
    <>c__DisplayClass9_0 CS$<>8__locals0 = new <>c__DisplayClass9_0();
    CS$<>8__locals0.memoryDataForPointer = this.GetMemoryDataForPointer(pointer);
    this._freeSegments.AddOrUpdate(CS$<>8__locals0.memoryDataForPointer.SizeInBytes, 
new Func<int, ConcurrentStack<AllocatedMemoryData>>(CS$<>8__locals0.<ReturnMemory>b__0),
new Func<int, ConcurrentStack<AllocatedMemoryData>, ConcurrentStack<AllocatedMemoryData>>(CS$<>8__locals0.<ReturnMemory>b__1)); }

As you can see, we are actually allocating three objects here. One is the captured variables class generated by the compiler (<>c__DisplayClass9_0) and two delegate instances. We do this regardless of if we need to add or update.

The refactored code looks like this:

 public void ReturnMemory(byte* pointer)
 {
     var memoryDataForPointer = GetMemoryDataForPointer(pointer);

     var q = _freeSegments.GetOrAdd(memoryDataForPointer.SizeInBytes, size => new ConcurrentStack<AllocatedMemoryData>());
     q.Push(memoryDataForPointer);

 }

And what actually gets called is:

public unsafe void ReturnMemory(byte* pointer)
{
    Interlocked.Increment(ref this._returnMemoryCalls);
    AllocatedMemoryData memoryDataForPointer = this.GetMemoryDataForPointer(pointer);
    if(<>c.<>9__9_0 == null)
    {
        <>c.<>9__9_0 = new Func<int, ConcurrentStack<AllocatedMemoryData>>(this.<ReturnMemory>b__9_0);
    }
    this._freeSegments.GetOrAdd(memoryDataForPointer.SizeInBytes, <>c.<>9__9_0).Push(memoryDataForPointer);
}

The field (<>c.<>9__9_0) is actually a static field, so it is only allocated once. Now we have a zero allocation method.

time to read 2 min | 216 words

In a recent code review, I had modified the following code:

_freeSegments.AddOrUpdate(memoryDataForPointer.SizeInBytes, x =>
{
   var newQueue = new ConcurrentQueue<AllocatedMemoryData>();
   newQueue.Enqueue(memoryDataForPointer);
   return newQueue;
}, (x, queue) =>
{
   queue.Enqueue(memoryDataForPointer);
   return queue;
});

Into this code:

var q = _freeSegments.GetOrAdd(memoryDataForPointer.SizeInBytes, 
                         size => new ConcurrentQueue<AllocatedMemoryData>());
q.Enqueue(memoryDataForPointer);

Can you tell me why?

FUTURE POSTS

No future posts left, oh my!

RECENT SERIES

  1. Recording (13):
    05 Mar 2024 - Technology & Friends - Oren Eini on the Corax Search Engine
  2. Meta Blog (2):
    23 Jan 2024 - I'm a JS Developer now
  3. Production postmortem (51):
    12 Dec 2023 - The Spawn of Denial of Service
  4. Challenge (74):
    13 Oct 2023 - Fastest node selection metastable error state–answer
  5. Filtering negative numbers, fast (4):
    15 Sep 2023 - Beating memcpy()
View all series

Syndication

Main feed Feed Stats
Comments feed   Comments Feed Stats
}