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
Draft of Blocking Queue #46
Conversation
update latest minor changes
Good job so far. Here are a few hints:
new SupplierJDK6<E>() {
@Override
public E get() {
return poll();
}
}
|
I'm with you on codegen. What we want is a class Q to be extended by a generic blocking stategy to give us some sort of mixin of strategy and lock free queue. We can try and do that with @RichardWarburton templates. |
@Override | ||
public E waitFor(SupplierJDK6<E> supplier) | ||
{ | ||
while(true) // Should introduce safepoints |
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.
Thread.yield() should introduce the safepoint, no?
In any case this loop will be just as correct written naturally:
E e;
while((e = s.get()) == null)
Thread.yield();
return e;
Thinking out loud, we have 2 threads, here's a simplified view of what's happening:
So the question: I think it should work, but having a bad week in terms of sleep, so not trusting myself much. |
That's exactly it. On Thu Jan 15 2015 at 9:17:14 AM Nitsan Wakart notifications@github.com
|
@nitsanw could you point me to the entrypoints of the templates of @RichardWarburton? |
Have a look at this commit from @RichardWarburton: d8d9a4c |
You can also see the class in question at: https://github.com/JCTools/JCTools/blob/master/jctools-experimental/src/main/java/org/jctools/util/Template.java And simple examples of usage at https://github.com/JCTools/JCTools/blob/master/jctools-experimental/src/test/java/org/jctools/util/TemplateTest.java |
Thanks to the cool Template and SimpleCompiler of @RichardWarburton!
The Template and SimpleCompiler classes are pretty cool. Thanks @RichardWarburton! Next step: Mixin Take and Put strategies? Your feedback welcomed. Cheers |
Thanks for all the hard work, this is looking pretty good. |
I have done some benchmark internally and this form is lighter, probably just by the way it signal. |
I do not see the benefits of the dynamic creation. Why don't you generate blocking implementations as java files? Moreover, a blocking implementation is currently always generated, even if it has been generated once or more before. |
@ChristianWulf dynamic creation can help in minimising the size of the deployed jar especially as more parameters are added to the generated flavours. It can also offer some interesting optimisation options that are otherwise not easy to achieve (e.g. several padding configurations for same queue). |
Caching should be evaluated on a case-by-case basis. Specifically code generating new instances can give the JIT compiler opportunities for optimisation that wouldn't be possible otherwise. Obviously it also has a potential tradeoff in terms of additional memory consumption and JIT overhead. So the ability to generate new instances vs cached instances is something that end users looking for optimal performance might want to have control over. |
Should I just add a cache layer in from of it? On Mon Jan 19 2015 at 2:38:35 PM Richard Warburton notifications@github.com
|
Conflicts: jctools-core/pom.xml jctools-core/src/main/java/org/jctools/queues/MpscLinkedQueue8.java
Great conversations going on, and since @nitsanw pointed me to this ticket I'd like to chime in with my use case - maybe you can see if this fits or not? :) I'm currently using the disruptor in a "MPSC" setup, but it only allows you to configure the producer side (so it's set to multi). I'm using it for two specific things:
Backpressure can easily be covered anyways, but are the batching semantics that make sense in that approach? Also, I guess making the waiting strategies configurable is a must (?) since I care about latency, but also I can't spin wait because this runs in a customer environment alongside with other services. Looking forward to help / test / whatever makes sense for you folks :) |
I'm not a fan of the batch end approach in the disruptor as it takes the following form:
This leads to:
I would suggest you pick a batch size you are happy with because flushing every X messages makes sense, and use the stronger guarantee of poll == null as a further trigger. |
@nitsanw makes sense.. I just wonder what the right batch size is, maybe it could also be latency driven (flush after N µs or when written before and poll == null) |
you are describing a mini-nagles algo:
For sending over network in less latency sensitive envs. it makes sense to wait for more messages before sending (as does nagles) |
yes, thanks - let's not pollute this topic here with it :) |
What about a SleepTakeStrategy where the consumer goes to bed for a configurable amount of time if the queue is empty? |
Seems like a specialization of the Park one?
|
When looking at the |
In particular it seems redundant for a notification based implementation to wake up periodically as the interface does not allow the thread to 'escape' from the take/put methods. |
Hi @daschl, Due to the single consumer relaxed constraint, the waiting Park strategy in this pull request is very fast to wake up. Faster than a object.notify or condition.signal/signalAll that you will found in a ArrayBlockingQueue. I needed something fast to wake up and we have benchmark this wake up time carefully (bench not provided here - but it's a single sleep -> round trip time on the queue). The backpressure is pretty single as you said, just look for offer() returning false. For the batching: Let's create a batching interface interface BatchingConsumer
{
void startBatch()
void newElement(E e)
void endBatch()
} In the consuming thread you do something like this: public void run()
{
while(true)
{
// wait for a first element
E e = queue.take();
batchConsumer.startBatch();
batchConsumer.newElement(e);
// Spin with some yield
for(int i=0; i<100;i++)
{
E e = queue.poll()
if (e != null)
batchConsumer.newElement(e);
else
Thread.yield();
}
batchConsumer.endBatch();
}
} This will make sure you don't have more than 100 elements in one batch. But I don't like to wait for data. I would rather do: public void run()
{
while(true)
{
// wait for a first element
E e = queue.take();
batchConsumer.startBatch();
batchConsumer.newElement(e);
// Batch
for(int i=0; i<100;i++)
{
E e = queue.poll()
if (e != null)
batchConsumer.newElement(e);
else
break;
}
batchConsumer.endBatch();
}
} Taking full advantage of the thread parking efficiency. |
With some check for QueueSpec support
@georges-gomes very interesting, thanks for sharing. Now I just need to find time and rip out the disruptor and see what I can get over this :) |
I found a bug in the @Override
public E waitPoll(final Queue<E> q) throws InterruptedException
{
E e = q.poll();
if (e != null)
{
return e;
}
t.set(Thread.currentThread());
while ((e = q.poll()) == null) {
LockSupport.park();
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException("Interrupted while waiting for the queue to become non-empty.");
}
}
t.lazySet(null);
return e;
} |
Yes, you are right! I will add that shortly. On Mon, Feb 23, 2015 at 7:26 PM ChristianWulf notifications@github.com
|
Second review in progress, will comment as I go...
|
Just a note on the MCParkTakeStrategy that condition.await generates garbage. I hit a case yesterday where replacing LinkedBQ with ArrayBQ was generating more garbage because of that... |
Semi relevant discussion on park/unpark HB rules:
|
Interesting! So there is potentially an issue on MCParkWaitStrat then. SPParkWaitStrat is good with the volatile write right? GG On Mon, Mar 16, 2015, 14:47 Nitsan Wakart notifications@github.com wrote:
|
I mean SCParkWaitStrat On Mon, Mar 16, 2015, 15:42 Georges Gomes georges.gomes@gmail.com wrote:
|
Hi Nitsan, I pushed your comments. Regarding the signaling: ##MCParkTakeStartegy @Override
public void signal()
{
ReentrantLock l = lock;
l.lock();
try
{
if (waiters>0)
{
cond.signal();
}
}
finally
{
l.unlock();
}
} ##SCParkTakeStrategy public volatile int storeFence = 0;
@Override
public void signal()
{
// Make sure the offer is visible before unpark
storeFence = 1; // store barrier
LockSupport.unpark(t.get()); // t.get() load barrier
} Let me know if you think something is not safe. Cheers |
According to http://shipilev.net/blog/2014/on-the-fence-with-dependencies/, it depends on the JVM implementation whether a write to or a read from a volatile variable inserts a StoreLoad barrier. Since you use both in SCParkTakeStrategy, this strategy should work correctly for all JVM implementations. |
Ok, I'm going to merge this fucker :-) |
merged! |
May the force be with you :) On Sat, Apr 18, 2015, 16:37 Nitsan Wakart notifications@github.com wrote:
|
Hi Nitsan,
This is not for merge - just to open the discussion about efficient BlockingQueue implementations.
The SpscArrayQueueBlocking here is hand-crafted by inheritance of SpscArrayQueue but ultimately I would prefer to have the BlockingQueue Implementations dynamically generated on demand by the QueueFactory.
Let me know your thoughts before I push it too far.
Cheers
Georges