diff options
| author | Kirill A. Shutemov <kirill.shutemov@linux.intel.com> | 2017-11-17 18:31:22 -0500 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2017-11-17 19:10:04 -0500 |
| commit | 64c349f4ae78723248c474531d0cbc524fc5ba77 (patch) | |
| tree | b8284ccb8224f9f4023d5b4c1caa6bc053a2c90f | |
| parent | 15df03c87983660a4d1eedb4541778592bd97684 (diff) | |
mm: add infrastructure for get_user_pages_fast() benchmarking
Performance of get_user_pages_fast() is critical for some workloads, but
it's tricky to test it directly.
This patch provides /sys/kernel/debug/gup_benchmark that helps with
testing performance of it.
See tools/testing/selftests/vm/gup_benchmark.c for userspace
counterpart.
Link: http://lkml.kernel.org/r/20170908215603.9189-2-kirill.shutemov@linux.intel.com
Signed-off-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Ingo Molnar <mingo@kernel.org>
Cc: Thorsten Leemhuis <regressions@leemhuis.info>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Huang Ying <ying.huang@intel.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
| -rw-r--r-- | mm/Kconfig | 9 | ||||
| -rw-r--r-- | mm/Makefile | 1 | ||||
| -rw-r--r-- | mm/gup_benchmark.c | 100 | ||||
| -rw-r--r-- | tools/testing/selftests/vm/Makefile | 1 | ||||
| -rw-r--r-- | tools/testing/selftests/vm/gup_benchmark.c | 91 |
5 files changed, 202 insertions, 0 deletions
diff --git a/mm/Kconfig b/mm/Kconfig index 9c4bdddd80c2..03ff7703d322 100644 --- a/mm/Kconfig +++ b/mm/Kconfig | |||
| @@ -756,3 +756,12 @@ config PERCPU_STATS | |||
| 756 | This feature collects and exposes statistics via debugfs. The | 756 | This feature collects and exposes statistics via debugfs. The |
| 757 | information includes global and per chunk statistics, which can | 757 | information includes global and per chunk statistics, which can |
| 758 | be used to help understand percpu memory usage. | 758 | be used to help understand percpu memory usage. |
| 759 | |||
| 760 | config GUP_BENCHMARK | ||
| 761 | bool "Enable infrastructure for get_user_pages_fast() benchmarking" | ||
| 762 | default n | ||
| 763 | help | ||
| 764 | Provides /sys/kernel/debug/gup_benchmark that helps with testing | ||
| 765 | performance of get_user_pages_fast(). | ||
| 766 | |||
| 767 | See tools/testing/selftests/vm/gup_benchmark.c | ||
diff --git a/mm/Makefile b/mm/Makefile index e7ebd176fb93..e669f02c5a54 100644 --- a/mm/Makefile +++ b/mm/Makefile | |||
| @@ -80,6 +80,7 @@ obj-$(CONFIG_PAGE_COUNTER) += page_counter.o | |||
| 80 | obj-$(CONFIG_MEMCG) += memcontrol.o vmpressure.o | 80 | obj-$(CONFIG_MEMCG) += memcontrol.o vmpressure.o |
| 81 | obj-$(CONFIG_MEMCG_SWAP) += swap_cgroup.o | 81 | obj-$(CONFIG_MEMCG_SWAP) += swap_cgroup.o |
| 82 | obj-$(CONFIG_CGROUP_HUGETLB) += hugetlb_cgroup.o | 82 | obj-$(CONFIG_CGROUP_HUGETLB) += hugetlb_cgroup.o |
| 83 | obj-$(CONFIG_GUP_BENCHMARK) += gup_benchmark.o | ||
| 83 | obj-$(CONFIG_MEMORY_FAILURE) += memory-failure.o | 84 | obj-$(CONFIG_MEMORY_FAILURE) += memory-failure.o |
| 84 | obj-$(CONFIG_HWPOISON_INJECT) += hwpoison-inject.o | 85 | obj-$(CONFIG_HWPOISON_INJECT) += hwpoison-inject.o |
| 85 | obj-$(CONFIG_DEBUG_KMEMLEAK) += kmemleak.o | 86 | obj-$(CONFIG_DEBUG_KMEMLEAK) += kmemleak.o |
diff --git a/mm/gup_benchmark.c b/mm/gup_benchmark.c new file mode 100644 index 000000000000..5c8e2abeaa15 --- /dev/null +++ b/mm/gup_benchmark.c | |||
| @@ -0,0 +1,100 @@ | |||
| 1 | #include <linux/kernel.h> | ||
| 2 | #include <linux/mm.h> | ||
| 3 | #include <linux/slab.h> | ||
| 4 | #include <linux/uaccess.h> | ||
| 5 | #include <linux/ktime.h> | ||
| 6 | #include <linux/debugfs.h> | ||
| 7 | |||
| 8 | #define GUP_FAST_BENCHMARK _IOWR('g', 1, struct gup_benchmark) | ||
| 9 | |||
| 10 | struct gup_benchmark { | ||
| 11 | __u64 delta_usec; | ||
| 12 | __u64 addr; | ||
| 13 | __u64 size; | ||
| 14 | __u32 nr_pages_per_call; | ||
| 15 | __u32 flags; | ||
| 16 | }; | ||
| 17 | |||
| 18 | static int __gup_benchmark_ioctl(unsigned int cmd, | ||
| 19 | struct gup_benchmark *gup) | ||
| 20 | { | ||
| 21 | ktime_t start_time, end_time; | ||
| 22 | unsigned long i, nr, nr_pages, addr, next; | ||
| 23 | struct page **pages; | ||
| 24 | |||
| 25 | nr_pages = gup->size / PAGE_SIZE; | ||
| 26 | pages = kvmalloc(sizeof(void *) * nr_pages, GFP_KERNEL); | ||
| 27 | if (!pages) | ||
| 28 | return -ENOMEM; | ||
| 29 | |||
| 30 | i = 0; | ||
| 31 | nr = gup->nr_pages_per_call; | ||
| 32 | start_time = ktime_get(); | ||
| 33 | for (addr = gup->addr; addr < gup->addr + gup->size; addr = next) { | ||
| 34 | if (nr != gup->nr_pages_per_call) | ||
| 35 | break; | ||
| 36 | |||
| 37 | next = addr + nr * PAGE_SIZE; | ||
| 38 | if (next > gup->addr + gup->size) { | ||
| 39 | next = gup->addr + gup->size; | ||
| 40 | nr = (next - addr) / PAGE_SIZE; | ||
| 41 | } | ||
| 42 | |||
| 43 | nr = get_user_pages_fast(addr, nr, gup->flags & 1, pages + i); | ||
| 44 | i += nr; | ||
| 45 | } | ||
| 46 | end_time = ktime_get(); | ||
| 47 | |||
| 48 | gup->delta_usec = ktime_us_delta(end_time, start_time); | ||
| 49 | gup->size = addr - gup->addr; | ||
| 50 | |||
| 51 | for (i = 0; i < nr_pages; i++) { | ||
| 52 | if (!pages[i]) | ||
| 53 | break; | ||
| 54 | put_page(pages[i]); | ||
| 55 | } | ||
| 56 | |||
| 57 | kvfree(pages); | ||
| 58 | return 0; | ||
| 59 | } | ||
| 60 | |||
| 61 | static long gup_benchmark_ioctl(struct file *filep, unsigned int cmd, | ||
| 62 | unsigned long arg) | ||
| 63 | { | ||
| 64 | struct gup_benchmark gup; | ||
| 65 | int ret; | ||
| 66 | |||
| 67 | if (cmd != GUP_FAST_BENCHMARK) | ||
| 68 | return -EINVAL; | ||
| 69 | |||
| 70 | if (copy_from_user(&gup, (void __user *)arg, sizeof(gup))) | ||
| 71 | return -EFAULT; | ||
| 72 | |||
| 73 | ret = __gup_benchmark_ioctl(cmd, &gup); | ||
| 74 | if (ret) | ||
| 75 | return ret; | ||
| 76 | |||
| 77 | if (copy_to_user((void __user *)arg, &gup, sizeof(gup))) | ||
| 78 | return -EFAULT; | ||
| 79 | |||
| 80 | return 0; | ||
| 81 | } | ||
| 82 | |||
| 83 | static const struct file_operations gup_benchmark_fops = { | ||
| 84 | .open = nonseekable_open, | ||
| 85 | .unlocked_ioctl = gup_benchmark_ioctl, | ||
| 86 | }; | ||
| 87 | |||
| 88 | static int gup_benchmark_init(void) | ||
| 89 | { | ||
| 90 | void *ret; | ||
| 91 | |||
| 92 | ret = debugfs_create_file_unsafe("gup_benchmark", 0600, NULL, NULL, | ||
| 93 | &gup_benchmark_fops); | ||
| 94 | if (!ret) | ||
| 95 | pr_warn("Failed to create gup_benchmark in debugfs"); | ||
| 96 | |||
| 97 | return 0; | ||
| 98 | } | ||
| 99 | |||
| 100 | late_initcall(gup_benchmark_init); | ||
diff --git a/tools/testing/selftests/vm/Makefile b/tools/testing/selftests/vm/Makefile index e49eca1915f8..7f45806bd863 100644 --- a/tools/testing/selftests/vm/Makefile +++ b/tools/testing/selftests/vm/Makefile | |||
| @@ -18,6 +18,7 @@ TEST_GEN_FILES += transhuge-stress | |||
| 18 | TEST_GEN_FILES += userfaultfd | 18 | TEST_GEN_FILES += userfaultfd |
| 19 | TEST_GEN_FILES += mlock-random-test | 19 | TEST_GEN_FILES += mlock-random-test |
| 20 | TEST_GEN_FILES += virtual_address_range | 20 | TEST_GEN_FILES += virtual_address_range |
| 21 | TEST_GEN_FILES += gup_benchmark | ||
| 21 | 22 | ||
| 22 | TEST_PROGS := run_vmtests | 23 | TEST_PROGS := run_vmtests |
| 23 | 24 | ||
diff --git a/tools/testing/selftests/vm/gup_benchmark.c b/tools/testing/selftests/vm/gup_benchmark.c new file mode 100644 index 000000000000..36df55132036 --- /dev/null +++ b/tools/testing/selftests/vm/gup_benchmark.c | |||
| @@ -0,0 +1,91 @@ | |||
| 1 | #include <fcntl.h> | ||
| 2 | #include <stdio.h> | ||
| 3 | #include <stdlib.h> | ||
| 4 | #include <unistd.h> | ||
| 5 | |||
| 6 | #include <sys/ioctl.h> | ||
| 7 | #include <sys/mman.h> | ||
| 8 | #include <sys/prctl.h> | ||
| 9 | #include <sys/stat.h> | ||
| 10 | #include <sys/types.h> | ||
| 11 | |||
| 12 | #include <linux/types.h> | ||
| 13 | |||
| 14 | #define MB (1UL << 20) | ||
| 15 | #define PAGE_SIZE sysconf(_SC_PAGESIZE) | ||
| 16 | |||
| 17 | #define GUP_FAST_BENCHMARK _IOWR('g', 1, struct gup_benchmark) | ||
| 18 | |||
| 19 | struct gup_benchmark { | ||
| 20 | __u64 delta_usec; | ||
| 21 | __u64 addr; | ||
| 22 | __u64 size; | ||
| 23 | __u32 nr_pages_per_call; | ||
| 24 | __u32 flags; | ||
| 25 | }; | ||
| 26 | |||
| 27 | int main(int argc, char **argv) | ||
| 28 | { | ||
| 29 | struct gup_benchmark gup; | ||
| 30 | unsigned long size = 128 * MB; | ||
| 31 | int i, fd, opt, nr_pages = 1, thp = -1, repeats = 1, write = 0; | ||
| 32 | char *p; | ||
| 33 | |||
| 34 | while ((opt = getopt(argc, argv, "m:r:n:tT")) != -1) { | ||
| 35 | switch (opt) { | ||
| 36 | case 'm': | ||
| 37 | size = atoi(optarg) * MB; | ||
| 38 | break; | ||
| 39 | case 'r': | ||
| 40 | repeats = atoi(optarg); | ||
| 41 | break; | ||
| 42 | case 'n': | ||
| 43 | nr_pages = atoi(optarg); | ||
| 44 | break; | ||
| 45 | case 't': | ||
| 46 | thp = 1; | ||
| 47 | break; | ||
| 48 | case 'T': | ||
| 49 | thp = 0; | ||
| 50 | break; | ||
| 51 | case 'w': | ||
| 52 | write = 1; | ||
| 53 | default: | ||
| 54 | return -1; | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | gup.nr_pages_per_call = nr_pages; | ||
| 59 | gup.flags = write; | ||
| 60 | |||
| 61 | fd = open("/sys/kernel/debug/gup_benchmark", O_RDWR); | ||
| 62 | if (fd == -1) | ||
| 63 | perror("open"), exit(1); | ||
| 64 | |||
