Ayende @ Rahien

My name is Oren Eini
Founder of Hibernating Rhinos LTD and RavenDB.
You can reach me by phone or email:


+972 52-548-6969

, @ Q c

Posts: 6,125 | Comments: 45,488

filter by tags archive

Voron Performance, the single biggest booster

time to read 4 min | 603 words

One of the surprising points for improvement in our performance run was the following logic, responsible for copying the data from the user to our own memory:

   1: using (var ums = new UnmanagedMemoryStream(pos, value.Length, value.Length, FileAccess.ReadWrite))
   2: {
   3:     value.CopyTo(ums);
   4: }

Those three lines of code were responsible for no less than 25% of our performance. It was obvious that something needed to be done. My belief is that the unmanaged memory stream is just not optimized for this scenario, resulting in a lot of copying, allocations and costs.

Here is what we did instead. We create a temporary space that is allocated once, like this:


You can see that we are doing some interesting stuff there. In particular, we are allocated a managed buffer, but also force the GC to pin it. We keep this around for the entire lifetime of the database, too. The idea here is that we want to avoid the cost of pinning & unpinning it all the time, even if it means that we have an unmovable memory.

At any rate that important thing about this is that it gives us access to the same memory from managed and unmanaged perspectives. And that, in turn, leads to the following code:


We first read the values from the stream into the managed buffer, then copy them from the unmanaged pointer to the buffer to our own memory.

The idea here is that we accept a Stream abstraction, and that can only work with managed buffers, so we have to go through this route, instead of having to copy the memory directly. The reason we do that is that we don’t want to force the user of our API to materialize the data fully. We want to be able to stream it into the database.

At any rate, this has made some serious improvement to our performance, but I’ll be showing the details on a future post.



Why TempPagePointer is just a byte* ?


Ohh... delete this question : )

Lars Wilhelmsen


Have you considered what will happen over time (e.g. over many GC cycles) if you have a lot of these pinned memory segments inside your managed heap?


Ayende Rahien

Lars, There is just one such temporary page for the entire database.

Paul Turner

This is exactly the kind of performance analysis and optimisation we should be teaching:

  1. Identify the part of an operation which is slowest by measuring.
  2. Replace a general-purpose component with something tailored to the specifics of the situation at hand.

Any other kind of "optimisation" is just hand-waving.

I shall be using this article as a concise example to others on the matter.

Marc Jacobi

Two observations: 1) you implement a read loop on a size (AbstractPager.PageSize) that is the same as the buffer length of the TemporaryPage object. 2) You should probably call GC.AddMemoryPresure in the ctor and GC.ReleaseMemoryPresure in the Dispose of the TemporaryPage class when the page size is "large" (whatever that is).

Ayende Rahien

Marc, 1) I don't follow your first point. Is there something specific that you were trying to say? 2) There is no additional memory being allocated here.

Ayende Rahien

Simon, Let assume that I start a read transaction (RT-1), which starts reading from the page table. Then we have a write transaction, that modify the page table. RT-1 is still operating, and needs to see the same page table that it had when it started.

Marc Jacobi

1) You use a while(true) loop to block-Read the content of value ...? Doesn't the Read call always return 0 on the second pass? If so, the while loop is unnecessary. If not, I would expect value.Length for the count parameter in the Read method call...

2) You're right, it managed memory - I thought it was unmanaged. ;-)

Ayende Rahien

1) Read() is freed to read LESS than the buffer size. The only contract it gives is that 0 means no more data. Simplest scenario, consider the case where you have a value that is 6000 bytes long.

Comment preview

Comments have been closed on this topic.


  1. The design of RavenDB 4.0: Physically segregating collections - 4 hours from now
  2. RavenDB 3.5 Whirlwind tour: I need to be free to explore my data - about one day from now
  3. RavenDB 3.5 whirl wind tour: I'll have the 3+1 goodies to go, please - 4 days from now
  4. The design of RavenDB 4.0: Voron has a one track mind - 5 days from now
  5. RavenDB 3.5 whirl wind tour: Digging deep into the internals - 6 days from now

And 12 more posts are pending...

There are posts all the way to May 30, 2016


  1. The design of RavenDB 4.0 (14):
    03 May 2016 - Making Lucene reliable
  2. RavenDB 3.5 whirl wind tour (14):
    04 May 2016 - I’ll find who is taking my I/O bandwidth and they SHALL pay
  3. Tasks for the new comer (2):
    15 Apr 2016 - Quartz.NET with RavenDB
  4. Code through the looking glass (5):
    18 Mar 2016 - And a linear search to rule them
  5. Find the bug (8):
    29 Feb 2016 - When you can't rely on your own identity
View all series


Main feed Feed Stats
Comments feed   Comments Feed Stats