-
Notifications
You must be signed in to change notification settings - Fork 102
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
Conversation
613bbe4
to
6cf2806
Compare
c99049b
to
1be0566
Compare
@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 I'd also suggest to move other things into this class so that each worker has its own instance since they are not threadsafe:
Anyhow let me know when it is ready and I'll take a look again :) |
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 I'll let you know when this is ready! |
There was a problem hiding this 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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it needed?
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To remove?
@njhill open for review if you want to chime in! :) |
There was a problem hiding this 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. |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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>() { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now obsolete?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; | ||
} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment now obsolete
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated it.
d5cd46b
to
6f859d9
Compare
Gotta fix a few things after the rebase to telemetry - don't panic! 😉 |
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.
b1e7aa4
to
043615b
Compare
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.