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

[client] reduce the number of allocations #105

Merged
merged 10 commits into from
Apr 9, 2020
Merged

[client] reduce the number of allocations #105

merged 10 commits into from
Apr 9, 2020

Conversation

truthbk
Copy link
Member

@truthbk truthbk commented Mar 9, 2020

This is an adaptation of #74 by @njhill by which we take the same principles and apply them to the new client architecture to reduce the overhead of allocations on the client, and consequently on the client application.

Still WIP - a couple of things went wrong after a rebase, so a few things still pending fixes.

@njhill
Copy link
Contributor

njhill commented Mar 11, 2020

@truthbk I started to review yesterday before you made further changes and had actually already written a comment suggesting to have a separate class for the sender Runnables as I see you've now added! Curious why you added separate lists for the StringBuilders/CharBuffers though - why not just have a dedicated instance in each class (in instance fields)? Then you could move the writeBuilderToSendBuffer method to that class too, which would allow for better reuse of the CharBuffers as in the original PR.

I'd also suggest to move other things into this class so that each worker has its own instance since they are not threadsafe:

  • The CharsetEncoder (utf8Encoder)
  • The NumberFormat instances

Anyhow let me know when it is ready and I'll take a look again :)

@truthbk
Copy link
Member Author

truthbk commented Mar 11, 2020

Thank you for taking a look @njhill! The goal is to do all of that too! I'm fixing things one step at a time. Don't spend too much time reviewing this until the WIP tag is removed from the PR :)

In any case, thank you for the feedback, I had the exact same thoughts in mind to avoid code duplication and ensure a clean code structure. I still hadn't gone into verifying the thready safety of CharsetEncoder and NumberFormat, so thank you for that.

I'll let you know when this is ready!

Copy link
Contributor

@ogaca-dd ogaca-dd left a comment

Choose a reason for hiding this comment

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

LGTM: I did not spot anything wrong but it is easy to miss something. You may want to check if this code is well tested.

final NumberFormat numberFormatter = NumberFormat.getInstance(Locale.US);
numberFormatter.setGroupingUsed(false);
numberFormatter.setMaximumFractionDigits(6);
static {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it needed?

Copy link
Contributor

Choose a reason for hiding this comment

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

+1

@@ -753,6 +756,7 @@ public void sends_too_large_message() throws Exception {
final Exception exception = exceptions.get(0);
assertEquals(InvalidMessageException.class, exception.getClass());
assertTrue(((InvalidMessageException)exception).getInvalidMessage().startsWith("_sc|toolong|"));
// assertEquals(BufferOverflowException.class, exception.getClass());
Copy link
Contributor

Choose a reason for hiding this comment

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

To remove?

@truthbk
Copy link
Member Author

truthbk commented Mar 19, 2020

@njhill open for review if you want to chime in! :)

Copy link
Contributor

@njhill njhill left a comment

Choose a reason for hiding this comment

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

@truthbk looks good mostly but left a few comments

interface Message {
/**
* Write this message to the provided {@link StringBuilder}. Will
* only ever be called from the sender thread.
Copy link
Contributor

Choose a reason for hiding this comment

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

Would be more accurate to say "a" sender thread now

final NumberFormat numberFormatter = NumberFormat.getInstance(Locale.US);
numberFormatter.setGroupingUsed(false);
numberFormatter.setMaximumFractionDigits(6);
static {
Copy link
Contributor

Choose a reason for hiding this comment

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

+1

*/
private static final ThreadLocal<NumberFormat> NUMBER_FORMATTERS = new ThreadLocal<NumberFormat>() {
private static final ThreadLocal<NumberFormat> NUMBER_FORMATTER = new ThreadLocal<NumberFormat>() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not also have the formatters as fields in ProcessingTask instead of ThreadLocals?

protected static final String MESSAGE_TOO_LONG = "Message longer than size of sendBuffer";
protected static final int WAIT_SLEEP_MS = 10; // 10 ms would be a 100HZ slice

protected final StatsDClientErrorHandler handler;

protected final BufferPool bufferPool;
protected final List<StringBuilder> builders; // StringBuilders for processing, 1 per worker
protected final List<CharBuffer> charBuffers; // CharBuffers for processing, 1 per worker
Copy link
Contributor

Choose a reason for hiding this comment

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

I think these two are now obsolete?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes.

CharBuffer buffer = CharBuffer.wrap(builder);
builders.add(builder);
charBuffers.add(buffer);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Now obsolete?

Copy link
Member Author

Choose a reason for hiding this comment

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

And Yes.

private final int qcapacity;
private final AtomicInteger qsize; // qSize will not reflect actual size, but a close estimate.

private class ProcessingTask extends StatsDProcessor.ProcessingTask {
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the difference between the two impls, they look pretty similar... could the common logic be de-duped i.e. moved into the superclass?

Copy link
Member Author

Choose a reason for hiding this comment

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

They're pretty close but the logic dealing with the queues blocking vs non-blocking is a little different. I might clean that up, but would rather do it in a separate PR.


if (Thread.interrupted()) {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This if block not needed

Copy link
Member Author

Choose a reason for hiding this comment

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

cleared it out.

return;
}

while (!(messages.isEmpty() && shutdown)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is probably better as !(shutdown && messages.isEmpty()) to remove unnecessary queue contention

try {
writeBuilderToSendBuffer(sendBuffer);
} catch (BufferOverflowException boe) {
outboundQueue.put(sendBuffer);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think sendBuffer.reset() is needed here as I had in the original PR, otherwise the message will span two packets (same below)

Copy link
Member Author

@truthbk truthbk Mar 20, 2020

Choose a reason for hiding this comment

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

Ummm, not sure. that breaks the tests too, I'll dig a little.

Copy link
Member Author

Choose a reason for hiding this comment

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

It has no effect, tried a bunch of tests same packet count, same message count. Resetting the bottom one will actually break things. So I believe we don't need the resets at all.

* http://stackoverflow.com/a/1285297/2648
* https://github.com/indeedeng/java-dogstatsd-client/issues/4
* The NumberFormat instances are not threadsafe but are only ever called from
* the sender thread.
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment now obsolete

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated it.

@truthbk
Copy link
Member Author

truthbk commented Mar 20, 2020

Gotta fix a few things after the rebase to telemetry - don't panic! 😉

njhill and others added 9 commits April 8, 2020 22:01
This is an optimization to reduce the amount of objects allocated when the various metrics are recorded, and minimize processing done on the application threads.

The various events are now passed in "raw" form via dedicated objects to the sender thread (rather than first being serialized to a string on the app thread). All of the serialization work is now done on the sender thread, avoiding allocation of any new strings or byte arrays. A single StringBuilder is reused in conjunction with the existing ByteBuffer. The NumberFormat ThreadLocals are no longer needed since they will now only be used by the one thread.

Also included are a couple of thread-safety tweaks to the unit tests.
@truthbk truthbk merged commit 28055c0 into master Apr 9, 2020
@truthbk truthbk deleted the jaime/less_alloc branch April 9, 2020 02:52
@truthbk truthbk added this to the 2.10.0 milestone May 5, 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.

4 participants