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

Rework initialization of constants & class variables #15333

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

Conversation

ysbaddaden
Copy link
Contributor

Follow up and closes #15216 by @BlobCodes. Also incorporates the LLVM optimization from master...BlobCodes:crystal:perf/crystal-once-v2.

Similar to the original PR, this changes the flag to have 3 states instead of 2. This led to change the signature of the __crystal_once[_init] functions that now take an Int8 (i8) instead of Bool (i1). In addition it drops the "once state" that isn't needed anymore.

This requires a new compiler build to benefit from the improvement. The legacy versions of the __crystal_once[_init] methods are still supported by both the stdlib and the compiler to keep both forward & backward compatibility (1.14- can build 1.15+ and 1.15+ can build 1.14-).

Unlike the original PR, this one doesn't use any atomics: the mutex already protects the flag, and the mutex itself is explicitly initialized by the main thread before any other thread is started (i.e. no parallelism issues).

A follow-up could leverage ReferenceStorage and .unsafe_construct to inline the Mutex instead of allocating in the GC heap. Along with #15330 then __crystal_once_init could become allocation free, which could prove useful for such a core/low level feature.

@oprypin
Copy link
Member

oprypin commented Jan 9, 2025

This should have a Co-authored-by: attribution somewhere in some commit

Co-authored-by: David Keller <[email protected]>

Based on the PR by @BlobCodes:
crystal-lang#15216

The performance improvement is two-fold:

1. the usage of a i8 instead of an i1 boolean to have 3 states instead
   of 2, which permits to quickly detect recursive calls without an
   array;
2. inline tricks to optimize the fast and slow paths.

Unlike the PR:

1. Doesn't use atomics: it already uses a mutex that guarantees acquire
   release memory ordering semantics, and __crystal_once_init is only
   ever called in the main thread before any other thread is started.
2. Removes the need for a state maintained by the compiler, yet keeps
   forward and backward compatibility (both signatures are supported).
Co-authored-by: David Keller <[email protected]>

@BlobCodes: I noticed that adding this code prevents LLVM from
re-running the once mechanism multiple times for the same variable.

Modified to avoid an undefined behavior when the assumption doesn't
hold that doubles as a safety net (print error + exit).
Co-authored-by: David Keller <[email protected]>

@BlobCodes: I think it would be better to print the bug message in
`Crystal.once` instead of `__crystal_once` to reduce complexity at the
callsite. The previous unreachable method can then be used in the
inlined `__crystal_once` so LLVM also knows it doesn't have to re-run
the method.

It's now even safe because `Crystal.once` would panic if it failed; it
should already be impossible, but let's err on the safe side.
@ysbaddaden ysbaddaden force-pushed the refactor/crystal-once branch from d1de2eb to ce6e9a2 Compare January 10, 2025 16:40
@ysbaddaden
Copy link
Contributor Author

Rebased to add the co-authored-by to the individual commits 👍

@oprypin
Copy link
Member

oprypin commented Jan 10, 2025

I think it's not registering in the interface. Maybe it has to be on the last line 🤔

@straight-shoota
Copy link
Member

FWIW we could also add the co-author attribution in the squash commit when we merge this PR (though we'd need to remember that, so it's best to already add it to the original commits 😅)

@Blacksmoke16
Copy link
Member

I'm pretty sure the easiest way would be to cherry pick their commits into a branch, then push up your own. If there are commits from more than 1 author on a branch GH should automatically add the co-authored-by to the squash commit message for all additional authors.

@ysbaddaden
Copy link
Contributor Author

ysbaddaden commented Jan 10, 2025

I added a commit to only enable the feature from crystal 1.16.0-dev. Let's merge after updating the version in the master branch.

EDIT: why is CI getting completely broken because I changed the version comparison from 1.15.0-dev to 1.16.0-dev?

@ysbaddaden
Copy link
Contributor Author

ysbaddaden commented Jan 10, 2025

Trying to work on #14905 I notice that this is still concurrency unsafe because we only enable Mutex for preview_mt. The example thus fails with "recursive initialization" which is wrong because they're concurrent accesses.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants