]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
zsmalloc: introduce new object mapping API
authorSergey Senozhatsky <senozhatsky@chromium.org>
Fri, 21 Feb 2025 22:25:43 +0000 (07:25 +0900)
committerAndrew Morton <akpm@linux-foundation.org>
Fri, 28 Feb 2025 01:00:24 +0000 (17:00 -0800)
Current object mapping API is a little cumbersome.  First, it's
inconsistent, sometimes it returns with page-faults disabled and sometimes
with page-faults enabled.  Second, and most importantly, it enforces
atomicity restrictions on its users.  zs_map_object() has to return a
liner object address which is not always possible because some objects
span multiple physical (non-contiguous) pages.  For such objects zsmalloc
uses a per-CPU buffer to which object's data is copied before a pointer to
that per-CPU buffer is returned back to the caller.  This leads to
another, final, issue - extra memcpy().  Since the caller gets a pointer
to per-CPU buffer it can memcpy() data only to that buffer, and during
zs_unmap_object() zsmalloc will memcpy() from that per-CPU buffer to
physical pages that object in question spans across.

New API splits functions by access mode:
- zs_obj_read_begin(handle, local_copy)
  Returns a pointer to handle memory.  For objects that span two
  physical pages a local_copy buffer is used to store object's
  data before the address is returned to the caller.  Otherwise
  the object's page is kmap_local mapped directly.

- zs_obj_read_end(handle, buf)
  Unmaps the page if it was kmap_local mapped by zs_obj_read_begin().

- zs_obj_write(handle, buf, len)
  Copies len-bytes from compression buffer to handle memory
  (takes care of objects that span two pages).  This does not
  need any additional (e.g. per-CPU) buffers and writes the data
  directly to zsmalloc pool pages.

In terms of performance, on a synthetic and completely reproducible
test that allocates fixed number of objects of fixed sizes and
iterates over those objects, first mapping in RO then in RW mode:

OLD API
=======

3 first results out of 10

  369,205,778      instructions        #    0.80  insn per cycle
   40,467,926      branches            #  113.732 M/sec

  369,002,122      instructions        #    0.62  insn per cycle
   40,426,145      branches            #  189.361 M/sec

  369,036,706      instructions        #    0.63  insn per cycle
   40,430,860      branches            #  204.105 M/sec

[..]

NEW API
=======

3 first results out of 10

  265,799,293      instructions        #    0.51  insn per cycle
   29,834,567      branches            #  170.281 M/sec

  265,765,970      instructions        #    0.55  insn per cycle
   29,829,019      branches            #  161.602 M/sec

  265,764,702      instructions        #    0.51  insn per cycle
   29,828,015      branches            #  189.677 M/sec

[..]

T-test on all 10 runs
=====================

Difference at 95.0% confidence
   -1.03219e+08 +/- 55308.7
   -27.9705% +/- 0.0149878%
   (Student's t, pooled s = 58864.4)

The old API will stay around until the remaining users switch to the new
one.  After that we'll also remove zsmalloc per-CPU buffer and CPU hotplug
handling.

The split of map(RO) and map(WO) into read_{begin/end}/write is suggested
by Yosry Ahmed.

Link: https://lkml.kernel.org/r/20250221222958.2225035-13-senozhatsky@chromium.org
Signed-off-by: Sergey Senozhatsky <senozhatsky@chromium.org>
Suggested-by: Yosry Ahmed <yosry.ahmed@linux.dev>
Cc: Minchan Kim <minchan@kernel.org>
Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
include/linux/zsmalloc.h
mm/zsmalloc.c

index a48cd0ffe57dda457f6d44ba8e00ccfdace99003..7d70983cf3980e764c9649e51051f618d97a71e8 100644 (file)
@@ -58,4 +58,12 @@ unsigned long zs_compact(struct zs_pool *pool);
 unsigned int zs_lookup_class_index(struct zs_pool *pool, unsigned int size);
 
 void zs_pool_stats(struct zs_pool *pool, struct zs_pool_stats *stats);
+
+void *zs_obj_read_begin(struct zs_pool *pool, unsigned long handle,
+                       void *local_copy);
+void zs_obj_read_end(struct zs_pool *pool, unsigned long handle,
+                    void *handle_mem);
+void zs_obj_write(struct zs_pool *pool, unsigned long handle,
+                 void *handle_mem, size_t mem_len);
+
 #endif
index aa7ab91ae74f1ed9f9ca77941277944e16b5d58d..29ca8eae2984f5929d9f955468e20ba0945c595a 100644 (file)
@@ -1377,6 +1377,135 @@ void zs_unmap_object(struct zs_pool *pool, unsigned long handle)
 }
 EXPORT_SYMBOL_GPL(zs_unmap_object);
 
