Skip to content

Comments

Lock lhash stats.#4418

Closed
paulidale wants to merge 2 commits intoopenssl:masterfrom
paulidale:lhash
Closed

Lock lhash stats.#4418
paulidale wants to merge 2 commits intoopenssl:masterfrom
paulidale:lhash

Conversation

@paulidale
Copy link
Contributor

Remove atomic operations from the lhash statistics gathering, an inconsistent
output was more than possible.

Instead, use a lock. The critical sections are deliberately as small as
possible.

The rationale for the NULL checks is to allow a future change to where the
statistics are optional.

Remove atomic operations from the lhash statistics gathering, an inconsistent
output was more than possible.

Instead, use a lock.  The critical sections are deliberately as small as
possible.

The rationale for the NULL checks is to allow a future change to where the
statistics are optional.
@paulidale paulidale mentioned this pull request Sep 25, 2017
2 tasks
@paulidale
Copy link
Contributor Author

paulidale commented Sep 26, 2017

I'm not convinced that the second commit is worthwhile. It attempts to avoid having OPENSSL_LH_retrieve grab the write lock twice during its operation which should improve performance. However, the changes are a bit ugly.

I've no issue with ignoring the second and just including the first.

Copy link
Contributor

@richsalz richsalz left a comment

Choose a reason for hiding this comment

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

minor bit of cleverness to remove :)

Copy link
Contributor

Choose a reason for hiding this comment

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

unh maybe put those in a "if miss" statement?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

:(

An attempt to provide constant overhead.

I'll switch to a conditional statement instead.

@dot-asm
Copy link
Contributor

dot-asm commented Sep 26, 2017

One can wonder if this is adequate trade-off. I mean printing stats is essentially debugging thing, i.e. very few would actually do it, and question is why does caller have to be unconditionally punished with overhead. Well, it is punished even now, but it's presumably acceptable on contemporary systems, i.e. those with atomic_add. Switching to full-blown unconditional locking even on contemporary systems feels excessive. Yes, two mentions of "unconditional" means that best scenario would probably be if application had an option to not collect stats :-) One can also wonder if debugging aid has to be that accurate considering overhead costs. After all, OPENSSL_LH_stats_bio prints whole structure but only part of it is actually lock-protected. Which kind of asks for question how come just those? I mean consider OPENSSL_LH_insert. It calls getrn, which cares about atomicity of just some fields but not others or traversing list[!], while OPENSSL_LH_insert is not concerned at all about synchronization. What am I missing?

@dot-asm
Copy link
Contributor

dot-asm commented Sep 26, 2017

Atomic adds additions in lhash were added in #3525, but I don't see how it was relevant... At least other structure fields are much more sensitive. Given that context it looks like it's caller's responsibility to synchronize access to lhash structure... Or again, what am I missing?

@dot-asm
Copy link
Contributor

dot-asm commented Sep 26, 2017

what am I missing?

Ah! It does expect caller to synchronize, but retrieve requires read lock, which makes it inappropriate to update statistics...

[edit: well, not "retrieve requires read lock", but rather "read lock is sufficient and should be preferred for retrieve"]

@paulidale
Copy link
Contributor Author

Yeah, the atomic operations in some instances but not in others confused me until I realised that writes to the lhash weren't locked but reads were. The caller deals with writing, the library deals with reading. The statistical collection is using what C++ allows as mutable fields.

The way I've done this allows the lock to be NULL and then there is zero overhead. My ideal would be for a lhash to be created without statistics and for there to be a call OPENSSL_LH_stats_enable to enable the collection. Not creating it by default is an API change, so I didn't add the extra function to enable/disable the statistical collection.

As for cost, it is one lock vs potentially many atomic operations. It is impossible to tell which is more expensive abstractly.

@dot-asm
Copy link
Contributor

dot-asm commented Sep 26, 2017

retrieve requires read lock,

The whole purpose for requiring read lock is performance. Which will be totally defeated by write locking stats fields... For reference, a single data point, on my computer tight loop with increment of volatile variable is ~6 cycles per increment, atomic_add - ~20, lock-increment-unlock - ~110.

@dot-asm
Copy link
Contributor

dot-asm commented Sep 26, 2017

It does expect caller to synchronize,

But I can't see that it's mentioned in documentation... Am I missing something again?

@dot-asm
Copy link
Contributor

dot-asm commented Sep 26, 2017

The caller deals with writing, the library deals with reading.

I don't follow...

@paulidale
Copy link
Contributor Author

When accessing the table for write, there are a minimum of three atomic operations and possibly many more on collision. Read has minimum of four and possibly many more. The speed difference isn't looking great.

I meant that the caller had to ensure that there was only ever a single thread attempting to write to the lhash (and that nothing was trying to read it at this time). There is no such restriction on reading the lhash. You are correct, there isn't any documentation.

I also noticed that the POD for stack includes the internal functions as well as the type safe wrappers but the one for lhash only has the type safe wrappers declared.

…lock is

only ever grabbed once for any operation.
@paulidale
Copy link
Contributor Author

An alternative would be to document that the lhash isn't thread safe (which it isn't) and to remove the locks and the atomic operations entirely.

