|
|
Subscribe / Log in / New account

Merging lock elision into glibc

By Nathan Willis
July 3, 2013

Glibc, the GNU C library, is a foundational free software project, and one that has served its important role for well over two decades. Recently, though, it has undergone a shift in its development model, with an increase in contributions from volunteers outside of the core team of committers. That sort of change can bring about strife, but it can also inspire important new features. The latter is certainly true—and the former might also be true—with the case of lock elision, which is set to be included for the first time in the just-frozen 2.18 branch. 2.18 will enable developers to test the glibc implementation of lock elision for some lock types but not others—primarily since there is not full consensus within the project on how to enable such experimental new features.

Lock elision, in general, is a technique for speeding up multi-threaded programs that may contend for the same lock. As more and more cores become available, synchronization between threads becomes increasingly expensive. One way to avoid the synchronization overhead is to use transactional memory instead. A memory transaction buffers all of the possible results of an operation; in the common case where nothing interrupts the operation, the transaction is committed, but if something does interfere, the transaction can be rolled back atomically.

For locks, this means that multiple threads can acquire locks on the same object, and if they are modifying different parts of the object, both transactions ought to succeed. For example, one thread can acquire a lock on an object (say, tablefoo), update the row it is interested in (tablefoo(N)), then release the lock. Meanwhile, a different thread can also acquire a lock on tablefoo for the purpose of updating tablefoo(M). Using memory transactions, both threads can afford to be conservative about locking the whole object, but cavalier about updating the part of the object they need—most of the time, both transactions will go through; only when both threads try to update tablefoo(N) is there a collision, at which point one transaction must be rolled back.

But the real trick is that, in this common case where the two threads do not collide, the locks themselves are unnecessary. If the program is smart enough to recognize this, acquiring and releasing the locks can simply be skipped. This is lock elision. Unfortunately, up until recently, real-world implementations of transactional memory (almost always in software) have been too slow to offer a real advantage over manipulating the locks in the traditional manner. That changed, however, with the debut of Intel's Transactional Synchronization Extension (TSX), a hardware implementation of transactional memory for the Haswell generation of CPUs. Consequently, building TSX support into the lock implementations of common libraries would allow existing programs to take advantage of lock elision speed-ups without even recompiling.

Intel's Andi Kleen has been working on a TSX-based lock elision implementation for glibc, which he wrote about back in January 2013. The 14-part patch set has been through many iterations, but in late June the deadline was fast approaching for the glibc 2.18 freeze, and the status of the patches was still a matter of debate.

The patch set adds elision capabilities to both POSIX thread (pthread) mutexes and read/write locks (rwlocks), and uses an adaptive algorithm to decide when to elide locks in a given code path. Essentially, the algorithm keeps track of whether each mutex succeeds at eliding a lock, and if it fails, elision is suspended for a period of time. Not all lock variants are supported; in particular, locks are never elided for recursive mutexes. Elision is automatically attempted when the CPU supports transactional memory, but developers can also explicitly enable or disable it in their code. Kleen's patch set also offers two environment variables, GLIBC_PTHREAD_MUTEX and GLIBC_PTHREAD_RWLOCK, which users can use to explicitly enable or disable each flavor of elision when starting a program.

Lock down?

The general consensus on the libc-alpha mailing list was that the mutex patches (patches 1 through 5, plus 7) were ready for inclusion. However, there was less agreement on the other patches, for three reasons. First, the glibc team was wary of the rwlock patches due to a disagreement over how to interpret the POSIX standard. According to the standard, a "normal" (i.e., non-recursive, non-errorcheck, non-timed) mutex is required to deadlock if the owner of the lock attempts to re-lock it while already holding the lock. However, if the lock in question is elided, this required deadlock does not occur. It is certainly debatable whether or not avoiding a deadlock is really a bad thing (after all, deadlocks are bugs), but the glibc project decided to follow the standard to the letter, and elide only non-"normal" mutexes.

But what is not clear from the specification is whether or not the same behavior is required for rwlocks. Carlos O'Donell has contacted the Austin Group to ask for clarification; if the official answer is that rwlocks are required to deadlock on re-locks, then the rwlock patches for glibc will not be merged as-is.

