-
Notifications
You must be signed in to change notification settings - Fork 342
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
Tuning of readPool buffer sizes #556
Comments
Trimmed output from pprof:
|
If the request is small, the 1M buffer is returned immediately after it is read. AFAIK, you don't know upfront which request you get so you have to be prepared for the worst (be happy to be proven wrong; maybe you can have 2 read loops, one with the large one, and one with the small one). |
The code you quote provisions space for answering to READ calls. It isn´t used for LOOKUP/FORGET/etc. |
I was thinking if it is possible to do it similar to how the outPayloadSize is calculated for buffers from the Lines 559 to 572 in aa9c516
Perhaps a smaller buffer that could be used to determine the type of request from the header and then size it appropriately.
It seems to be allocated for all types of requests per this, I don't see any if condition which allocates the buffer only for READ or WRITE requests: Lines 351 to 359 in aa9c516
|
Sorry, I was confused by the naming the This buffer is put back into the pool if it's not used, see Line 381 in aa9c516
|
Yeah, but that's still a huge allocation, which may not be fully used, which is what I want to reduce. It could result in a lot of huge buffers lying in the sync.Pool (as seen in the pprof output above) after a burst of high activity which may take a long time to be GC-ed. |
that is WAI , no? High activity costs memory, and memory needs GC to be reclaimed.
how would that work? If you try to read from the device with a buffer that is too small, the read will fail with EINVAL. |
Perhaps the buffer allocation could be skipped till we know the input size? So, just a plain byte slice for the initial Meaning the pool alloc moves from here: Lines 351 to 359 in aa9c516
to here: Lines 559 to 572 in aa9c516
|
I suggest you try out the idea, and send me a PR if it works. |
Btw, just reading the code further, the max concurrency for requests seems to be With that value, I’m not sure how so many byte slices are even allocated because they should have been getting re-used. Something for me to check further. |
@hanwen Forgive me as I'm unfamiliar with go-fuse but... how does it manage /dev/fuse? Is it like libfuse with a thread pool reading from the fd and then handling each request or is it reading messages and then passing them off to something else to process? If the latter is that queue bounded? |
@trapexit - look for singleReader in server.go and read surrounding code. |
Using a fixed-size pool with a channel for synchronization seems to be a lot better on memory usage. That function has all but disappeared from the memory profile, and the allocs in the benchmarks have also reduced.
Will let it run for a few days to see if it remains stable before sending a PR. |
I'm just trying to help @darthShadow. The question is merely if there is a bounded or unbounded message reading functionality. @darthShadow are you using singleReader? The reason unbounded is bad is because while your FUSE server is likely to respond faster than incoming requests can be made that holds generally true only till "forget" messages come in. They can come in huge waves when there is any cache / memory pressure or there are forced node forgets. These messages require no response and can come in in the hundreds or thousands in a short period. |
No, this is with |
recent kernels would send them as batches (see BatchForget type).
it's unbounded, both for singleReader=false and singleReader=true. |
I would expect a performance degradation for highly parallel filesystem access. It would be interesting to see a benchmark that reproduces that, so we can understand the tradeoff better.
This should be easy to reproduce; you can force a cache drop by writing into |
For Lines 193 to 198 in aa9c516
Lines 342 to 345 in aa9c516
I’ve set the size to Lines 544 to 548 in aa9c516
Let me know if my understanding is wrong here and I’ve missed something. |
maxReaders is exactly what it says: it is the maximum number of goroutines that can be reading the /dev/fuse device, and therefore also the maximum number of outstanding buffers from the readPool. The maximum number of concurrent requests is still unbounded: as soon as a read completes, we start processing the request, but also start a new reader to pick up the slack. |
Not always. I ran into this issue of unbounded processing within the past 24 months. Batch forget has been around for many years and even recent'ish kernels I've seen floods of regular forgets. |
How does https://review.gerrithub.io/c/hanwen/go-fuse/+/1207746 work for you? |
Thanks for the explanation and correcting my flawed understanding. I’ve cherry-picked your commit into my fork for testing, but it will be a while before I get to it, sorry. |
I've submitted the backpressure change, because it seemed sensible on principle. I am not sure if this will fix the original issue, but for that I would need a reliable reproduction of the problem. |
So, I modified the fixed-size pool slightly by backing it with a sync.Pool, which increased the allocs but should allow for better reuse of the structs. Reference: darthShadow@7af94a6#diff-8e280562298ff7d5f517dba57766a75956945d309b7b99d805e0fa2dec1e55c8 With that and the backpressure change, the numbers are quite promising. The percentage of alloc_space in the heap profile for the readpool has gone down from almost 30% to less than 1%. Will keep it running for a few days to see if the numbers hold up over time. |
It would be interesting if you could tease apart which change made the difference: your custom pool or the backpressure.
? the blog posts that you link are both very old (one says explicitly "THIS BLOG POST IS VERY OLD NOW. YOU PROBABLY DON'T WANT TO USE THE TECHNIQUE DESCRIBED HERE. GO'S sync.Pool IS A BETTER WAY TO GO."). The |
Would it be possible to tune the buffer sizes a little bit intelligently for the readPool here:
go-fuse/fuse/server.go
Lines 218 to 229 in aa9c516
It starts with a minimum buffer size of
1M
(because of MaxWrite being set to1M
) which, as per my understanding, is not necessary for most requests like LOOKUP, FORGET, READDIR etc.I have a mostly read-heavy mount (with reads done via passthrough), and in times of high usage, the memory grows quite a bit with a majority of it due to the readPool allocations. It does go down eventually during quieter periods, so there is no leak, just something to enhance.
I guess I could revert to the default MaxWrite value, but I wanted to check if the pool could be enhanced instead and let me keep my faster writes 😅 .
The text was updated successfully, but these errors were encountered: