mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00
zsmalloc: introduce new object mapping API
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/20250303022425.285971-15-senozhatsky@chromium.org Signed-off-by: Sergey Senozhatsky <senozhatsky@chromium.org> Suggested-by: Yosry Ahmed <yosry.ahmed@linux.dev> Reviewed-by: Yosry Ahmed <yosry.ahmed@linux.dev> Cc: Hillf Danton <hdanton@sina.com> Cc: Kairui Song <ryncsn@gmail.com> Cc: Minchan Kim <minchan@kernel.org> Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
This commit is contained in:
parent
e27af3f936
commit
44f7641349
2 changed files with 133 additions and 0 deletions
|
@ -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
|
||||
|
|
125
mm/zsmalloc.c
125
mm/zsmalloc.c
|
@ -1362,6 +1362,131 @@ 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;
|
||||
|
||||
/* 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;
|
||||
|
||||
/* 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.
|
||||
|
|
Loading…
Add table
Reference in a new issue