Second, the definition of "normal" for mutexes is not a simple affair. The POSIX standard in fact requires specific behavior of "normal" mutexes (such as the deadlock-on-re-lock behavior mentioned above). But the standard also allows for a different type of mutex termed "default" mutexes, in which the implementation is allowed more freedom of behavior. In previous versions of glibc, PTHREAD_MUTEX_NORMAL and PTHREAD_MUTEX_DEFAULT were defined to be identical. Kleen's patch set splits them, in order to allow "default" mutexes to be elided. Technically, not deadlocking on re-lock would violate the standard, even though not deadlocking because the lock had been elided would often be seen as a preferable outcome. But splitting PTHREAD_MUTEX_NORMAL and PTHREAD_MUTEX_DEFAULT could be seen as an ABI change, and, even though it would not affect old binaries, several in the project (such as Roland McGrath) felt that more consideration is needed before making the split, since it would be difficult to reverse after the fact.

Finally, there was also a lack of consensus about whether or not environment variables are ultimately the most appropriate mechanism with which to tune optional runtime features like lock elision. In addition to the coarse-grained enable-or-disable-elision functionality of the new environment variables, Kleen's patch set also adds several parameters that can be used to tune the behavior of the adaptive algorithm. Some would prefer adding a "tunables" API, while others see no problem with adding new environment variables under a well-known namespace (namely, GLIBC_) as long as there is sufficient discussion. Plus, since the elision algorithm is brand-spanking-new, with little or no testing outside of the confines of the glibc project, it is still possible that the algorithm itself could undergo a major overhaul before it is ready for real-world use. Offering tunable parameters is one way for real-world tests to help refine the algorithm, but if users become dependent on the specifics exposed, swapping in a different algorithm later becomes trickier.

Testing is a related matter, albeit one not currently holding up inclusion of the lock elision patch set. IBM's Dominik Vogt has been testing the patch set on the company's System z platform, which is the only other widely available processor architecture to offer its own implementation of hardware transactional memory. So far, his tests have produced as many questions as answers (in part because he is still working his way through the internal processes required to publicly release the test suite as free software). But in the long term, providing a test suite is a vital step for the project—doubtless in coming years there will be more processor architectures to add their own implementations of transactional memory.

Friends of the library

O'Donell declared glibc frozen for 2.18 on July 2, after Kleen checked in the approved mutex patches. As of press time, the project was still waiting to hear back from the Austin Group for clarification on the rwlock deadlock-on-re-lock question, and it appears that decisions on the normal/default split, tunables, and other patches may get deferred until later. O'Donell has assembled the contributor input on the tunables question in a page on the project's wiki. That discussion is expected to take longer than anyone wishes to keep glibc 2.18 in freeze.

Apart from the technical details of adding lock elision, Kleen's work to add the feature to glibc can be seen as a case study of the project's new, consensus-driven development style. For many years, development was overseen by Ulrich Drepper, who earned a reputation as a prickly gatekeeper past whom few outside contributions ever made it to land in the codebase. Other longtime project members (including McGrath) formed a "steering committee" to try and work around the practical problems that arose from Drepper's management style. But Drepper left the project in 2010, and in March 2012, McGrath announced that the steering committee had voluntarily dissolved, to make way for a more open, community-driven model.

Kleen's lock elision patch set, coming as it does largely from outside, is proof that the hard-to-persuade gatekeeper model is indeed gone. In practice, the glibc community arrived at rough consensus on the patch set in a series of epic-length list threads, and it could certainly be argued that the eventual consensus was incomplete. In fact, O'Donell was even a bit apologetic toward Kleen about how difficult the process had been, encouraging him to stick around and continue to contribute. Or, as he put it in a separate message, "We haven't merged something of this complexity in a long time. Please bear with us as we get better with the process."

But, ultimately, at least part of the mutex lock elision implementation has been merged, which might not have happened at all if one project manager were to have made the call in isolation. The consensus model still defers greatly to the experienced contributors (like McGrath), but that is certainly appropriate. In the end, patches were merged, with more or less full agreement, and the remaining issues are largely debates about the long term impact of the implementation—not vehement opposition.


