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

Add inline shared lock #785

Merged
merged 11 commits into from
Jul 28, 2021
Merged

Add inline shared lock #785

merged 11 commits into from
Jul 28, 2021

Conversation

senderista
Copy link
Contributor

This is being added so I can embed a shared-memory rwlock in the chunk header (to enforce mutual exclusion between compaction and GC tasks, while allowing concurrency between GC tasks). Note that no blocking facility is provided, because I don't need it for this application (and the implementation is fairly trivial without it).

Copy link
Contributor

@LaurentiuCristofor LaurentiuCristofor left a comment

Choose a reason for hiding this comment

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

Generally looks good, but I'd change this to an actual class, so instead of operating with atomic instances we'd just operate with instances of this class.
I've also left comments on making the logic clearer and simpler in some situations.

@senderista
Copy link
Contributor Author

I addressed most comments in the latest commit. I'll look into converting this namespace to a class and aligning the predicate checks with their descriptive comments tomorrow.

Copy link
Contributor

@simone-gaia simone-gaia left a comment

Choose a reason for hiding this comment

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

Not many comments on top of Laurentiu's extensive review. I agree with eh suggestion of using an OO approach.

production/inc/gaia_internal/common/inline_shared_lock.hpp Outdated Show resolved Hide resolved
@senderista
Copy link
Contributor Author

I think I've addressed all pending suggestions except refactoring into a class. I think I'll need to address that after some higher-priority work.

Copy link
Contributor

@LaurentiuCristofor LaurentiuCristofor left a comment

Choose a reason for hiding this comment

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

I resolved most comments. Main outstanding request is to change this into a class that operates on an atomic value.

@senderista
Copy link
Contributor Author

Changed this to a class. I don't think it's actually an improvement, but I need to get this merged...

{
namespace common
{
class inline_shared_lock
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the inline for performance purposes? If so it may not be relevant, https://stackoverflow.com/a/1759575/1214125.

Now I don;t know enough about these compiler things to detect whether it is worthwhile here or not. Keep in mind that by not having a cpp, you increase compilation time.

I think I already had this discussion in the past.

Copy link
Contributor Author

@senderista senderista Jul 25, 2021

Choose a reason for hiding this comment

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

No, the name of the class indicates that it's designed to be "inlined" into a shared-memory data structure like our chunk header. There's probably a better name to convey this notion though. (The name made more sense originally when the whole lock was just a single atomic<uint64_t>, not a class.)

Copy link
Contributor

Choose a reason for hiding this comment

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

I meant the inline methods within the class (I know that class names do not affect the compilation process)

Copy link
Contributor

Choose a reason for hiding this comment

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

I would only mark as inline and keep in the header the shorter helper methods like is_free. But the actual lock acquiring/releasing methods could be moved to a cpp file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't agree with that, since methods cannot be inlined if I move them into a .cpp file (except within that translation unit itself).

Copy link
Contributor

Choose a reason for hiding this comment

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

I thought that we discussed this before and there was a way for compilers to do this.

See more discussion and examples of compilers that already do such things here, for example:
https://stackoverflow.com/questions/8450030/can-modern-c-compilers-inline-functions-that-are-defined-in-a-cpp-file

I think you're overthinking this anyway. On one hand you've argued that we shouldn't optimize this code because it's not used in a critical path (yet). OTOH you're now concerned about the questionable loss of performance optimization due to the compiler not being able to inline this code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using LTO can massively slow compile times, so it would have to be a carefully considered decision (clang thinLTO is supposed to be faster but I haven't tried it). By keeping potentially hot paths in inline functions, I'm giving the compiler maximum flexibility, without assuming that LTO is enabled.

{
namespace common
{
class inline_shared_lock
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add a line between the namespace declarations and what is defined inside them (both at beginning and end)?

I would also move the comment just before the class declaration, because in the future, this file may get some additional supporting declarations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


// NB: This cannot be used safely in program logic without synchronization,
// and is intended only for logging/debugging.
size_t get_reader_count();
Copy link
Contributor

Choose a reason for hiding this comment

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

If this is for debugging, it should definitely be private.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, it's not for use inside the class.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why don't you actually make this safe then? You only need to read the value from the atomic once and then do all the checks on that value, so the answer is consistent. You already have the helpers required for this approach.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think it can really be used safely in a lock-free context (even double-checking after use would require safe retry and could entail an ABA hazard). But you're right that I could at least make it atomic, which I've done in the pending commit.

Copy link
Contributor

Choose a reason for hiding this comment

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

Last implementation looks good to me. I'd remove this comment because the same thing could be said about is_free and any other check whose result may already be obsolete by the time it is processed.


std::atomic<uint64_t> m_lock_word{c_free_lock};

inline bool is_valid();
Copy link
Contributor

Choose a reason for hiding this comment

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

I would add another private label before this section of methods and before the field declaration. I like to separate the fields from the constants/methods so one can quickly figure out what a class instance contains as data.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

ASSERT_INVARIANT(is_valid(), "Invalid state for lock word!");
}

size_t inline_shared_lock::get_reader_count()
Copy link
Contributor

Choose a reason for hiding this comment

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

All the functions below could go into a cpp file. We could also agree to not bother with inline methods at all and move everything into a cpp file and let the compiler figure things out.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The compiler cannot "figure things out" if you move the methods into a .cpp file. It can't inline anything outside the current translation unit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

BTW, I will move the inline keyword to the definitions rather than the declarations, based on the arguments here: https://isocpp.org/wiki/faq/inline-functions#where-to-put-inline-keyword.

Copy link
Contributor

Choose a reason for hiding this comment

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

It seems the linker can inline functions for which definition is not available. Didn't find a lot about this so it's probably safe to keep inline for now.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that can only happen with LTO, which I don't think we currently use (maybe we should!).

Copy link
Contributor

Choose a reason for hiding this comment

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

Copying an answer to a different comment that is relevant to this one as well:

I thought that we discussed this before and there was a way for compilers to do this.

See more discussion and examples of compilers that already do such things here, for example:
https://stackoverflow.com/questions/8450030/can-modern-c-compilers-inline-functions-that-are-defined-in-a-cpp-file

I think you're overthinking this anyway. On one hand you've argued that we shouldn't optimize this code because it's not used in a critical path (yet). OTOH you're now concerned about the questionable loss of performance optimization due to the compiler not being able to inline this code.

Copy link
Contributor

@LaurentiuCristofor LaurentiuCristofor left a comment

Choose a reason for hiding this comment

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

Looks good. Just left a few comments on some minor issues.

Copy link
Contributor

@simone-gaia simone-gaia left a comment

Choose a reason for hiding this comment

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

LGTM

@senderista senderista merged commit b4ce7ee into master Jul 28, 2021
@senderista senderista deleted the inline_shared_lock branch July 28, 2021 00:24
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.

3 participants