Could allocating a byte array be performance-critical?
Asked Answered
I

4

6

In my small little file transfer website (this one, running .NET 4.5.1) I'm following the Microsoft Knowledge Base article 812406 to send previously uploaded files from the server to the browser.

Doing performance optimization I was suprised to find that the line

var buffer = new byte[10000];

takes a considerable percentage of time (I'm using Red Gate's ANTS Performance Profiler). The buffer is only allocated once per full download/client.

My questions:

  • Is it good practice to allocate a buffer this way and this size?
  • Any alternatives on allocating a ≈10k buffer?

Update 1:

Thanks to your comments I've seen that the memory is allocated inside the loop, too.

Still, ANTS Profiler only marks that allocation outside of the loop to take that much time, which I honestly do not understand (yet). I have removed the (meaningless) allocation inside the loop.

Update 2:

Having implemented the suggested BufferManager and also reduced the buffer size from 10k down to 4096 (just in case...), my website runs very smooth since days.

Inga answered 5/4, 2014 at 19:21 Comment(16)
What is a "considerable percentage of time?"Philippians
Please tell us what version of .NET you are running.Venice
@RobertHarvey I felt that coming ;-) It's about 30 percent of the whole download method of the linked MS KB article.Inga
Are you reusing the buffer, or creating a new one for each 10,000 bytes? How hard is it actually taxing your processor core? Are you CPU bound, or I/O bound?Philippians
@Venice I've modified my question. It is .net 4.5.1.Inga
@RobertHarvey I'm using the MS KB function nearly identical. So it's just once, not in a loop.Inga
How large is your download?Philippians
Anything can be performance-critical i you spend enough of your time doing it.Spyglass
@RobertHarvey From some MBs up to 1-2 GBs. I'm sure that I'm creating the buffer only once!Inga
The example code reallocates it in a loop.Venice
@Venice Damn it you are right! Crazy enough, ANTS profiler only shows the performance hit on the first allocation.Inga
It seems to me that this buffer= new Byte[10000]; inside the loop is completely superfluous. I would simply drop it;Gallager
First of all, are you having a performance problem?Tao
@LasseV.Karlsen Yes! :-)Inga
OK, and can you describe the performance problem. I believe you when you say that line takes the most CPU, but if, after a lot of back and forth here, we figure out that your performance problem is that you have a 1Mbit network connection to your server, then all of that is for naught.Tao
Thanks, @LasseV.Karlsen - I'm currently trying this BufferManager thing and see, what the profiler tells me.Inga
G
4

Yes. Actually, WCF uses a "buffer manager" to prevent this problem.

I have been myself developing a network service, and during profiling I found that the allocation of Byte[] buffers was creating a bottleneck. Not only during the allocation, but also the time the processor wasted in the GC was very high. Improvements to reuse those buffers and avoid allocations yield very big performance improvements.

You can use the BufferManager class to avoid writing your own buffer management strategy.

Gare answered 5/4, 2014 at 19:29 Comment(3)
I'm currently implementing this right now... Any opinion whether 10k is a too-large buffer in terms of download-block-size?Inga
I use 8K (8192 bytes) as I saw that is what TcpListener uses by default in my computer. 10k should be fine as well. But ensure it is configurable and you can test different values ;)Gare
FYI .NET 5 comes with an ArrayPool<T> class.Lamarre
G
4

Creating objects is usually very fast in .NET, but since this array object has a large size, clearing all its bytes takes a long time. C# always sets all bytes to 0 thus setting all of the fields to their default values when creating objects. (The constructors and field initializers can of course assign different values in classes and structs.)

In the example given by Microsoft the buffer is allocated before the loop and never changes its size. In addition, writing to the output stream writes only the required bytes.

// Gets the exact number of bytes read
length = iStream.Read(buffer, 0, 10000);

// Writes only 'length' bytes to the output
Response.OutputStream.Write(buffer, 0, length);

Therefore there is no need to "clear" the buffer by assigning a new one at each loop iteration. Dirty extra bytes won't hurt.

Solution: Drop the line buffer= new Byte[10000]; inside the while-loop!

Gallager answered 5/4, 2014 at 19:43 Comment(3)
Thank you, I'll do that.Inga
He's stating that this happens once per download/client. It doesn't sound like this is happening inside a loop.Tao
His statement is wrong. The example he mentions does this in a loop.Gallager
S
1

Calling a constructor like that, especially with such a large buffer, is definitely going to take a lot of CPU time. Of course, the answers for your questions are going to be opinion based, but here's mine:

  1. There is nothing wrong with allocating a buffer of that size. 10K isn't that much memory on modern systems. Of course doing so in any kind of loop is going to eat up CPU time very quickly.

  2. If you can, try and avoid reconstructing the buffer for each use. Using a previously defined buffer avoids having to reallocate the memory every time you need it. Of course if this is threaded (multiple connections), each connection/thread would need its own buffer, but at least thats one allocation per connection, not a new buffer for every "chunk" of streamed data as in the linked example.

Shanell answered 5/4, 2014 at 19:27 Comment(3)
Yes, the example code is a poor example. Only allocate the buffer once per client.Venice
Clearing 10.000 bytes is not taking a lot of time. Doing it all the time, sure, but I don't see the evidence that says this is the case.Tao
The example allocates that array in a loop! The comments all seem to agree on this... Running an XNA game with a small constructor in the draw/update loop can cause massive CPU spikes, during a download with a fast connection, that loop is running pretty fast as well.Shanell
F
1

If you have file transfer tasks I suggest to use Microsoft.IO.RecyclableMemoryStream.

It has RecyclableMemoryStreamManager type, which can create RecyclableMemoryStream (which reuses bytes internally).

Pros:

  • It re-use byte arrays internally. E.g. if you need temporary memory stream, these classes will just get it for you, without additional cleanup.
  • It divides internal arrays by chunks.

Cons:

  • You should dispose each stream (otherwise your application will use a lot of memory)
Farias answered 26/10, 2018 at 9:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.