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

Introduce ASRecursiveUnfairLock and tests #858

Merged
merged 8 commits into from
Mar 28, 2018
Merged

Conversation

Adlai-Holler
Copy link
Member

  • Adds a new C lock struct that adds recursiveness to unfair_lock but isn't much slower.
  • Adds an experiment exp_unfair_lock that makes all ASDN::Mutex instances use either os_unfair_lock or ASRecursiveUnfairLock under the hood, if we're on iOS/tvOS >= 10.
  • The name Mutex isn't totally appropriate now but it's fine. If this works well we can move it.

Here's the perf on my 2016 MBP in release mode. Take synthetic tests with a grain of salt. "Uncontested" (less contested) = 2 threads, contested = 16 threads. See ASRecursiveUnfairLockTests.m.

Takeaways:

  • Close to plain unfair lock performance in uncontested case.
  • 25x faster in uncontested case, 50x faster in contested case.

Data:

testNoLockUncontested: average: 1ms, relative standard deviation: 15.788%
testPlainUnfairLockUncontested: average: 4ms, relative standard deviation: 26.805%
testRecursiveUnfairLockUncontested: average: 5ms, relative standard deviation: 10.843%
testRecursiveMutexUncontested: average: 129ms, relative standard deviation: 3.632%

testNoLockContested: average: 4ms, relative standard deviation: 7.002%
testPlainUnfairLockContested: average: 18ms, relative standard deviation: 3.598%
testRecursiveUnfairLockContested: average: 25ms, relative standard deviation: 12.423%
testRecursiveMutexContested: average: 1248ms, relative standard deviation: 3.859%

@TextureGroup TextureGroup deleted a comment Mar 27, 2018
@TextureGroup TextureGroup deleted a comment Mar 27, 2018
if (gMutex_unfair) {
// nop
} else {
AS_POSIX_ASSERT_NOERR(pthread_mutex_destroy (&_m));
Copy link
Member Author

Choose a reason for hiding this comment

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

I renamed this assert macro just because it was so dang long.

// non-copyable.
ReadWriteLockWriteLocker(const ReadWriteLockWriteLocker&) = delete;
ReadWriteLockWriteLocker &operator=(const ReadWriteLockWriteLocker&) = delete;
};
Copy link
Member Author

Choose a reason for hiding this comment

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

Removed unused C++ classes from this header.

@appleguy
Copy link
Member

This is a seminal contribution. Texture (and AsyncDisplayKit) developers have always dreamed of a faster locking primitive than the unnecessarily-fair pthread mutex, but it has felt just out of reach with so much else to do. Thank you @Adlai-Holler for jumping on this opportunity!

Some background reading for folks unfamiliar with os_unfair_lock:
https://blog.mozilla.org/nfroyd/2017/03/29/on-mutex-performance-part-1/ (discusses that pthread mutex happens to be fair on OS X / iOS platforms)
https://webkit.org/blog/6161/locking-in-webkit/ (the path that Safari / WebKit developers took to address this problem)
https://twitter.com/catfish_man/status/743252500728315905?lang=en (excitement about the new-in-iOS 10 API)
https://gist.github.com/steipete/36350a8a60693d440954b95ea6cbbafc (some hard-to-read, but more broad benchmarking comparisons with other iOS locking primitives)

Copy link
Member

@jparise jparise 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 to me. I don't see any issues or obvious optimization opportunities.

@@ -10,6 +10,8 @@
// http://www.apache.org/licenses/LICENSE-2.0
//

/// Note this has to be public because it's imported by public header ASThread.h =/
Copy link
Member

Choose a reason for hiding this comment

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

But only temporarily for the lifetime of the exp_unfair_lock experiment?

// Just a cache for pthread_self so that we never call it twice.
pthread_t s = NULL;

// Try to lock without blocking. If we fail, check what thread owns it.
Copy link
Member

Choose a reason for hiding this comment

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

grammar nit: s/what/which/

if (!recursive) {
AS_POSIX_ASSERT_NOERR(pthread_mutex_init (&_m, NULL));
} else {
// Fall back to recursive mutex.
Copy link
Member

Choose a reason for hiding this comment

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

Is the indentation here off one level?

@ghost
Copy link

ghost commented Mar 28, 2018

1 Warning
⚠️ This is a big PR, please consider splitting it up to ease code review.

Generated by 🚫 Danger

@Adlai-Holler
Copy link
Member Author

Thanks Jon for reviewing. Merging!

@Adlai-Holler Adlai-Holler merged commit 4bbbd72 into master Mar 28, 2018
@Adlai-Holler Adlai-Holler deleted the AHRecursiveUnfairLock branch March 28, 2018 18:29
@garrettmoon
Copy link
Member

garrettmoon commented Mar 30, 2018

We should be on the lookout for deadlocks due to priority inversion when this lands. Of course if all clients and Texture itself are built correctly, no priority inversion cases will come up 🤞. I'm definitely excited for the perf gains, reducing locking contention will be huge!

@Adlai-Holler
Copy link
Member Author

We good, unfair locks protect against priority inversion! All that they lack is a guarantee that you will get the lock first if you tried to lock first:

From https://opensource.apple.com/source/libplatform/libplatform-125/include/os/lock_private.h.auto.html

 * @abstract
 * Replacement for the deprecated OSSpinLock. Does not spin on contention but
 * waits in the kernel to be woken up by an unlock. The opaque lock value
 * contains thread ownership information that the system may use to attempt to
 * resolve priority inversions.

@garrettmoon
Copy link
Member

Amazing :)

bernieperez pushed a commit to AtomTickets/Texture that referenced this pull request Apr 25, 2018
* Introduce ASRecursiveUnfairLock and tests

* Document it and put underscores to scare people away

* Increment changelog

* Integrate it with ASDN::Mutex behind experiment

* Rename the experiment

* Love these license headers oh so much

* Move internal header because we have to

* Address Jon's feedback
bolsinga pushed a commit that referenced this pull request May 27, 2020
- Followup to #1742
- At Pinterest this shipped with D516974 in late 02/2020
- As discussed in #858 this is iOS10 or later, so the runtime `gMutex_unfair` check is still necessary for Texture.
rcancro pushed a commit to rcancro/Texture that referenced this pull request May 28, 2020
- Followup to TextureGroup#1742
- At Pinterest this shipped with D516974 in late 02/2020
- As discussed in TextureGroup#858 this is iOS10 or later, so the runtime `gMutex_unfair` check is still necessary for Texture.
piotrdebosz pushed a commit to getstoryteller/Texture that referenced this pull request Mar 1, 2021
- Followup to TextureGroup#1742
- At Pinterest this shipped with D516974 in late 02/2020
- As discussed in TextureGroup#858 this is iOS10 or later, so the runtime `gMutex_unfair` check is still necessary for Texture.
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.

4 participants