to post comments

Merging lock elision into glibc

Posted Jul 4, 2013 3:22 UTC (Thu) by siddhesh (guest, #64914) [Link] (2 responses)

I don't know if the Ulrich bashing was necessary to try and prove that the current consensus model is working well; the increase in contributions should be sufficient evidence. Very good summary of the lock elision patches otherwise.

Merging lock elision into glibc

Posted Jul 4, 2013 4:37 UTC (Thu) by JoeBuck (subscriber, #2330) [Link] (1 responses)

The "Ulrich bashing" was quite mild, I think, and it would have been inappropriate to omit it as it is completely relevant. It simply wouldn't have been possible for an outside contributor to get a feature of this complexity merged (even partially) back when Drepper was running the show. That doesn't mean that he isn't a brilliant programmer and capable maintainer (he is both), but he saw glibc as his personal cathedral.

Ulrich bashing

Posted Jul 5, 2013 18:46 UTC (Fri) by giraffedata (guest, #1954) [Link]

I actually sensed a deliberate holding back of criticism of the Drepper dynasty. What remains is the minimum necessary to complete one of the topics of the article (2nd sentence: "... [glibc] has undergone a shift in its development model, ...").

Also, the article doesn't actually render judgement on Ulrich; it just describes how things were; any negative connotations would have to come from the reader.

Merging lock elision into glibc

Posted Jul 4, 2013 22:44 UTC (Thu) by dirtyepic (guest, #30178) [Link] (25 responses)

> According to the standard, a "normal" ... mutex is required to deadlock
> if the owner of the lock attempts to re-lock it while already holding the
> lock. However, if the lock in question is elided, this required deadlock
> does not occur. It is certainly debatable whether or not avoiding a
> deadlock is really a bad thing (after all, deadlocks are bugs), but the
> glibc project decided to follow the standard to the letter, and elide
> only non-"normal" mutexes.

I don't follow this at all. If the lock is elided then it was never held in the first place, so why would not deadlocking violate POSIX?

Merging lock elision into glibc

Posted Jul 4, 2013 23:07 UTC (Thu) by jake (editor, #205) [Link] (21 responses)

> If the lock is elided then it was never held in the first place, so
> why would not deadlocking violate POSIX?

I think the basic idea is that a broken program (one that deadlocks) would start working (because it doesn't deadlock) if the lock was elided. So the same source might work if lock elision is active, while it would fail without lock elision. POSIX evidently says it should fail (deadlock) in all cases.

jake

Merging lock elision into glibc

Posted Jul 5, 2013 0:47 UTC (Fri) by mjw (subscriber, #16740) [Link] (1 responses)

The specific wording they are concerned about is explained in this Austin Group Defect Tracker item: http://austingroupbugs.net/view.php?id=720

Merging lock elision into glibc

Posted Jul 5, 2013 14:27 UTC (Fri) by tvld (guest, #59052) [Link]

That's supposedly inconsistent wording for rwlocks. For plain mutexes, the POSIX specs are clear in that deadlock-on-relock is required for PTHREAD_MUTEX_NORMAL mutexes and similar kinds, but not for PTHREAD_MUTEX_DEFAULT.

Merging lock elision into glibc

Posted Jul 5, 2013 7:46 UTC (Fri) by micka (subscriber, #38720) [Link] (15 responses)

Is there a reason to be bug for bug compatible with POSIX ?

Shouldn't the objective be "let's just be better than POSIX" even if it means not being compatible ?

After all, I don't think any Linux distribution was ever POSIX certified. Or if one has been, it's marginal.

is deadlocking really worst?

Posted Jul 5, 2013 8:19 UTC (Fri) by tialaramex (subscriber, #21167) [Link] (4 responses)

I'm confused as to why people are sure that /not deadlocking/ is better. Maybe somebody can walk me through the rationale.

A program which deadlocks has a serious bug in it. The argument seems to be that not deadlocking would make this program better, but, since it has some bug that normally leads to deadlock it's presumably now going to do something entirely unpredictable, and there is no reason at all why we should believe that's better, indeed from a debugging POV it's worst because it'll be harder to understand what the root cause is.

Tools that detect code which seemingly _could_ deadlock (but perhaps rarely does in practice due to a hard-to-win race condition) have been seen as really useful. If deadlocking is so bad, why are we so happy to have those tools which detect the same situation a little earlier?

is deadlocking really worst?

Posted Jul 5, 2013 11:05 UTC (Fri) by iq-0 (subscriber, #36655) [Link]

The problem is not in that "not deadlocking" is better/worse, but that you *must* deadlock in certain recursive locking scenario's.

In this case there is a technical problem in (consequently) detecting specific situations and it raises the question whether the current specification is really what is wanted or just how it turned out to be (request for clarification).

If it is determined that it must deadlock (or at least not continue as if there is no problemn) than the current proposed change simply can't be used without breaking compatibility with the standard. Not good or bad perse, just a simple matter of fact.

is deadlocking really worst?

Posted Jul 7, 2013 6:58 UTC (Sun) by dlang (guest, #313) [Link] (2 responses)

as I read it, the issue is that if the requirement is that you deadlock if the application tries to take the same lock twice, then you can't use lock elision, you must actually take the lock so that you can detect if you try to take it twice.

With lock elision, you don't actually take the lock, instead you assume that the lock won't matter, but set things up so that you will detect if two threads try to modify the same thing.

but doing this won't detect the deadlock situation.

so if you insist that the program MUST deadlock, then you can't get the speed advantage of lock elision, even on properly written programs that will never deadlock.

Personally, I think it should be up to the sysadmin (based on which version of the library they install on their system) to decide if they want the performance boost, or the detection of buggy programs by sometimes detecting bad locking patterns if they exist.

If you can't tell, I think it is perfectly reasonable to not detect and deadlock the application in cases like this. Yes, it will fail to show the bug for some developers, but I believe that most of the time, this sort of bug is going to be invisible to them anyway due to local race conditions, so it doesn't actually help getting fewer bugs out to users.

is deadlocking really worst?

Posted Jul 7, 2013 16:11 UTC (Sun) by jimparis (guest, #38647) [Link] (1 responses)

> so if you insist that the program MUST deadlock, then you can't get the speed advantage of lock elision, even on properly written programs that will never deadlock.

I'm confused, why can't you just set things up so that it still deadlocks anyway? The speed advantage of lock elision is to avoid costly synchronization primitives between threads. This same-thread-deadlocking situation has nothing to do with synchronization. You could still set a thread-local flag when you get to the point where you would have normally locked, and clear it at the unlock. If it's already set, trigger a deadlock using whatever means you'd like.

I'm guessing that doesn't work for some reason, maybe related to the transaction?

is deadlocking really worst?

Posted Jul 7, 2013 20:53 UTC (Sun) by dlang (guest, #313) [Link]

I don't know the details, but it seems that it optimizes away the lock, and so doesn't detect that it's being taken twice or something like that

Merging lock elision into glibc

Posted Jul 5, 2013 14:35 UTC (Fri) by tvld (guest, #59052) [Link]

> Is there a reason to be bug for bug compatible with POSIX ?

That assumes that the PTHREAD_MUTEX_NORMAL semantics, as specified by POSIX, is a bug. There's no proof or indication for that. Just because it's not your preferred semantics doesn't mean that it's a buggy semantics.

Note that you'll get PTHREAD_MUTEX_DEFAULT mutexes whenever you use a static initializer for the mutex, or just don't set the mutex type explicitly. We can use lock elision for DEFAULT mutexes, and assuming that it's rather rare to set a specific mutex type, most mutexes will be able to use elision. The problem with the earlier versions of Andi's patches was that they didn't pay attention to the PTHREAD_MUTEX_NORMAL requirements. The glibc community found a solution for how to split out NORMAL from DEFAULT (they shared the same enum constant) without having to change the API or ABI. This solution was then also part of what ended up being committed.

Merging lock elision into glibc

Posted Jul 7, 2013 12:19 UTC (Sun) by Lennie (subscriber, #49641) [Link] (8 responses)

I think the default behaviour should be what it was.

This prevents breaking existing programs.

Something as central as glibc should probably do that.

A Linux specific API, extension or environment variable which changes that default behaviour to speed up certain programs that have been known to work/tested should be fine.

When certain distributions have enabled it by default for a few years and nothing breaks or programs have adopted then maybe it is possible to change the default.

But keeping the safe default indefinitely is also not needed, like with atime and Linux filesystems behaviour.

Merging lock elision into glibc

Posted Jul 7, 2013 20:55 UTC (Sun) by dlang (guest, #313) [Link] (7 responses)

> This prevents breaking existing programs.

I think it's important to keep in mind that "breaking existing programs" consists of taking a program that is buggy enough that it would deadlock under the old version and making it sometimes keep running instead of deadlocking.

No program that ran before would stop running

Merging lock elision into glibc

Posted Jul 7, 2013 21:02 UTC (Sun) by Lennie (subscriber, #49641) [Link] (3 responses)

Not deadlocking could corrupt data, right ?

So I thought it still applied.

Merging lock elision into glibc

Posted Jul 7, 2013 21:11 UTC (Sun) by dlang (guest, #313) [Link] (2 responses)

if a program is buggy enough, it can corrupt data any time it's run, with or without deadlocks.

The idea of software deadlocking (at which point it can do nothing to protect it's data or to write out data it has in memory) being a 'good thing' for data safety seems to be grasping at straws to justify a policy.

the original POSIX policy that says that one thread trying to take a mutex twice must deadlock seems to me more a case of formalizing the say things currently worked because nobody at the time imagined that there would be a reasonable way to _not_ have it deadlock than a decision that any version that didn't deadlock would be bad.

Merging lock elision into glibc

Posted Jul 8, 2013 9:05 UTC (Mon) by ibukanov (subscriber, #3942) [Link] (1 responses)

> if a program is buggy enough, it can corrupt data any time it's run, with or without deadlocks.

I once wrote a code that lead to a double free and data corruption when later the data is written to the disk. Fortunately a test running with MALLOC_CHECK_ enabled caught that.

Similarly with POSIX deadlock behavior a bug in a program could be discovered faster.

So this is a classic debate about enabling assert in a production code. It makes thinks slower but discovers bugs faster.

Merging lock elision into glibc

Posted Jul 8, 2013 22:38 UTC (Mon) by wahern (subscriber, #37304) [Link]

"If a program is buggy enough" really begs the question. We know that almost all programs have bugs, but you can't know a priori if it has "enough" bugs to make runtime assertions worthwhile in any particular case.

As an OpenBSD developer pointed out (see my quotation below), glib had locking bugs caught by their assertions; bugs which went unnoticed on Linux. Now, if you ask me, anybody using glib for long-lived server processes, or anything handling potentially malicious data, is already asking for trouble, but it's common enough (even recommended!) that it would be nice if bugs in these things were caught sooner rather than later.

Merging lock elision into glibc

Posted Jul 7, 2013 23:41 UTC (Sun) by tvld (guest, #59052) [Link] (2 responses)

Please look at http://sourceware.org/glibc/wiki/LockElisionGuide, which has examples of correct programs that may rely on the deadlock-on-relock requirement.

Merging lock elision into glibc

Posted Jul 8, 2013 4:04 UTC (Mon) by Cyberax (✭ supporter ✭, #52523) [Link] (1 responses)

These are kinda borderline. Are there any real programs out there depending on it?

IMO, it would be nice to have this behavior available depending on environment variable. It should default to "off" for a couple of glibc releases then it should be changed to "on". After 5-10 years it can be removed altogether.

Merging lock elision into glibc

Posted Jul 8, 2013 13:05 UTC (Mon) by tvld (guest, #59052) [Link]

> Are there any real programs out there depending on it?

The right question to ask is if there *cannot* be any real programs out there that depend on it (now or in the future) -- which we can't know, so we have to conservatively assume that such programs exist.

Merging lock elision into glibc

Posted Jul 5, 2013 14:25 UTC (Fri) by tvld (guest, #59052) [Link]

This is not just about broken programs. While it is true that deadlock-on-relock could give you some kind of fail-stop behavior in a program that incorrectly tries to acquire a non-recursive mutex twice, not all programs that deadlock-on-relock need to be necessarily broken.

In particular, they can still handle signals, and a deadlock-on-relock situation will not prevent a program from shutting down. For example, deadlock-on-relock might be a valid way to suspend a thread until program termination. Thus, this kind of deadlock is not necessarily an obstacle to forward progress, so you can't for sure say it's a bug. glibc's lock elision implementation guidelines (http://sourceware.org/glibc/wiki/LockElisionGuide) have further examples, as well as a more detailed discussion of POSIX semantics vs. purely HTM-based lock elision implementations.

Merging lock elision into glibc

Posted Jul 6, 2013 5:46 UTC (Sat) by andikleen (guest, #39006) [Link] (1 responses)

Lock elision doesn't make deadlocks go away, they just won't happen every time, but only when the lock aborts. Typically the first call will abort, and some random number of later calls.

To my knowledge POSIX doesn't define what a deadlock actually is, so you can't even say it's required to fail.

FWIW I think this is all completely irrelevant to any practical software engineering.

Merging lock elision into glibc

Posted Jul 6, 2013 9:39 UTC (Sat) by andresfreund (subscriber, #69562) [Link]

> FWIW I think this is all completely irrelevant to any practical software engineering.

Why should making already hard to reproduce bugs even harder to reproduce have no practical implications?

Merging lock elision into glibc

Posted Jul 5, 2013 14:15 UTC (Fri) by tvld (guest, #59052) [Link] (1 responses)

Lock elision is an *implementation-level mechanism*; from the perspective of the program, lock elision ensures executions that behave as if the lock was held. If we want to enable lock elision transparently (i.e., for the mutex types currently defined by POSIX), then we must ensure that a correct program that operations within what's defined by POSIX cannot observe a difference in behavior when lock elision is used vs. when it's not used.

Merging lock elision into glibc

Posted Jul 5, 2013 19:10 UTC (Fri) by giraffedata (guest, #1954) [Link]

lock elision ensures executions that behave as if the lock was held.

I'd say it differently: the program behaves that way because the lock (POSIX mutex) is held. POSIX defines the mutex as held when pthread_mutex_lock() returns rc 0. The only thing not held is an internal lock that the OS uses to implement the POSIX mutex.

Merging lock elision into glibc

Posted Jul 7, 2013 5:43 UTC (Sun) by alan (guest, #4018) [Link]

I think the reason for the concern around it is that the lock probably *should* have been taken, it is being elided for performance reasons, but there actually are multiple threads manipulating a single object. So a lock is taken in theory, just not in practice.

Merging lock elision into glibc

Posted Jul 6, 2013 0:36 UTC (Sat) by wahern (subscriber, #37304) [Link] (1 responses)

FWIW, an interesting comment in the Austin Group Defect Tracker thread (thanks to mjw, above, for the link):

Side note: in OpenBSD, PTHREAD_MUTEX_DEFAULT mutexes call abort() if you pthread_mutex_lock() a mutex you already hold or pthread_mutex_unlock() a mutex you don't hold...and that has turned up several locking bugs in open source projects like glib2, where code *simply failed to grab the right lock* and no one noticed because the default mutexes on Linux returned an ignorable error. The assumption that most code has been debugged completely and that therefore the default behavior should be to turn off the checks and run as fast as possible is a bad joke.

Merging lock elision into glibc

Posted Jul 8, 2013 23:42 UTC (Mon) by ibukanov (subscriber, #3942) [Link]

Thanks for the quote.

With assertions disabled one gains in performance. However that win is limited, like 10%. Yet potential losses, like the value of important data that were silently corrupt, could be very high. So despite very small probability of that the total probabilistic cost of those gains can be very negative.

Merging lock elision into glibc

Posted Jul 11, 2013 1:44 UTC (Thu) by heijo (guest, #88363) [Link]

Isn't it possible to store the locks a thread holds in the TLS, and use that to check whether a given lock if held? (and thus solve the self deadlock issues, etc.)

If the number of locks held exceeds a threshold, then just count the extra ones instead of tracking them and disable elision until they are all released.


Copyright © 2013, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds