gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/perf/linux/fork_benchmark.cc (about)

     1  // Copyright 2020 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  #include <unistd.h>
    16  
    17  #include "gtest/gtest.h"
    18  #include "absl/synchronization/barrier.h"
    19  #include "benchmark/benchmark.h"
    20  #include "test/util/cleanup.h"
    21  #include "test/util/file_descriptor.h"
    22  #include "test/util/logging.h"
    23  #include "test/util/test_util.h"
    24  #include "test/util/thread_util.h"
    25  
    26  namespace gvisor {
    27  namespace testing {
    28  
    29  namespace {
    30  
    31  constexpr int kBusyMax = 250;
    32  
    33  // Do some CPU-bound busy-work.
    34  int busy(int max) {
    35    // Prevent the compiler from optimizing this work away,
    36    volatile int count = 0;
    37  
    38    for (int i = 1; i < max; i++) {
    39      for (int j = 2; j < i / 2; j++) {
    40        if (i % j == 0) {
    41          count++;
    42        }
    43      }
    44    }
    45  
    46    return count;
    47  }
    48  
    49  void BM_CPUBoundUniprocess(benchmark::State& state) {
    50    for (auto _ : state) {
    51      busy(kBusyMax);
    52    }
    53  }
    54  
    55  BENCHMARK(BM_CPUBoundUniprocess);
    56  
    57  void BM_CPUBoundAsymmetric(benchmark::State& state) {
    58    const size_t max = state.max_iterations;
    59    pid_t child = fork();
    60    if (child == 0) {
    61      for (size_t i = 0; i < max; i++) {
    62        busy(kBusyMax);
    63      }
    64      _exit(0);
    65    }
    66    ASSERT_THAT(child, SyscallSucceeds());
    67    ASSERT_TRUE(state.KeepRunningBatch(max));
    68  
    69    int status;
    70    EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceeds());
    71    EXPECT_TRUE(WIFEXITED(status));
    72    EXPECT_EQ(0, WEXITSTATUS(status));
    73    ASSERT_FALSE(state.KeepRunning());
    74  }
    75  
    76  BENCHMARK(BM_CPUBoundAsymmetric)->UseRealTime();
    77  
    78  void BM_CPUBoundSymmetric(benchmark::State& state) {
    79    std::vector<pid_t> children;
    80    auto child_cleanup = Cleanup([&] {
    81      for (const pid_t child : children) {
    82        int status;
    83        EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceeds());
    84        EXPECT_TRUE(WIFEXITED(status));
    85        EXPECT_EQ(0, WEXITSTATUS(status));
    86      }
    87      ASSERT_FALSE(state.KeepRunning());
    88    });
    89  
    90    const int processes = state.range(0);
    91    for (int i = 0; i < processes; i++) {
    92      size_t cur = (state.max_iterations + (processes - 1)) / processes;
    93      if ((state.iterations() + cur) >= state.max_iterations) {
    94        cur = state.max_iterations - state.iterations();
    95      }
    96      pid_t child = fork();
    97      if (child == 0) {
    98        for (size_t i = 0; i < cur; i++) {
    99          busy(kBusyMax);
   100        }
   101        _exit(0);
   102      }
   103      ASSERT_THAT(child, SyscallSucceeds());
   104      if (cur > 0) {
   105        // We can have a zero cur here, depending.
   106        ASSERT_TRUE(state.KeepRunningBatch(cur));
   107      }
   108      children.push_back(child);
   109    }
   110  }
   111  
   112  BENCHMARK(BM_CPUBoundSymmetric)->Range(2, 16)->UseRealTime();
   113  
   114  // Child routine for ProcessSwitch/ThreadSwitch.
   115  // Reads from readfd and writes the result to writefd.
   116  void SwitchChild(int readfd, int writefd) {
   117    while (1) {
   118      char buf;
   119      int ret = ReadFd(readfd, &buf, 1);
   120      if (ret == 0) {
   121        break;
   122      }
   123      TEST_CHECK_MSG(ret == 1, "read failed");
   124  
   125      ret = WriteFd(writefd, &buf, 1);
   126      if (ret == -1) {
   127        TEST_CHECK_MSG(errno == EPIPE, "unexpected write failure");
   128        break;
   129      }
   130      TEST_CHECK_MSG(ret == 1, "write failed");
   131    }
   132  }
   133  
   134  // Send bytes in a loop through a series of pipes, each passing through a
   135  // different process.
   136  //
   137  //  Proc 0        Proc 1
   138  //    * ----------> *
   139  //    ^   Pipe 1    |
   140  //    |             |
   141  //    | Pipe 0      | Pipe 2
   142  //    |             |
   143  //    |             |
   144  //    |   Pipe 3    v
   145  //    * <---------- *
   146  //  Proc 3        Proc 2
   147  //
   148  // This exercises context switching through multiple processes.
   149  void BM_ProcessSwitch(benchmark::State& state) {
   150    // Code below assumes there are at least two processes.
   151    const int num_processes = state.range(0);
   152    ASSERT_GE(num_processes, 2);
   153  
   154    std::vector<pid_t> children;
   155    auto child_cleanup = Cleanup([&] {
   156      for (const pid_t child : children) {
   157        int status;
   158        EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceeds());
   159        EXPECT_TRUE(WIFEXITED(status));
   160        EXPECT_EQ(0, WEXITSTATUS(status));
   161      }
   162    });
   163  
   164    // Must come after children, as the FDs must be closed before the children
   165    // will exit.
   166    std::vector<FileDescriptor> read_fds;
   167    std::vector<FileDescriptor> write_fds;
   168  
   169    for (int i = 0; i < num_processes; i++) {
   170      int fds[2];
   171      ASSERT_THAT(pipe(fds), SyscallSucceeds());
   172      read_fds.emplace_back(fds[0]);
   173      write_fds.emplace_back(fds[1]);
   174    }
   175  
   176    // This process is one of the processes in the loop. It will be considered
   177    // index 0.
   178    for (int i = 1; i < num_processes; i++) {
   179      // Read from current pipe index, write to next.
   180      const int read_index = i;
   181      const int read_fd = read_fds[read_index].get();
   182  
   183      const int write_index = (i + 1) % num_processes;
   184      const int write_fd = write_fds[write_index].get();
   185  
   186      // std::vector isn't safe to use from the fork child.
   187      FileDescriptor* read_array = read_fds.data();
   188      FileDescriptor* write_array = write_fds.data();
   189  
   190      pid_t child = fork();
   191      if (!child) {
   192        // Close all other FDs.
   193        for (int j = 0; j < num_processes; j++) {
   194          if (j != read_index) {
   195            read_array[j].reset();
   196          }
   197          if (j != write_index) {
   198            write_array[j].reset();
   199          }
   200        }
   201  
   202        SwitchChild(read_fd, write_fd);
   203        _exit(0);
   204      }
   205      ASSERT_THAT(child, SyscallSucceeds());
   206      children.push_back(child);
   207    }
   208  
   209    // Read from current pipe index (0), write to next (1).
   210    const int read_index = 0;
   211    const int read_fd = read_fds[read_index].get();
   212  
   213    const int write_index = 1;
   214    const int write_fd = write_fds[write_index].get();
   215  
   216    // Kick start the loop.
   217    char buf = 'a';
   218    ASSERT_THAT(WriteFd(write_fd, &buf, 1), SyscallSucceedsWithValue(1));
   219  
   220    for (auto _ : state) {
   221      ASSERT_THAT(ReadFd(read_fd, &buf, 1), SyscallSucceedsWithValue(1));
   222      ASSERT_THAT(WriteFd(write_fd, &buf, 1), SyscallSucceedsWithValue(1));
   223    }
   224  }
   225  
   226  BENCHMARK(BM_ProcessSwitch)->Range(2, 16)->UseRealTime();
   227  
   228  // Equivalent to BM_ThreadSwitch using threads instead of processes.
   229  void BM_ThreadSwitch(benchmark::State& state) {
   230    // Code below assumes there are at least two threads.
   231    const int num_threads = state.range(0);
   232    ASSERT_GE(num_threads, 2);
   233  
   234    // Must come after threads, as the FDs must be closed before the children
   235    // will exit.
   236    std::vector<std::unique_ptr<ScopedThread>> threads;
   237    std::vector<FileDescriptor> read_fds;
   238    std::vector<FileDescriptor> write_fds;
   239  
   240    for (int i = 0; i < num_threads; i++) {
   241      int fds[2];
   242      ASSERT_THAT(pipe(fds), SyscallSucceeds());
   243      read_fds.emplace_back(fds[0]);
   244      write_fds.emplace_back(fds[1]);
   245    }
   246  
   247    // This thread is one of the threads in the loop. It will be considered
   248    // index 0.
   249    for (int i = 1; i < num_threads; i++) {
   250      // Read from current pipe index, write to next.
   251      //
   252      // Transfer ownership of the FDs to the thread.
   253      const int read_index = i;
   254      const int read_fd = read_fds[read_index].release();
   255  
   256      const int write_index = (i + 1) % num_threads;
   257      const int write_fd = write_fds[write_index].release();
   258  
   259      threads.emplace_back(std::make_unique<ScopedThread>([read_fd, write_fd] {
   260        FileDescriptor read(read_fd);
   261        FileDescriptor write(write_fd);
   262        SwitchChild(read.get(), write.get());
   263      }));
   264    }
   265  
   266    // Read from current pipe index (0), write to next (1).
   267    const int read_index = 0;
   268    const int read_fd = read_fds[read_index].get();
   269  
   270    const int write_index = 1;
   271    const int write_fd = write_fds[write_index].get();
   272  
   273    // Kick start the loop.
   274    char buf = 'a';
   275    ASSERT_THAT(WriteFd(write_fd, &buf, 1), SyscallSucceedsWithValue(1));
   276  
   277    for (auto _ : state) {
   278      ASSERT_THAT(ReadFd(read_fd, &buf, 1), SyscallSucceedsWithValue(1));
   279      ASSERT_THAT(WriteFd(write_fd, &buf, 1), SyscallSucceedsWithValue(1));
   280    }
   281  
   282    // The two FDs still owned by this thread are closed, causing the next thread
   283    // to exit its loop and close its FDs, and so on until all threads exit.
   284  }
   285  
   286  BENCHMARK(BM_ThreadSwitch)->Range(2, 16)->UseRealTime();
   287  
   288  void BM_ThreadStart(benchmark::State& state) {
   289    const int num_threads = state.range(0);
   290  
   291    for (auto _ : state) {
   292      state.PauseTiming();
   293  
   294      auto barrier = new absl::Barrier(num_threads + 1);
   295      std::vector<std::unique_ptr<ScopedThread>> threads;
   296  
   297      state.ResumeTiming();
   298  
   299      for (int i = 0; i < num_threads; ++i) {
   300        threads.emplace_back(std::make_unique<ScopedThread>([barrier] {
   301          if (barrier->Block()) {
   302            delete barrier;
   303          }
   304        }));
   305      }
   306  
   307      if (barrier->Block()) {
   308        delete barrier;
   309      }
   310  
   311      state.PauseTiming();
   312  
   313      for (const auto& thread : threads) {
   314        thread->Join();
   315      }
   316  
   317      state.ResumeTiming();
   318    }
   319  }
   320  
   321  BENCHMARK(BM_ThreadStart)->Range(1, 2048)->UseRealTime();
   322  
   323  // Benchmark the complete fork + exit + wait.
   324  void BM_ProcessLifecycle(benchmark::State& state) {
   325    const int num_procs = state.range(0);
   326  
   327    std::vector<pid_t> pids(num_procs);
   328    for (auto _ : state) {
   329      for (int i = 0; i < num_procs; ++i) {
   330        int pid = fork();
   331        if (pid == 0) {
   332          _exit(0);
   333        }
   334        ASSERT_THAT(pid, SyscallSucceeds());
   335        pids[i] = pid;
   336      }
   337  
   338      for (const int pid : pids) {
   339        ASSERT_THAT(RetryEINTR(waitpid)(pid, nullptr, 0),
   340                    SyscallSucceedsWithValue(pid));
   341      }
   342    }
   343  }
   344  
   345  BENCHMARK(BM_ProcessLifecycle)->Range(1, 512)->UseRealTime();
   346  
   347  }  // namespace
   348  
   349  }  // namespace testing
   350  }  // namespace gvisor