<div dir="ltr"><div class="gmail_extra"><div class="gmail_quote">On Mon, Apr 3, 2017 at 12:31 PM, Chad Versace <span dir="ltr"><<a href="mailto:chadversary@chromium.org" target="_blank">chadversary@chromium.org</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div class="HOEnZb"><div class="h5">On Fri 31 Mar 2017, Chad Versace wrote:<br>
> On Wed 15 Mar 2017, Jason Ekstrand wrote:<br>
> > This cache allows us to easily ensure that we have a unique anv_bo for<br>
> > each gem handle.  We'll need this in order to support multiple-import of<br>
> > memory objects and semaphores.<br>
> ><br>
> > v2 (Jason Ekstrand):<br>
> >  - Reject BO imports if the size doesn't match the prime fd size as<br>
> >    reported by lseek().<br>
> ><br>
> > v3 (Jason Ekstrand):<br>
> >  - Fix reference counting around cache_release (Chris Willson)<br>
> >  - Move the mutex_unlock() later in cache_release<br>
> > ---<br>
> >  src/intel/vulkan/anv_<wbr>allocator.c | 261 ++++++++++++++++++++++++++++++<wbr>+++++++++<br>
> >  src/intel/vulkan/anv_private.h   |  26 ++++<br>
> >  2 files changed, 287 insertions(+)<br>
><br>
><br>
> > +static uint32_t<br>
> > +hash_uint32_t(const void *key)<br>
> > +{<br>
> > +   return (uint32_t)(uintptr_t)key;<br>
> > +}<br>
><br>
> This hash function does not appear hashy.<br>
><br>
> If I correctly understand the details of Mesa's struct hash_table,<br>
> choosing the identify function for the hash function causes unwanted<br>
> clustering when inserting consecutive gem handles.  Since the kernel does<br>
> allocate gem handles consecutively, the problem is real.<br>
><br>
> For proof, consider the following:<br>
><br>
>    - Suppose a long-running process (maybe the compositor) has thrashed on the<br>
>      hash table long enough that its bucket count<br>
>      is ht->size = hash_sizes[7].size = 283. Suppose a spike of<br>
>      compositor activity raises the hash table's density to about 0.5.<br>
>      And suppose the hash table buckets are filled with the consecutive gem<br>
>      handles<br>
><br>
>      {0, 0, 0, 0, 4, 5, 6, 7, 8, 9, ..., 127, 128, 0, 0, 0, ..., 0 }<br>
><br>
>      The exact density is (128 - 4 + 1) / 283 = 0.4417.<br>
><br>
>    - Next, some other in-process activity (maybe OpenGL) generated<br>
>      a lot of gem handles after Vulkan's most recently imported<br>
>      gem handle, 128.<br>
<br>
</div></div>This point in the example---the reason why the gem handles in the<br>
anv_bo_cache skip from 128 to 287---is bogus in Vulkan. The problem *is*<br>
real for multiple in-process OpenGL contexts derived from the same<br>
EGLDisplay, using EGL_EXT_image_dma_buf_import, because each context<br>
shares the same intel_screen, and therefore the same drm device fd. But<br>
in Vulkan, each VkDevice opens its own drm device id. So, bogus example.<br>
<br>
BUT, that leads to a new question...<br>
<br>
Since each VkDevice has a unique drm device fd, and since the kernel<br>
allocates gem handles consecutively on the fd, and since struct<br>
hash_table only grows and never shrinks, and since patch 8/18 inserts<br>
every VkDeviceMemory into the cache... I believe no collisions are<br>
possible in anv_bo_cache.<br></blockquote><div><br></div><div>Does this fall under the category of unbreakable kernel ABI or is it just a side-effect of the implementation?  If not, then I'm reluctant to trust it.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
If there are no collisions, then the hash table is only adding overhead,<br></blockquote><div><br></div><div>Sure, but a no-collision hash table is pretty cheap...<br></div><div><br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
and we should use a direct-addressing lookup table. The bo cache should<br>
look like this:<br>
<br>
    struct anv_bo_cache {<br>
       /* The array indices are gem handles. Null entries are legal. */<br>
       struct anv_bo **bos;<br>
<br>
       /* Length of the array. Because the array can have holes, this<br>
        * is *not* the number of gem handles in the array.<br>
        */<br>
       size_t len;<br>
<br>
       pthread_mutex_t mutex;<br>
    };<br>
<br>
    struct anv_bo *<br>
    anv_bo_cache_lookup(struct anv_bo_cache *cache, uint32_t gem_handle)<br>
    {<br>
       struct anv_bo *bo = NULL;<br>
<br>
       pthread_mutex_lock(&cache-><wbr>mutex);<br>
<br>
       if (gem_handle < cache->len)<br>
          bo = cache->entries[gem_handle] == NULL)<br>
<br>
       pthread_mutex_unlock(&cache-><wbr>mutex);<br>
<br>
       return bo;<br>
<br>
    }<br>
<br>
BUT, that leads to yet another question...<br>
<br>
Why is patch 8/18 inserting every VkDeviceMemory into the cache? If<br>
I understand things correctly, we only *need* to insert a VkDeviceMemory<br>
into the cache if, in vkAllocateMemory, either (1)<br>
VkExportMemoryAllocateInfoKHX:<wbr>:handleTypes != 0 or (2)<br>
VkMemoryAllocateInfo's pNext chain contains an import structure.<br></blockquote><div><br></div><div>Because I'm lazy.  In order to start using the bo cache, anv_device_memory::bo needs to be a pointer (well, it's makes the BO cache API simpler and more efficient if it's a pointer).  This would mean that we would have to allocate an additional chunk of memory or go through some other hoops in order to make it work.  At the end of the day, just stuffing everything in the cache was simpler and kept us to a single path.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
If we insert into the cache only those VkDeviceMemories that are<br>
imported or that will be exported, then the bo cache remains small, and<br>
we *should* use a hash table.<br>
</blockquote></div><br></div><div class="gmail_extra">Maybe.  But the client isn't supposed to be allocating hundreds of VkDeviceMemory objects.  It's supposed to allocate a few and then suballocate from those.  If the client allocates so many memory objects that they start hitting hash table performance issues, that's their own fault.<br><br></div><div class="gmail_extra">Also, please remember that vkAllocateMemory is considered to be a *very* heavy-weight function in Vulkan.  Compared to an ioctl which allocates memory, a hash table insert is trivial.  I'm reasonably happy to make a few changes here or there to make it more efficient if any of this proves to be a problem.  However, I think we're working way too hard to micro-optimize something that takes < 0.001% of runtime.<br><br></div><div class="gmail_extra">--Jason<br></div></div>