@paulidale
Copy link
Contributor Author

Atomic operations initially put in by #3525

@dot-asm
Copy link
Contributor

dot-asm commented Sep 27, 2017

An alternative would be to document that the lhash isn't thread safe (which it isn't) and to remove the locks and the atomic operations entirely.

I was about to suggest that too! Though I'd say that since it's caller's responsibility to synchronize access to the structure, it would be appropriate to state that if caller wants reliable stats, then it should use exclusive write locks even with retrieve operations. And if reliable stats are not required, then shared read lock with retrieve operation would do.

@dot-asm
Copy link
Contributor

dot-asm commented Sep 27, 2017

a single data point, on my computer tight loop with increment of volatile variable is ~6 cycles per increment, atomic_add - ~20, lock-increment-unlock - ~110.

One should recognize that tight loop is not necessarily representative case. In real life, in presence of other meaningful instructions, execution time will so to say "dissolve", or get "amortized" if you wish. However, one can be pretty sure that "amortization" would be minimal in last case. Or in other words lock-increment-unlock would still be times slower than any other option.

@paulidale
Copy link
Contributor Author

Should I still remove the locking in light of #4427 where they become optional and not used by default?
I'll update the documentation regardless.

@dot-asm
Copy link
Contributor

dot-asm commented Sep 27, 2017

Should I still remove the locking in light of #4427 where they become optional and not used by default?

That's what I for one would be in favour of. I.e. remove the locking and atomic increments [and put synchronization responsibility on caller], even for reliable stats. In this case #4427 is rendered inapplicable. There might be other opinions...

One can still wonder following question. Would it be appropriate to bring locking into this module? I mean instead of relying on caller to do the locking, one can do locking in stack subroutines. This is probably inappropriate to do in minor release, but it doesn't mean that we can't discuss future plans. Argument against doing this might be that common usage pattern is such that caller is more than likely locking own structure that refers stack, so that second lock would be excessive...

@paulidale
Copy link
Contributor Author

Adding locking isn't gong to be API breaking.

There are lock free extensible hash tables in the literature. If the common operations can be made to only use a single atomic compare-and-set sequence, one might be worthwhile.

@paulidale
Copy link
Contributor Author

This PR is unnecessary in light of #4429

@paulidale paulidale closed this Sep 28, 2017
@paulidale
Copy link
Contributor Author

I've taken the mutually agreeable path of not trying to lock in lhash.
The support wasn't complete, wasn't properly documented and didn't really achieve anything.

@paulidale paulidale deleted the lhash branch September 28, 2017 00:20
paulidale added a commit to paulidale/openssl that referenced this pull request Oct 4, 2017
indicate the level of locking required for various operations.

Remove the lock and atomics from the lhash code.  These we're not complete
or adequate.

Refer to openssl#4418 and openssl#4427 for details.
levitte pushed a commit that referenced this pull request Oct 8, 2017
indicate the level of locking required for various operations.

Remove the lock and atomics from the lhash code.  These we're not complete
or adequate.

Refer to #4418 and #4427 for details.

Reviewed-by: Rich Salz <[email protected]>
Reviewed-by: Ben Kaduk <[email protected]>
Reviewed-by: Andy Polyakov <[email protected]>
(Merged from #4429)
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