Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

zram: replace global tb_lock with fine grain lock

Currently, we use a rwlock tb_lock to protect concurrent access to the
whole zram meta table. However, according to the actual access model,
there is only a small chance for upper user to access the same
table[index], so the current lock granularity is too big.

The idea of optimization is to change the lock granularity from whole
meta table to per table entry (table -> table[index]), so that we can
protect concurrent access to the same table[index], meanwhile allow the
maximum concurrency.

With this in mind, several kinds of locks which could be used as a
per-entry lock were tested and compared:

Test environment:
x86-64 Intel Core2 Q8400, system memory 4GB, Ubuntu 12.04,
kernel v3.15.0-rc3 as base, zram with 4 max_comp_streams LZO.

iozone test:
iozone -t 4 -R -r 16K -s 200M -I +Z
(1GB zram with ext4 filesystem, take the average of 10 tests, KB/s)

Test base CAS spinlock rwlock bit_spinlock
-------------------------------------------------------------------
Initial write 1381094 1425435 1422860 1423075 1421521
Rewrite 1529479 1641199 1668762 1672855 1654910
Read 8468009 11324979 11305569 11117273 10997202
Re-read 8467476 11260914 11248059 11145336 10906486
Reverse Read 6821393 8106334 8282174 8279195 8109186
Stride read 7191093 8994306 9153982 8961224 9004434
Random read 7156353 8957932 9167098 8980465 8940476
Mixed workload 4172747 5680814 5927825 5489578 5972253
Random write 1483044 1605588 1594329 1600453 1596010
Pwrite 1276644 1303108 1311612 1314228 1300960
Pread 4324337 4632869 4618386 4457870 4500166

To enhance the possibility of access the same table[index] concurrently,
set zram a small disksize(10MB) and let threads run with large loop
count.

fio test:
fio --bs=32k --randrepeat=1 --randseed=100 --refill_buffers
--scramble_buffers=1 --direct=1 --loops=3000 --numjobs=4
--filename=/dev/zram0 --name=seq-write --rw=write --stonewall
--name=seq-read --rw=read --stonewall --name=seq-readwrite
--rw=rw --stonewall --name=rand-readwrite --rw=randrw --stonewall
(10MB zram raw block device, take the average of 10 tests, KB/s)

Test base CAS spinlock rwlock bit_spinlock
-------------------------------------------------------------
seq-write 933789 999357 1003298 995961 1001958
seq-read 5634130 6577930 6380861 6243912 6230006
seq-rw 1405687 1638117 1640256 1633903 1634459
rand-rw 1386119 1614664 1617211 1609267 1612471

All the optimization methods show a higher performance than the base,
however, it is hard to say which method is the most appropriate.

On the other hand, zram is mostly used on small embedded system, so we
don't want to increase any memory footprint.

This patch pick the bit_spinlock method, pack object size and page_flag
into an unsigned long table.value, so as to not increase any memory
overhead on both 32-bit and 64-bit system.

On the third hand, even though different kinds of locks have different
performances, we can ignore this difference, because: if zram is used as
zram swapfile, the swap subsystem can prevent concurrent access to the
same swapslot; if zram is used as zram-blk for set up filesystem on it,
the upper filesystem and the page cache also prevent concurrent access
of the same block mostly. So we can ignore the different performances
among locks.

Acked-by: Sergey Senozhatsky <sergey.senozhatsky@gmail.com>
Reviewed-by: Davidlohr Bueso <davidlohr@hp.com>
Signed-off-by: Weijie Yang <weijie.yang@samsung.com>
Signed-off-by: Minchan Kim <minchan@kernel.org>
Cc: Jerome Marchand <jmarchan@redhat.com>
Cc: Nitin Gupta <ngupta@vflare.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Weijie Yang and committed by
Linus Torvalds
d2d5e762 023b409f

