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

Remove superfluous encoder.Reset() #262

Closed
wants to merge 1 commit into from

Conversation

dferstay
Copy link

When using the Apache Pulsar go client
(https://github.com/apache/pulsar-client-go) configured to use zstd
compression during message production in high-throughput service, we
see high-memory allocation due to Encoder.Reset().

Encoders on the e.encoders channel are already reset;
there is no need to reset an encoder after pulling it from the channel.

Signed-off-by: Daniel Ferstay dferstay@splunk.com

@dferstay dferstay force-pushed the remove-superfluous-reset branch from f2db7b8 to ab8538f Compare May 26, 2020 21:16
When using the Apache Pulsar go client
(https://github.com/apache/pulsar-client-go) configured to use zstd
compression during message production in high-throughput service, we
see high-memory allocation due to Encoder.Reset().

Encoders on the `e.encoders` channel are already reset;
there is no need to reset an encoder after pulling it from the channel.

Signed-off-by: Daniel Ferstay <dferstay@splunk.com>
@dferstay dferstay force-pushed the remove-superfluous-reset branch from ab8538f to 37efcfd Compare May 26, 2020 21:17
@klauspost
Copy link
Owner

Very nice! Thanks for the contribution.

I plan to make a release soon. Probably when #260 is done which is making slow but steady progress.

@klauspost
Copy link
Owner

@dferstay Out of curiosity - where did you observe the allocations (code line or type of alloc)?

It shouldn't really do any allocations once it has 'warmed up', so I am a bit curious what parts are doing the allocations.

@klauspost
Copy link
Owner

I will check the failures tomorrow, heading to bed now. It should work as you propose the change AFAICT. There might be a problem where it relies on the double Reset when re-using.

@klauspost
Copy link
Owner

It appears to pick of phantom matches across Resets when only one is done.

@dferstay
Copy link
Author

dferstay commented May 26, 2020

@dferstay Out of curiosity - where did you observe the allocations (code line or type of alloc)?

It shouldn't really do any allocations once it has 'warmed up', so I am a bit curious what parts are doing the allocations.

We see allocations coming from the zstd/enc_fast.go, the Reset() method. Looking at the code in Reset(), there are attempts to reuse existing objects and the arrays that back slices, so I'm a little confused where the space is being burned. See the attached pprof -inuse_space graph.

pprof_heap_alloc

In our testing, the pulsar topic that we are writing to has eight partitions; this means there are:

  • 8 * PartitionProducer instances
  • 8 * BatchBuilder instances

All 8 BatchBuilder intances will use the same Zstd Encoder instance.

The code that calls the encoder is here:
https://github.com/apache/pulsar-client-go/blob/e31d474c5d25f13cfdc69596e1c58a2a9e7d5fb7/pulsar/internal/compression/zstd.go#L41

@dferstay
Copy link
Author

We are currently using the default encoder options, which means we are using a doubleFastEncoder with:

  • blockSize: 1 << 16
  • windowSize: 8 << 20
    With the above windowSize, the encoder's maxMatchOff is ~33MB; when the histogram for the encoder is allocated it will have a capacity of 2*maxMatchOff, or ~66MB. Is ~288MB normal memory use for a doubleFastEncoder?

klauspost added a commit that referenced this pull request May 27, 2020
Increment buffer offset when encoding without history.

Since it does not store history, we must offset `e.cur` to avoid false matches for next user.

Fixes the need for double resets to clear history.

Prepares for #262
@klauspost
Copy link
Owner

klauspost commented May 27, 2020

Correct, you did the math yourself. To support the default number of concurrent encodes it needs a history for each. I am guessing you are testing on a CPU with GOMAXPROCS set to 8 and 33x8 = 264.

You can tweak the number of encoders available with WithEncoderConcurrency (will enable more or less concurrent encodes) and the window size with WithWindowSize (smaller gives less compression).

Window size can be adjusted down without any penalty if your typical payload is smaller.

I will look into the complexity of reducing the upfront allocations where we know the payload size when using EncodeAll. When it is less than a single block size (128K) we don't need to track the history at all.

@klauspost
Copy link
Owner

klauspost commented May 27, 2020

I have updated #263 to only allocate history when it is needed, so single blocks < 128K will not have a history allocated (but will keep it if it already has been).

I don't want to risk having to re-allocate history, so we either allocate what we want or nothing at all if we don't need it. I will do some fuzz testing before merging.

This will give double Resets in some cases, but that itself is pretty harmless.

To control maximum memory usage you can adjust the parameters described above.

klauspost added a commit that referenced this pull request May 27, 2020
Increment buffer offset when encoding without history.

Since it does not store history, we must offset `e.cur` to avoid false matches for next user.

Fixes the need for double resets to clear history.

Only allocate history when we need to encode more than 1 block.

Replaces #262
@dferstay
Copy link
Author

I am guessing you are testing on a CPU with GOMAXPROCS set to 8 and 33x8 = 264

Correct.

I have updated #263 to only allocate history when it is needed, so single blocks < 128K will not have a history allocated (but will keep it if it already has been).

Excellent!

To control maximum memory usage you can adjust the parameters described above.

Thank you very much for the explanation; much appreciated.

@dferstay dferstay closed this May 27, 2020
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.

2 participants