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

Code generator atomic queues for linked queues and linked array queue variation #187

Merged
merged 18 commits into from
Aug 21, 2017

Conversation

kay
Copy link
Contributor

@kay kay commented Aug 14, 2017

As part of doing this I also implemented MessagePassingQueue for all variations. The reason for this was simply that I had to anyway (path of least resistance). However I can break that out into a separate PR if that makes more sense.

Queue classes now generated:

  • BaseLinkedAtomicQueue
  • BaseMpscLinkedAtomicArrayQueue
  • BaseSpscLinkedAtomicArrayQueue
  • MpscChunkedAtomicArrayQueue
  • MpscGrowableAtomicArrayQueue
  • MpscLinkedAtomicQueue
  • MpscUnboundedAtomicArrayQueue
  • SpscChunkedAtomicArrayQueue
  • SpscGrowableAtomicArrayQueue
  • SpscLinkedAtomicQueue
  • SpscUnboundedAtomicArrayQueue

kay added 17 commits August 14, 2017 17:06
BaseLinkedQueue: Moved the last level of padding into a dedicated class

BaseLinkedAtomicQueue: Added padding for the atomic field updaters
Construction of a new LinkedQueueNode or new LinkedQueueAtomicNode is done in the base class

Some parameter variable names homogenized
Exposed static methods like lvElement on AtomicReferenceArray to mimick UnsafeRefArrayAccess methods
I've replaced direct accesses to the buffer with the soElement/lvElement/etc
style method accessors like the unsafe variants. I've moved the direct field
accessors, e.g. soProducerIndex to the padded classes.
@@ -46,6 +47,8 @@
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;

public final class JavaParsingAtomicArrayQueueGenerator extends VoidVisitorAdapter<Void> {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changes to this class were largely refactoring to try to stay similar to the linked queue generator

@@ -157,11 +171,6 @@ public final boolean isEmpty() {
return lvConsumerNode() == lvProducerNode();
}

@Override
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved to end of class to help in diffing during dev

protected long producerIndex;

@Override
public final long lvProducerIndex()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These accessor methods were promoted to the parent padding class to be consistent with the array queue implementations

* This method assumes index is actually (index << 1) because lower bit is used for resize. This is
* compensated for by reducing the element shift. The computation is constant folded, so there's no cost.
*/
private static long modifiedCalcElementOffset(long index, long mask)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was moved into a util method, LinkedArrayQueueUtil.modifiedCalcElementOffset

@@ -407,51 +452,11 @@ private E newBufferPeek(E[] nextBuffer, long index)
private long newBufferAndOffset(E[] nextBuffer, long index)
{
consumerBuffer = nextBuffer;
consumerMask = (nextBuffer.length - 2) << 1;
consumerMask = (length(nextBuffer) - 2) << 1;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is defined in LinkedArrayQueueUtil.length as a sneaky way of simplying the generation process. i.e. for atomics all it has to do is import LinkedArrayAtomicQueueUtil.length and this will "just work"(tm)

return nextBuffer;
}

private long nextArrayOffset(E[] curr)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was moved into LinkedArrayQueueUtil

@@ -52,58 +49,48 @@ public SpscChunkedArrayQueue(int chunkSize, int capacity)
}

@Override
public int capacity()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved to the end of this class

if (casProducerIndex(pIndex, pIndex + 2)) {
break;
}
}
// INDEX visible before ELEMENT, consistent with consumer expectation
final int offset = modifiedCalcElementOffset(pIndex, mask);
buffer.lazySet(offset, e);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Swapped this to using the soElement method in the util classes for easy transformation by the generator

}

private void soProducerIndex(long v) {
producerIndex.set(v);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This seemed wrong.. the generator outputs lazySet. I think that's correct.

}

private void soConsumerIndex(long v) {
consumerIndex.set(v);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This seemed wrong.. the generator outputs lazySet. I think that's correct.

}

private void soProducerLimit(long v) {
producerLimit.set(v);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This seemed wrong.. the generator outputs lazySet. I think that's correct.

@@ -104,6 +180,40 @@ public BaseMpscLinkedAtomicArrayQueue(final int initialCapacity) {
}

@Override
public final int size() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This, and a few other methods in the class were moved around but unchanged to make it easier to diff unsafe vs atomics during dev

soElement(curr, nextArrayOffset(curr), next);
}

private void soElement(AtomicReferenceArray curr, int i, Object e) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved into util class

adjustLookAheadStep(chunkCapacity);
soProducerIndex(0L);// serves as a StoreStore barrier to support correct publication
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Atomics version had this but unsafe used a final field so this disappears

int chunkCapacity = Math.max(Pow2.roundToPowerOfTwo(chunkSize), 16);
long mask = chunkCapacity - 1;
AtomicReferenceArray<E> buffer = allocate(chunkCapacity + 1);
producerBuffer = buffer;
producerMask = mask;
consumerBuffer = buffer;
consumerMask = mask;
producerBufferLimit = mask - 1; // we know it's all empty to start with
soProducerIndex(0L);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

ATTENTION!: atomics had barrier this but unsafe version didn't. However this class nor it's parents write to a final field. This would have been an existing bug in the unsafe version, could you look this over and let me know your thoughts.

@kay kay force-pushed the homogenize_atomic_linked_queues branch from d6f62d4 to 15e1f6a Compare August 14, 2017 17:02
producerQueueLimit = maxQueueCapacity;
soProducerIndex(0L);// serves as a StoreStore barrier to support correct publication
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Think this is fine as maxQueueCapacity is final and this barrier will be inserted then

Copy link
Contributor

Choose a reason for hiding this comment

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

This volatile/ordered write at the end of construction as a substitute for final field safe publication guarantees does not work sadly (see Shipilev JMM thingy). I think this was a misguided effort in any case, queues should be safely published before used, which is generally the case anyhow.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're referring to http://altair.cs.oswego.edu/pipermail/concurrency-interest/2013-November/011951.html ? i.e. that technically by JMM wording ctors that contain volatile writes is not the same as a final write in terms of safe publication?

@nitsanw nitsanw merged commit 15e1f6a into JCTools:master Aug 21, 2017
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