+60 -33
+42 -27
drivers/block/zram/zram_drv.c
··· 183 183 static int zram_test_flag(struct zram_meta *meta, u32 index, 184 184 enum zram_pageflags flag) 185 185 { 186 - return meta->table[index].flags & BIT(flag); 186 + return meta->table[index].value & BIT(flag); 187 187 } 188 188 189 189 static void zram_set_flag(struct zram_meta *meta, u32 index, 190 190 enum zram_pageflags flag) 191 191 { 192 - meta->table[index].flags |= BIT(flag); 192 + meta->table[index].value |= BIT(flag); 193 193 } 194 194 195 195 static void zram_clear_flag(struct zram_meta *meta, u32 index, 196 196 enum zram_pageflags flag) 197 197 { 198 - meta->table[index].flags &= ~BIT(flag); 198 + meta->table[index].value &= ~BIT(flag); 199 + } 200 + 201 + static size_t zram_get_obj_size(struct zram_meta *meta, u32 index) 202 + { 203 + return meta->table[index].value & (BIT(ZRAM_FLAG_SHIFT) - 1); 204 + } 205 + 206 + static void zram_set_obj_size(struct zram_meta *meta, 207 + u32 index, size_t size) 208 + { 209 + unsigned long flags = meta->table[index].value >> ZRAM_FLAG_SHIFT; 210 + 211 + meta->table[index].value = (flags << ZRAM_FLAG_SHIFT) | size; 199 212 } 200 213 201 214 static inline int is_partial_io(struct bio_vec *bvec) ··· 268 255 goto free_table; 269 256 } 270 257 271 - rwlock_init(&meta->tb_lock); 272 258 return meta; 273 259 274 260 free_table: ··· 316 304 flush_dcache_page(page); 317 305 } 318 306 319 - /* NOTE: caller should hold meta->tb_lock with write-side */ 307 + 308 + /* 309 + * To protect concurrent access to the same index entry, 310 + * caller should hold this table index entry's bit_spinlock to 311 + * indicate this index entry is accessing. 312 + */ 320 313 static void zram_free_page(struct zram *zram, size_t index) 321 314 { 322 315 struct zram_meta *meta = zram->meta; ··· 341 324 342 325 zs_free(meta->mem_pool, handle); 343 326 344 - atomic64_sub(meta->table[index].size, &zram->stats.compr_data_size); 327 + atomic64_sub(zram_get_obj_size(meta, index), 328 + &zram->stats.compr_data_size); 345 329 atomic64_dec(&zram->stats.pages_stored); 346 330 347 331 meta->table[index].handle = 0; 348 - meta->table[index].size = 0; 332 + zram_set_obj_size(meta, index, 0); 349 333 } 350 334 351 335 static int zram_decompress_page(struct zram *zram, char *mem, u32 index) ··· 357 339 unsigned long handle; 358 340 size_t size; 359 341 360 - read_lock(&meta->tb_lock); 342 + bit_spin_lock(ZRAM_ACCESS, &meta->table[index].value); 361 343 handle = meta->table[index].handle; 362 - size = meta->table[index].size; 344 + size = zram_get_obj_size(meta, index); 363 345 364 346 if (!handle || zram_test_flag(meta, index, ZRAM_ZERO)) { 365 - read_unlock(&meta->tb_lock); 347 + bit_spin_unlock(ZRAM_ACCESS, &meta->table[index].value); 366 348 clear_page(mem); 367 349 return 0; 368 350 } ··· 373 355 else 374 356 ret = zcomp_decompress(zram->comp, cmem, size, mem); 375 357 zs_unmap_object(meta->mem_pool, handle); 376 - read_unlock(&meta->tb_lock); 358 + bit_spin_unlock(ZRAM_ACCESS, &meta->table[index].value); 377 359 378 360 /* Should NEVER happen. Return bio error if it does. */ 379 361 if (unlikely(ret)) { ··· 394 376 struct zram_meta *meta = zram->meta; 395 377 page = bvec->bv_page; 396 378 397 - read_lock(&meta->tb_lock); 379 + bit_spin_lock(ZRAM_ACCESS, &meta->table[index].value); 398 380 if (unlikely(!meta->table[index].handle) || 399 381 zram_test_flag(meta, index, ZRAM_ZERO)) { 400 - read_unlock(&meta->tb_lock); 382 + bit_spin_unlock(ZRAM_ACCESS, &meta->table[index].value); 401 383 handle_zero_page(bvec); 402 384 return 0; 403 385 } 404 - read_unlock(&meta->tb_lock); 386 + bit_spin_unlock(ZRAM_ACCESS, &meta->table[index].value); 405 387 406 388 if (is_partial_io(bvec)) 407 389 /* Use a temporary buffer to decompress the page */ ··· 479 461 if (page_zero_filled(uncmem)) { 480 462 kunmap_atomic(user_mem); 481 463 /* Free memory associated with this sector now. */ 482 - write_lock(&zram->meta->tb_lock); 464 + bit_spin_lock(ZRAM_ACCESS, &meta->table[index].value); 483 465 zram_free_page(zram, index); 484 466 zram_set_flag(meta, index, ZRAM_ZERO); 485 - write_unlock(&zram->meta->tb_lock); 467 + bit_spin_unlock(ZRAM_ACCESS, &meta->table[index].value); 486 468 487 469 atomic64_inc(&zram->stats.zero_pages); 488 470 ret = 0; ··· 532 514 * Free memory associated with this sector 533 515 * before overwriting unused sectors. 534 516 */ 535 - write_lock(&zram->meta->tb_lock); 517 + bit_spin_lock(ZRAM_ACCESS, &meta->table[index].value); 536 518 zram_free_page(zram, index); 537 519 538 520 meta->table[index].handle = handle; 539 - meta->table[index].size = clen; 540 - write_unlock(&zram->meta->tb_lock); 521 + zram_set_obj_size(meta, index, clen); 522 + bit_spin_unlock(ZRAM_ACCESS, &meta->table[index].value); 541 523 542 524 /* Update stats */ 543 525 atomic64_add(clen, &zram->stats.compr_data_size); ··· 578 560 int offset, struct bio *bio) 579 561 { 580 562 size_t n = bio->bi_iter.bi_size; 563 + struct zram_meta *meta = zram->meta; 581 564 582 565 /* 583 566 * zram manages data in physical block size units. Because logical block ··· 599 580 } 600 581 601 582 while (n >= PAGE_SIZE) { 602 - /* 603 - * Discard request can be large so the lock hold times could be 604 - * lengthy. So take the lock once per page. 605 - */ 606 - write_lock(&zram->meta->tb_lock); 583 + bit_spin_lock(ZRAM_ACCESS, &meta->table[index].value); 607 584 zram_free_page(zram, index); 608 - write_unlock(&zram->meta->tb_lock); 585 + bit_spin_unlock(ZRAM_ACCESS, &meta->table[index].value); 609 586 index++; 610 587 n -= PAGE_SIZE; 611 588 } ··· 836 821 zram = bdev->bd_disk->private_data; 837 822 meta = zram->meta; 838 823 839 - write_lock(&meta->tb_lock); 824 + bit_spin_lock(ZRAM_ACCESS, &meta->table[index].value); 840 825 zram_free_page(zram, index); 841 - write_unlock(&meta->tb_lock); 826 + bit_spin_unlock(ZRAM_ACCESS, &meta->table[index].value); 842 827 atomic64_inc(&zram->stats.notify_free); 843 828 } 844 829
+18 -6
drivers/block/zram/zram_drv.h
··· 50 50 #define ZRAM_SECTOR_PER_LOGICAL_BLOCK \ 51 51 (1 << (ZRAM_LOGICAL_BLOCK_SHIFT - SECTOR_SHIFT)) 52 52 53 - /* Flags for zram pages (table[page_no].flags) */ 53 + 54 + /* 55 + * The lower ZRAM_FLAG_SHIFT bits of table.value is for 56 + * object size (excluding header), the higher bits is for 57 + * zram_pageflags. 58 + * 59 + * zram is mainly used for memory efficiency so we want to keep memory 60 + * footprint small so we can squeeze size and flags into a field. 61 + * The lower ZRAM_FLAG_SHIFT bits is for object size (excluding header), 62 + * the higher bits is for zram_pageflags. 63 + */ 64 + #define ZRAM_FLAG_SHIFT 24 65 + 66 + /* Flags for zram pages (table[page_no].value) */ 54 67 enum zram_pageflags { 55 68 /* Page consists entirely of zeros */ 56 - ZRAM_ZERO, 69 + ZRAM_ZERO = ZRAM_FLAG_SHIFT + 1, 70 + ZRAM_ACCESS, /* page in now accessed */ 57 71 58 72 __NR_ZRAM_PAGEFLAGS, 59 73 }; ··· 77 63 /* Allocated for each disk page */ 78 64 struct zram_table_entry { 79 65 unsigned long handle; 80 - u16 size; /* object size (excluding header) */ 81 - u8 flags; 82 - } __aligned(4); 66 + unsigned long value; 67 + }; 83 68 84 69 struct zram_stats { 85 70 atomic64_t compr_data_size; /* compressed size of pages stored */ ··· 93 80 }; 94 81 95 82 struct zram_meta { 96 - rwlock_t tb_lock; /* protect table */ 97 83 struct zram_table_entry *table; 98 84 struct zs_pool *mem_pool; 99 85 };