+void *zs_obj_read_begin(struct zs_pool *pool, unsigned long handle,
+                       void *local_copy)
+{
+       struct zspage *zspage;
+       struct zpdesc *zpdesc;
+       unsigned long obj, off;
+       unsigned int obj_idx;
+       struct size_class *class;
+       void *addr;
+
+       WARN_ON(in_interrupt());
+
+       /* Guarantee we can get zspage from handle safely */
+       read_lock(&pool->lock);
+       obj = handle_to_obj(handle);
+       obj_to_location(obj, &zpdesc, &obj_idx);
+       zspage = get_zspage(zpdesc);
+
+       /* Make sure migration doesn't move any pages in this zspage */
+       zspage_read_lock(zspage);
+       read_unlock(&pool->lock);
+
+       class = zspage_class(pool, zspage);
+       off = offset_in_page(class->size * obj_idx);
+
+       if (off + class->size <= PAGE_SIZE) {
+               /* this object is contained entirely within a page */
+               addr = kmap_local_zpdesc(zpdesc);
+               addr += off;
+       } else {
+               size_t sizes[2];
+
+               /* this object spans two pages */
+               sizes[0] = PAGE_SIZE - off;
+               sizes[1] = class->size - sizes[0];
+               addr = local_copy;
+
+               memcpy_from_page(addr, zpdesc_page(zpdesc),
+                                off, sizes[0]);
+               zpdesc = get_next_zpdesc(zpdesc);
+               memcpy_from_page(addr + sizes[0],
+                                zpdesc_page(zpdesc),
+                                0, sizes[1]);
+       }
+
+       if (!ZsHugePage(zspage))
+               addr += ZS_HANDLE_SIZE;
+
+       return addr;
+}
+EXPORT_SYMBOL_GPL(zs_obj_read_begin);
+
+void zs_obj_read_end(struct zs_pool *pool, unsigned long handle,
+                    void *handle_mem)
+{
+       struct zspage *zspage;
+       struct zpdesc *zpdesc;
+       unsigned long obj, off;
+       unsigned int obj_idx;
+       struct size_class *class;
+
+       obj = handle_to_obj(handle);
+       obj_to_location(obj, &zpdesc, &obj_idx);
+       zspage = get_zspage(zpdesc);
+       class = zspage_class(pool, zspage);
+       off = offset_in_page(class->size * obj_idx);
+
+       if (off + class->size <= PAGE_SIZE) {
+               if (!ZsHugePage(zspage))
+                       off += ZS_HANDLE_SIZE;
+               handle_mem -= off;
+               kunmap_local(handle_mem);
+       }
+
+       zspage_read_unlock(zspage);
+}
+EXPORT_SYMBOL_GPL(zs_obj_read_end);
+
+void zs_obj_write(struct zs_pool *pool, unsigned long handle,
+                 void *handle_mem, size_t mem_len)
+{
+       struct zspage *zspage;
+       struct zpdesc *zpdesc;
+       unsigned long obj, off;
+       unsigned int obj_idx;
+       struct size_class *class;
+
+       WARN_ON(in_interrupt());
+
+       /* Guarantee we can get zspage from handle safely */
+       read_lock(&pool->lock);
+       obj = handle_to_obj(handle);
+       obj_to_location(obj, &zpdesc, &obj_idx);
+       zspage = get_zspage(zpdesc);
+
+       /* Make sure migration doesn't move any pages in this zspage */
+       zspage_read_lock(zspage);
+       read_unlock(&pool->lock);
+
+       class = zspage_class(pool, zspage);
+       off = offset_in_page(class->size * obj_idx);
+
+       if (off + class->size <= PAGE_SIZE) {
+               /* this object is contained entirely within a page */
+               void *dst = kmap_local_zpdesc(zpdesc);
+
+               if (!ZsHugePage(zspage))
+                       off += ZS_HANDLE_SIZE;
+               memcpy(dst + off, handle_mem, mem_len);
+               kunmap_local(dst);
+       } else {
+               /* this object spans two pages */
+               size_t sizes[2];
+
+               off += ZS_HANDLE_SIZE;
+               sizes[0] = PAGE_SIZE - off;
+               sizes[1] = mem_len - sizes[0];
+
+               memcpy_to_page(zpdesc_page(zpdesc), off,
+                              handle_mem, sizes[0]);
+               zpdesc = get_next_zpdesc(zpdesc);
+               memcpy_to_page(zpdesc_page(zpdesc), 0,
+                              handle_mem + sizes[0], sizes[1]);
+       }
+
+       zspage_read_unlock(zspage);
+}
+EXPORT_SYMBOL_GPL(zs_obj_write);
+
 /**
  * zs_huge_class_size() - Returns the size (in bytes) of the first huge
  *                        zsmalloc &size_class.