Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Remove synchronization of JyAttribute.getAttr to improve performance in multithreaded scenarios. #361

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

Stewori
Copy link
Contributor

@Stewori Stewori commented Oct 18, 2024

Fixes #360

Copy link
Member

@jeff5 jeff5 left a comment

Choose a reason for hiding this comment

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

It all hangs on the correctness of the argument. I find these things hard to convince myself of from just the theory.

An example of a test that arranges intense thread competition is this one I wrote to hammer the (prospective) type system. There was a risk, if I hadn't got it right, of deadlock or publication of half-finished objects.

The time measurements are made to prove the order of events rather than to measure performance. Synchronisation is explicit, so I'm not too worried about code re-ordering in my case.

The claim that the reader sees a coherent state as last written would be easier to accept if setAttr were to make a new list, in which everything could be constructed final, and only the head pointer would need to be volatile (and the nonBuiltin* counters). Assuming a setAttr is much less common than a getAttr, and lists are mostly short, the copy would not be expensive. You are best placed to say whether that's true.

which confirms the anticipated behavior of volatile fields for Java 5 and onwards.
Write accesses in the linked attribute list are designed to update the list tail
such that a concurrent read access would iterate either through the old state or
the new state. No invalid intermediate state is exposed to the read access.
Copy link
Member

Choose a reason for hiding this comment

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

It will be important that the list node is fully initialised and stable before it can be referenced by the unsynchronised reader in code like this:

JyAttribute newAtt = attr_type < 0 ?
new AttributeLink(attr_type, value) :
new TransientAttributeLink(attr_type, value);
newAtt.setNext(att);
ob.attributes = newAtt;

I see that the lines of code here are sequenced so that the publication of the reference to the new node comes last. Now we have to be sure the compiler won't rearrange assignment before construction. ISTR either the JLS or Pugh explicitly mentions this possibility. We have guarantees about writes to an individual variable, but what about actions nearby in the programme order of the thread? I think the generous application of the volatile keyword may have achieved that, but as I've said, I find this stuff difficult to reason about securely.

Copy link
Contributor Author

@Stewori Stewori Oct 19, 2024

Choose a reason for hiding this comment

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

As I understand the volatile semantics, having ob.attributes and (Transient)AttributeLink.next volatile should indeed prevent reordering of newAtt.setNext(att); and ob.attributes = newAtt;. Do you have an idea how we can confirm that? Perhaps by disassembling the bytecode of JyAttribute.class. That would be tedious and I suspect the JIT may apply subsequent optimization, including reordering where permitted. Perhaps an isolated test of JyAttribute concurrent access? Like 100 Threads read and write attributes permanently for 5 minutes for a dummy PyObject. This really stresses the design and the test would observe the results to confirm the anticipated behavior. Still tedious to implement. Read access would have to confirm that concurrent write access would never make an unrelated attribute (with higher index than the written one) temporarily invisible. Provoking an exception of any kind would of course be a fast-fail for the whole test.

I'm skeptical about changing the implementation more broadly (to create an entirely new list every time). If the volatile semantics does not work as expected and promised, that approach would be similarly broken. Proper initialization of the whole new list might be reordered with assignment to ob.attribute. A read access in the wrong moment could pick the uninitialized thing. So either it works as specified and it would be fine in any case, or it would be broken in any case. The new-list-every-time-approach would btw also require next to be volatile to prevent reordering of list initialization. Then, if we would make a new thing every time we could also make it an array. But also then, the array would have to be volatile to ensure proper initialization. However I'm not even aware what volatile would theoretically mean for an array (every item be like a volatile field I suppose).

All in all, the question of trust in the specs applies to each approach and we would likely need a proper stress test in any case. So I would go with the already implemented solution, wich is of all alternatives the one with the fewest changes to the status quo. Also, I do not see enough benefit to justify rewriting it yet again.

Copy link
Member

Choose a reason for hiding this comment

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

Good point about the array. And given that initialisation might skip round the write to ob.attributes I'm not convinced it helps. The idea was to reduce the number of volatile fields so we had only one to reason about instead of 3n+1 things.

That's the sort of test I meant, but it might not have to run so long if you could guarantee the threads compete. Inspecting the output of a particular compiler is not good enough. Our correctness has to be based on good theory and a reasonably harsh practical exam.

@Stewori
Copy link
Contributor Author

Stewori commented Oct 19, 2024

Before we think about writing a stress test I suggest to await response of @newprogrammer101 . If the PR does not solve the issue or blows something up, we would need to rethink the solution anyway.

Copy link

@fayazkhan121 fayazkhan121 left a comment

Choose a reason for hiding this comment

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

if (nonBuiltinAttrTypeOffset == 0) { throw new IllegalStateException("No more attr types available."); }
Magic numbers are being used in conditional checks.
Recommendation: Define these values as constants to improve code readability and maintainability

public static byte reserveCustomAttrType() {
public static synchronized byte reserveCustomAttrType() {
The method reserveCustomAttrType has been changed to a synchronised method. This is a good practice for thread safety, but it introduces a potential bottleneck under heavy load.
Recommendation: Assess if finer-grained synchronisation (e.g., using atomic variables) could replace the synchronisation here to improve concurrency without sacrificing thread safety.


/**
* Reserves and returns a new non-transient attr type for custom use.
*
* @return a non-transient attr type for custom use
*/
public static byte reserveCustomAttrType() {

Choose a reason for hiding this comment

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

Observation: The method reserveCustomAttrType has been changed to a synchronised method. This is a good practice for thread safety, but it introduces a potential bottleneck under heavy load.

Recommendation: Assess if finer-grained synchronisation (e.g., using atomic variables) could replace the synchronisation here to improve concurrency without sacrificing thread safety.

Copy link
Member

Choose a reason for hiding this comment

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

AI? Infrequently called, and relieves bottlenecks elsewhere. (Point of PR.)


/**
* Reserves and returns a new non-transient attr type for custom use.
*
* @return a non-transient attr type for custom use
*/
public static byte reserveCustomAttrType() {
public static synchronized byte reserveCustomAttrType() {
if (nonBuiltinAttrTypeOffset == 0) {

Choose a reason for hiding this comment

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

Magic numbers are being used in conditional checks.
Recommended : Define these values as constants to improve code readability and maintainability:

Copy link
Member

Choose a reason for hiding this comment

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

May be valid, but not in scope of change.

@newprogrammer101
Copy link

newprogrammer101 commented Jan 24, 2025

Before we think about writing a stress test I suggest to await response of @newprogrammer101 . If the PR does not solve the issue or blows something up, we would need to rethink the solution anyway.

Thanks @Stewori for the quick change in this CR. This commit is to resolve concurrency issue on Jython package. But currently when I created multi JythonExecutor instance for testing purpose, I found CPU was at BLOCK state on PrintStream.flush as mentioned in #360 (comment). I would suggest re-evaluate not only synchronize key word on Jython package, but also on its dependencies. @jeff5

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.

JyAttribute.getAttr significantly slow down during multi-threading
4 participants