Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Simple version of PooledMemoryStream #452

Closed
wants to merge 5 commits into from

Conversation

Pliner
Copy link
Contributor

@Pliner Pliner commented Sep 13, 2018

Proposed Changes

This PR continues work on allocations(see #443). Its goal is to provide the pool of MemoryStream to reduce allocations, which were caused by resize of the internal buffer of MemoryStream.

The idea of PR is not to pool MemoryStream, but to pool internal buffers. So, PooledMemoryStream is introduced, which is the same as MemoryStream, but it also pools internal buffers with help of System.Buffers.

A bit more info about System.Buffers:

  1. It has default shared implementation(ArrayPool<T>.Shared), which can be reused not only by rabbitmq client.
  2. The default implementation has the following allocation strategy: it allocates arrays of size 2^i, where i from 4 to 20. If the size of renting array is larger than 2^20, it will allocate an array outside of the pool.
  3. Also it has some trimming strategy, which helps to free memory in case of low memory.
  4. This package is referenced transitively by MongoDB Client(DnsClient references System.Buffers), so it may be not so awfull to have this package as dependency.

Types of Changes

What types of changes does your code introduce to this project?
Put an x in the boxes that apply

  • Bugfix (non-breaking change which fixes issue #NNNN)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation (correction or otherwise)
  • Cosmetics (whitespace, appearance)

Checklist

Put an x in the boxes that apply. You can also fill these out after creating
the PR. If you're unsure about any of them, don't hesitate to ask on the
mailing list. We're here to help! This is simply a reminder of what we are
going to look for before merging your code.

  • I have read the CONTRIBUTING.md document
  • I have signed the CA (see https://cla.pivotal.io/sign/rabbitmq)
  • All tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation (if appropriate)
  • Any dependent changes have been merged and published in related repositories

Further Comments

Producer.cs

    public static class EntryPoint
    {
        public static void Main(string[] args)
        {
            var connectionFactory = new ConnectionFactory();
            using (var connection = connectionFactory.CreateConnection())
            {
                using (var model = connection.CreateModel())
                {
                    model.ExchangeDeclare("Exchange", "direct", true);
                    for (var i = 0; i < 10000; ++i)
                    {
                        model.BasicPublish("Exchange", "#", body: new byte[100000]);
                    }
                }
            }   

            Console.ReadKey();
        }
    }

Before:
image
Before.zip

After:
image
After.zip

As you can see, there are no more huge allocations of byte arrays except Producer. We can expect the same behaviour when size of body is less then 1 megabytes, because max size of pooled array is 2^20.

One more non-proven observation: after this fix Producer.cs(I recompiled it in Release mode) works significantly faster(before - 3.7s , after - 2.5s).

PS It's an experiment. Please, do not merge it, because PooledMemoryStream is not properly tested. Also I'm sure we need to measure the performance too.

@michaelklishin
Copy link
Member

Almost a 4-fold peak heap usage reduction, good job @Pliner!

@@ -75,7 +75,7 @@ public Command(MethodBase method, ContentHeaderBase header, byte[] body)
}

public byte[] Body { get; private set; }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please be aware of and remove whitespace changes like these.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeap, sorry, missed it 😞

@@ -67,10 +67,12 @@ public static async Task TimeoutAfter(this Task task, int millisecondsTimeout)
}
}


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this newline added by an automatic tool of some sort?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't know, actually. Fixed it.

@kjnilsson
Copy link
Contributor

Looks very promising. :)

@paulomorgado
Copy link
Contributor

Have you looked at pipelines?

https://blog.marcgravell.com/search/label/pipelines

@michaelklishin
Copy link
Member

@Pliner the title still says WIP. Should we wait for more changes, e.g. are you going to adopt pipelines? And what'd be the minimum required .NET version be if so? Thanks.

@bording
Copy link
Collaborator

bording commented Sep 16, 2018

@michaelklishin Using pipelines would be a fundamental re-architecture of the entire client. It's what my experimental client, Angora, is using.

@Pliner
Copy link
Contributor Author

Pliner commented Sep 17, 2018

@bording @paulomorgado Thanks for information about pipelines 😉

@michaelklishin I will push some cosmetic changes today. Is it ok to add System.Buffer as a dependency to client?

PS Also today I will try to benchmark client at least on EasyNetQ.

@kjnilsson
Copy link
Contributor

