selftests: vm: add COW time test for KSM pages
authorZhansaya Bagdauletkyzy <zhansayabagdaulet@gmail.com>
Thu, 2 Sep 2021 22:00:57 +0000 (15:00 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Fri, 3 Sep 2021 16:58:18 +0000 (09:58 -0700)
Since merged pages are copied every time they need to be modified, the
write access time is different between shared and non-shared pages.  Add
ksm_cow_time() function which evaluates latency of these COW breaks.
First, 4000 pages are allocated and the time, required to modify 1 byte in
every other page, is measured.  After this, the pages are merged into 2000
pairs and in each pair, 1 page is modified (i.e.  they are decoupled) to
detect COW breaks.  The time needed to break COW of merged pages is then
compared with performance of non-shared pages.

The test is run as follows: ./ksm_tests -C
The output:
Total size:    15 MiB

Not merged pages:
Total time:     0.002185489 s
Average speed:  3202.945 MiB/s

Merged pages:
Total time:     0.004386872 s
Average speed:  1595.670 MiB/s

Link: https://lkml.kernel.org/r/1d03ee0d1b341959d4b61672c6401d498bff5652.1629386192.git.zhansayabagdaulet@gmail.com
Signed-off-by: Zhansaya Bagdauletkyzy <zhansayabagdaulet@gmail.com>
Reviewed-by: Tyler Hicks <tyhicks@linux.microsoft.com>
Reviewed-by: Pavel Tatashin <pasha.tatashin@soleen.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
tools/testing/selftests/vm/ksm_tests.c

index 432dfe6..b61dcdb 100644 (file)
@@ -33,7 +33,8 @@ enum ksm_test_name {
        CHECK_KSM_UNMERGE,
        CHECK_KSM_ZERO_PAGE_MERGE,
        CHECK_KSM_NUMA_MERGE,
-       KSM_MERGE_TIME
+       KSM_MERGE_TIME,
+       KSM_COW_TIME
 };
 
 static int ksm_write_sysfs(const char *file_path, unsigned long val)
@@ -98,7 +99,8 @@ static void print_help(void)
               " -U (page unmerging)\n"
               " -P evaluate merging time and speed.\n"
               "    For this test, the size of duplicated memory area (in MiB)\n"
-              "    must be provided using -s option\n\n");
+              "    must be provided using -s option\n"
+              " -C evaluate the time required to break COW of merged pages.\n\n");
 
        printf(" -a: specify the access protections of pages.\n"
               "     <prot> must be of the form [rwx].\n"
@@ -455,6 +457,77 @@ err_out:
        return KSFT_FAIL;
 }
 
+static int ksm_cow_time(int mapping, int prot, int timeout, size_t page_size)
+{
+       void *map_ptr;
+       struct timespec start_time, end_time;
+       unsigned long cow_time_ns;
+
+       /* page_count must be less than 2*page_size */
+       size_t page_count = 4000;
+
+       map_ptr = allocate_memory(NULL, prot, mapping, '*', page_size * page_count);
+       if (!map_ptr)
+               return KSFT_FAIL;
+
+       if (clock_gettime(CLOCK_MONOTONIC_RAW, &start_time)) {
+               perror("clock_gettime");
+               return KSFT_FAIL;
+       }
+       for (size_t i = 0; i < page_count - 1; i = i + 2)
+               memset(map_ptr + page_size * i, '-', 1);
+       if (clock_gettime(CLOCK_MONOTONIC_RAW, &end_time)) {
+               perror("clock_gettime");
+               return KSFT_FAIL;
+       }
+
+       cow_time_ns = (end_time.tv_sec - start_time.tv_sec) * NSEC_PER_SEC +
+                      (end_time.tv_nsec - start_time.tv_nsec);
+
+       printf("Total size:    %lu MiB\n\n", (page_size * page_count) / MB);
+       printf("Not merged pages:\n");
+       printf("Total time:     %ld.%09ld s\n", cow_time_ns / NSEC_PER_SEC,
+              cow_time_ns % NSEC_PER_SEC);
+       printf("Average speed:  %.3f MiB/s\n\n", ((page_size * (page_count / 2)) / MB) /
+                                              ((double)cow_time_ns / NSEC_PER_SEC));
+
+       /* Create 2000 pairs of duplicate pages */
+       for (size_t i = 0; i < page_count - 1; i = i + 2) {
+               memset(map_ptr + page_size * i, '+', i / 2 + 1);
+               memset(map_ptr + page_size * (i + 1), '+', i / 2 + 1);
+       }
+       if (ksm_merge_pages(map_ptr, page_size * page_count, start_time, timeout))
+               goto err_out;
+
+       if (clock_gettime(CLOCK_MONOTONIC_RAW, &start_time)) {
+               perror("clock_gettime");
+               goto err_out;
+       }
+       for (size_t i = 0; i < page_count - 1; i = i + 2)
+               memset(map_ptr + page_size * i, '-', 1);
+       if (clock_gettime(CLOCK_MONOTONIC_RAW, &end_time)) {
+               perror("clock_gettime");
+               goto err_out;
+       }
+
+       cow_time_ns = (end_time.tv_sec - start_time.tv_sec) * NSEC_PER_SEC +
+                      (end_time.tv_nsec - start_time.tv_nsec);
+
+       printf("Merged pages:\n");
+       printf("Total time:     %ld.%09ld s\n", cow_time_ns / NSEC_PER_SEC,
+              cow_time_ns % NSEC_PER_SEC);
+       printf("Average speed:  %.3f MiB/s\n", ((page_size * (page_count / 2)) / MB) /
+                                              ((double)cow_time_ns / NSEC_PER_SEC));
+
+       munmap(map_ptr, page_size * page_count);
+       return KSFT_PASS;
+
+err_out:
+       printf("Not OK\n");
+       munmap(map_ptr, page_size * page_count);
+       return KSFT_FAIL;
+}
+
 int main(int argc, char *argv[])
 {
        int ret, opt;
@@ -468,7 +541,7 @@ int main(int argc, char *argv[])
        bool merge_across_nodes = KSM_MERGE_ACROSS_NODES_DEFAULT;
        long size_MB = 0;
 
-       while ((opt = getopt(argc, argv, "ha:p:l:z:m:s:MUZNP")) != -1) {
+       while ((opt = getopt(argc, argv, "ha:p:l:z:m:s:MUZNPC")) != -1) {
                switch (opt) {
                case 'a':
                        prot = str_to_prot(optarg);
@@ -522,6 +595,9 @@ int main(int argc, char *argv[])
                case 'P':
                        test_name = KSM_MERGE_TIME;
                        break;
+               case 'C':
+                       test_name = KSM_COW_TIME;
+                       break;
                default:
                        return KSFT_FAIL;
                }
@@ -571,6 +647,10 @@ int main(int argc, char *argv[])
                ret = ksm_merge_time(MAP_PRIVATE | MAP_ANONYMOUS, prot, ksm_scan_limit_sec,
                                     size_MB);
                break;
+       case KSM_COW_TIME:
+               ret = ksm_cow_time(MAP_PRIVATE | MAP_ANONYMOUS, prot, ksm_scan_limit_sec,
+                                  page_size);
+               break;
        }
 
        if (ksm_restore(&ksm_sysfs_old)) {