On 09/14/2012 03:36 PM, Maarten Lankhorst wrote:
Op 14-09-12 12:50, Thomas Hellström schreef:
Hi Maarten!
Broadening the audience a bit..
On 9/14/12 9:12 AM, Maarten Lankhorst wrote:
Op 13-09-12 23:00, Thomas Hellstrom schreef:
On 09/13/2012 07:13 PM, Maarten Lankhorst wrote:
Hey
Op 13-09-12 18:41, Thomas Hellstrom schreef:
On 09/13/2012 05:19 PM, Maarten Lankhorst wrote: > Hey, > > Op 12-09-12 15:28, Thomas Hellstrom schreef: >> On 09/12/2012 02:48 PM, Maarten Lankhorst wrote: >>> Hey Thomas, >>> >>> I'm playing around with moving reservations from ttm to global, but how ttm >>> ttm is handling reservations is getting in the way. The code wants to move >>> the bo from the lru lock at the same time a reservation is made, but that >>> seems to be slightly too strict. It would really help me if that guarantee >>> is removed. >> Hi, Maarten. >> >> Removing that restriction is not really possible at the moment. >> Also the memory accounting code depends on this, and may cause reservations >> in the most awkward places. Since these reservations don't have a ticket >> they may and will cause deadlocks. So in short the restriction is there >> to avoid deadlocks caused by ticketless reservations. > I have finished the lockdep annotations now which seems to catch almost > all abuse I threw at it, so I'm feeling slightly more confident about moving > the locking order and reservations around. Maarten, moving reservations in TTM out of the lru lock is incorrect as the code is written now. If we want to move it out we need something for ticketless reservations
I've been thinking of having a global hash table of tickets with the task struct pointer as the key, but even then, we'd need to be able to handle EBUSY errors on every operation that might try to reserve a buffer.
The fact that lockdep doesn't complain isn't enough. There *will* be deadlock use-cases when TTM is handed the right data-set.
Isn't there a way that a subsystem can register a callback to be performed to remove stuff from LRU and to take a pre-reservation lock?
What if multiple subsystems need those? You will end up with a deadlock again.
I think it would be easier to change the code in ttm_bo.c to not assume the first item on the lru list is really the least recently used, and assume the first item that can be reserved without blocking IS the least recently used instead.
So what would happen then is that we'd spin on the first item on the LRU list, since when reserving we must release the LRU lock, and if reserving fails, we thus need to restart LRU traversal. Typically after a schedule(). That's bad.
So let's take a step back and analyze why the LRU lock has become a problem. From what I can tell, it's because you want to use per-object lock when reserving instead of a global reservation lock (that TTM could use as the LRU lock). Is that correct? and in that case, in what situation do you envision such a global lock being contended to the extent that it hurts performance?
> Lockdep WILL complain about trying to use multiple tickets, doing ticketed > and unticketed blocking reservations mixed, etc. > > I want to remove the global fence_lock and make it a per buffer lock, with some > lockdep annotations it's perfectly legal to grab obj->fence_lock and obj2->fence_lock > if you have a reservation, but it should complain loudly about trying to take 2 fence_locks > at the same time without a reservation. Yes, TTM was previously using per buffer fence locks, and that works fine from a deadlock perspective, but it hurts performance. Fencing 200 buffers in a command submission (google-earth for example) will mean 198 unnecessary locks, each discarding the processor pipelines. Locking is a *slow* operation, particularly on systems with many processors, and I don't think it's a good idea to change that back, without analyzing the performance impact. There are reasons people are writing stuff like RCU to avoid locking...
So why don't we simply use RCU for fence pointers and get rid of the fence locking? :D danvet originally suggested it as a joke but if you think about it, it would make a lot of sense for this usecase.
I thought of that before, but the problem is you'd still need a spinlock to change the buffer's fence pointer, even if reading it becomes quick.
Actually, I changed lockdep annotations a bit to distinguish between the cases where ttm_bo_wait is called without reservation, and ttm_bo_wait is called with, as far as I can see there are only 2 places that do it without, at least if I converted my git tree properly..
http://cgit.freedesktop.org/~mlankhorst/linux/log/?h=v10-wip
First one is nouveau_bo_vma_del, this can be fixed easily. Second one is ttm_bo_cleanup_refs and ttm_bo_cleanup_refs_or_queue, if reservation is done first before ttm_bo_wait, the fence_lock could be dropped entirely by adding smb_mb() in reserve and unreserve, functionally there would be no difference. So if you can verify my lockdep annotations are correct in the most recent commit wrt what's using ttm_bo_wait without reservation we could remove the fence_lock entirely.
~Maarten
Being able to wait for buffer idle or get the fence pointer without reserving is a fundamental property of TTM. Reservation is a long-term lock. The fence lock is a very short term lock. If I were to choose, I'd rather accept per-object fence locks than removing this property, but see below.
I was trying to say the ONLY place where this is used in ttm is in ttm_bo_cleanup_refs(or *_or_queue). No driver depends on this, they all take a reservation first. As such fence_lock can be removed if you change the order of the reservation and waiting around, making it behave the same as the rest of ttm. http://cgit.freedeskt op.org/~mlankhorst/linux/commit/?h=v10-wip&id=b879ac51810408809d6424d26da4013171d1bc15 fixes up those 2 calls, then removes the fence_lock altogether.
In the nouveau case the function nouveau_bo_vma_del seems to be called from different places both with reservation and without reservation, and while it might look simple to add a reserve around a wait for idle, it's a design decision not to require it, because in some situations it's just not a good thing to do.
Likewise, to be able to guarantee that a reserved object is not on any LRU list is also an important property. Removing that property will, in addition to the spin wait we've already discussed make understanding TTM locking even more difficult, and I'd really like to avoid it.
This guarantee will no longer be true if reservations for buffer objects are going to be shared across devices, other users of the reservations may have their own requirements.
Why is this property important though? If it could be changed to 'likely unreserved' instead of guaranteed not on the lru list the few places that rely on this could be fixed to attempt a nonblocking reserve first.
Please see above what I write about spinning around the first item and list traversal restart.
Is it just ttm_mem_evict_first / ttm_bo_swapout / ttm_bo_force_list_clean that would break down? If so those could do a simple iteration over the list to find the first unreserved buffer instead, or do the default thing those functions do when the list is empty if they traversed the full list without finding anything that could be reserved.
TTM guarantees if desired, each user-space client full access to *all* GPU memory resources, so that a user-space client reliably knows when to flush. If it fails buffer validation in the first execbuf pass, it may take an exclusive execbuf lock, evict *all* buffers and retry. Then *all* buffers must be evicted. We can't leave any buffers around that are reserved but not taken off LRU lists.
Also, at final buffer destruction you must be able to guarantee that you have the buffer reserved and that you are the only one holding a reference. That's pretty tricky to do if you don't take the buffer off LRU lists atomically when reserving....
If this were a real performance problem we were trying to solve it would be easier to motivate changes in this area, but if it's just trying to avoid a global reservation lock and a global fence lock that will rarely if ever see any contention, I can't see the point. On the contrary, having per-object locks will be very costly when reserving / fencing many objects. As mentioned before, in the fence lock case it's been tried and removed, so I'd like to know the reasoning behind introducing it again, and in what situations you think the global locks will be contended.
Because I'm trying to keep things as simple as possible, and this whole reservation business is already giving me a headache so I would really like to avoid any special lock that interacts with reservations in a complex way and the easiest way would be to instead accept that there reservation and your lru removal call may not be atomic. I hope to eventually support eviction though, so some notification that the buffer was reserved succesfully is going to be likely, I just don't think it needs to be done atomically.
Then may I humbly suggest that you put a global spinlock around reserving, and a list of callbacks to be called while still locked after reserving. Any subsystem wanting to pull stuff off LRU lists can then register a callback. That's no complex interaction.
/Thomas