@Pliner generally we haven't had a very good experience with adding dependencies to the client. That said System.Buffers looks pretty safe. Does it work with net451?

@Pliner
Copy link
Contributor Author

Pliner commented Sep 17, 2018

@Pliner generally we haven't had a very good experience with adding dependencies to the client. That said System.Buffers looks pretty safe. Does it work with net451?

Yep.

I'm not sure too, but I will help to reimplement this pool inside the client as was suggested by @Scooletz in #443 in case of any troubles due to dependency.

@kjnilsson
Copy link
Contributor

The troubles with dependencies are hard to predict without a proper release that people actually use. For now I would prefer if we tried to avoid taking dependencies unless you think there is a substantial risk of severe bugs re-implementing the pool?

@Pliner
Copy link
Contributor Author

Pliner commented Sep 17, 2018

The troubles with dependencies are hard to predict without a proper release that people actually use. For now I would prefer if we tried to avoid taking dependencies unless you think there is a substantial risk of severe bugs re-implementing the pool?

Deal. I will implement simple pool and compare it to ArrayPool from System.Buffers. I hope it will help to decide is it worth to depend on it or not 😄

@bording
Copy link
Collaborator

bording commented Sep 17, 2018

One reason that a reference to System.Buffers might not be a good idea is that it only ships a netstandard1.1 and netstandard2.0 version of the lib, so if you're using net461 through net471 you can end up in some weird pain because of how the .NET Standard 2.0 support for those versions works. Lots of difficult to solve binding redirect errors, etc.

@paulomorgado
Copy link
Contributor

And it's not trivial to everyone to get out of that mess.

@paulomorgado
Copy link
Contributor

The beauty of it is that we don't need to have the same implementation for all targets. Targets the benefit from better libraries should take advantage of it.

@michaelklishin
Copy link
Member

@Pliner there's some interest in a 5.2.0 release in the next few days. Is this still a WIP? Should we QA it?

@michaelklishin
Copy link
Member

I see that the System.Buffers dependency is still there. So maybe this can go into 5.3.0 then.

@Pliner
Copy link
Contributor Author

Pliner commented Sep 19, 2018

I see that the System.Buffers dependency is still there. So maybe this can go into 5.3.0 then.

Yes. I need a bit more time. I hope today I will push the new pool and remove System.Buffers dependency.

@michaelklishin michaelklishin added this to the 6.0 milestone Sep 19, 2018
@michaelklishin
Copy link
Member

michaelklishin commented Sep 19, 2018

Currently we have #441 scheduled for 5.2.0 since it is completely backwards-compatible w.r.t. to the public API. and #442, #443 and this PR will have to potentially wait till 6.0.

0. Formatting
1. Rename GetSegment to GetBufferSegment
2. Exceptions of PooledMemoryStream match to exceptions of MemoryStream
@Pliner
Copy link
Contributor Author

Pliner commented Sep 20, 2018

I pushed the new pool. Several considerations:

  1. ArrayPool allocates lazily ~16mb of arrays.
  2. Pool is a simple version of Roslyn pools. Merely the same approach is implemented in StackExchange.Redis.
  3. ArrayPool was not tested or compared with ArrayPool from System.Buffers. I will do it soon.

@paulomorgado
Copy link
Contributor

@Pliner,

If it's the same API, consider, for targets that can depend on System.Buffers, use System.Buffers instead of replicating it.

@rwkarg
Copy link

rwkarg commented Nov 12, 2018

There is also a provided RecyclableMemoryStream that handles internal pooling of buffers (most applicable to this use case) and also using multiple small buffers instead of one large one for large streams to avoid LOH allocation (probably less useful, though there certainly could be messages larger than 85K bytes)
https://github.com/Microsoft/Microsoft.IO.RecyclableMemoryStream
This would be a good battle tested (it's used by the Bing team) option to evaluate.

@stebet
Copy link
Collaborator

stebet commented Jan 25, 2020

I've made several related/similar changes using the latest libraries. Check out #694 and the associated PR once I submit it (ironing out the last remaining bugs in tests). It includes System.IO.Pipelnes support as well.

@Pliner
Copy link
Contributor Author

Pliner commented Jan 26, 2020

Closed because of #694

@Pliner Pliner closed this Jan 26, 2020
@Pliner Pliner deleted the pool-memory-stream branch January 26, 2020 06:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants