blob: 9746934df85f0d4171263d4ff35f88c92e32fe72 [file] [log] [blame] [edit]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/zx/vmar.h>
#include <lib/zx/vmo.h>
#include <vector>
#include <fbl/string_printf.h>
#include <perftest/perftest.h>
#include "assert.h"
namespace {
// Measure the time taken to write or read a chunk of data to/from a VMO
// using the zx_vmo_write() or zx_vmo_read() syscalls respectively.
bool VmoReadOrWriteTest(perftest::RepeatState* state, uint32_t copy_size, bool do_write) {
state->SetBytesProcessedPerRun(copy_size);
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(copy_size, 0, &vmo));
std::vector<char> buffer(copy_size);
// Write the buffer so that the pages are pre-committed. This matters
// more for the read case.
ASSERT_OK(vmo.write(buffer.data(), 0, copy_size));
if (do_write) {
while (state->KeepRunning()) {
ASSERT_OK(vmo.write(buffer.data(), 0, copy_size));
}
} else {
while (state->KeepRunning()) {
ASSERT_OK(vmo.read(buffer.data(), 0, copy_size));
}
}
return true;
}
// Measure the time taken to write or read a chunk of data to/from a mapped VMO. The writing/reading
// is either done from userland using memcpy() (when user_memcpy=true) or by the kernel using
// zx_vmo_read()/zx_vmo_write() (when user_memcpy=false).
bool VmoReadOrWriteMapTestImpl(perftest::RepeatState* state, uint32_t copy_size, bool do_write,
int flags, bool user_memcpy) {
state->SetBytesProcessedPerRun(copy_size);
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(copy_size, 0, &vmo));
std::vector<char> buffer(copy_size);
zx_vaddr_t mapped_addr;
zx::vmo vmo_buf;
if (!user_memcpy) {
// Create a temporary VMO that we can use to get the kernel to read/write our mapped memory.
ASSERT_OK(zx::vmo::create(copy_size, 0, &vmo_buf));
}
// Write the buffer so that the pages are pre-committed. This matters
// more for the read case.
ASSERT_OK(vmo.write(buffer.data(), 0, copy_size));
if (do_write) {
while (state->KeepRunning()) {
ASSERT_OK(zx::vmar::root_self()->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | flags, 0, vmo, 0,
copy_size, &mapped_addr));
if (user_memcpy) {
std::memcpy(reinterpret_cast<void*>(mapped_addr), buffer.data(), copy_size);
} else {
// To write to the mapped in portion we *read* from the temporary VMO.
ASSERT_OK(vmo_buf.read(reinterpret_cast<void*>(mapped_addr), 0, copy_size));
}
ASSERT_OK(zx::vmar::root_self()->unmap(mapped_addr, copy_size));
}
} else { // read
while (state->KeepRunning()) {
ASSERT_OK(zx::vmar::root_self()->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | flags, 0, vmo, 0,
copy_size, &mapped_addr));
if (user_memcpy) {
std::memcpy(buffer.data(), reinterpret_cast<void*>(mapped_addr), copy_size);
} else {
// To read from the mapped in portion we *write* it to the temporary VMO.
ASSERT_OK(vmo_buf.write(reinterpret_cast<void*>(mapped_addr), 0, copy_size));
}
ASSERT_OK(zx::vmar::root_self()->unmap(mapped_addr, copy_size));
}
}
return true;
}
bool VmoReadOrWriteMapTest(perftest::RepeatState* state, uint32_t copy_size, bool do_write,
bool user_memcpy) {
return VmoReadOrWriteMapTestImpl(state, copy_size, do_write, 0, user_memcpy);
}
bool VmoReadOrWriteMapRangeTest(perftest::RepeatState* state, uint32_t copy_size, bool do_write,
bool user_memcpy) {
return VmoReadOrWriteMapTestImpl(state, copy_size, do_write, ZX_VM_MAP_RANGE, user_memcpy);
}
// Measure the time taken to clone a vmo and destroy it. If map_size is non zero,
// then this function tests the case where the original vmo is mapped in chunks
// of map_size; otherwise it tests the case where the original vmo is not mapped.
bool VmoCloneTest(perftest::RepeatState* state, uint32_t copy_size, uint32_t map_size) {
if (map_size > 0) {
state->DeclareStep("map");
}
state->DeclareStep("clone");
state->DeclareStep("close");
if (map_size > 0) {
state->DeclareStep("unmap");
}
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(copy_size, 0, &vmo));
ASSERT_OK(vmo.op_range(ZX_VMO_OP_COMMIT, 0, copy_size, nullptr, 0));
zx::vmar vmar;
zx_vaddr_t addr = 0;
// Allocate a single vmar so we have a single reserved block if mapping in using multiple chunks.
ASSERT_OK(zx::vmar::root_self()->allocate2(
ZX_VM_CAN_MAP_SPECIFIC | ZX_VM_CAN_MAP_READ | ZX_VM_CAN_MAP_WRITE, 0, copy_size, &vmar,
&addr));
while (state->KeepRunning()) {
if (map_size > 0) {
zx_vaddr_t chunk_addr;
for (uint32_t off = 0; off < copy_size; off += map_size) {
ASSERT_OK(vmar.map(ZX_VM_MAP_RANGE | ZX_VM_PERM_READ | ZX_VM_SPECIFIC, off, vmo, off,
map_size, &chunk_addr));
}
state->NextStep();
}
zx::vmo clone;
ASSERT_OK(vmo.create_child(ZX_VMO_CHILD_COPY_ON_WRITE, 0, copy_size, &clone));
state->NextStep();
clone.reset();
if (map_size > 0) {
state->NextStep();
ASSERT_OK(vmar.unmap(addr, copy_size));
}
}
return true;
}
// Measure the time it takes to clone a vmo. Specifically, this measures:
// - Clone a vmo.
// - Read or write either the original vmo (do_target_clone=false) or the
// clone (do_target_clone=true).
// - For bidirectional clones, we don't expect varying do_target_clone to
// significantly affect this performance.
// - do_full_op controls whether we read or write the whole vmo or just
// a subset of the pages, as the performance characteristics of a
// partially populated clone and a fully populated clone can differ.
// - Destroy the clone.
bool VmoCloneReadOrWriteTest(perftest::RepeatState* state, uint32_t copy_size, bool do_write,
bool do_target_clone, bool do_full_op) {
state->DeclareStep("clone");
state->DeclareStep(do_write ? "write" : "read");
state->DeclareStep("close");
state->SetBytesProcessedPerRun(copy_size);
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(copy_size, 0, &vmo));
ASSERT_OK(vmo.op_range(ZX_VMO_OP_COMMIT, 0, copy_size, nullptr, 0));
std::vector<char> buffer(copy_size);
while (state->KeepRunning()) {
zx::vmo clone;
ASSERT_OK(vmo.create_child(ZX_VMO_CHILD_COPY_ON_WRITE, 0, copy_size, &clone));
state->NextStep();
const zx::vmo& target = do_target_clone ? clone : vmo;
if (do_full_op) {
if (do_write) {
ASSERT_OK(target.write(buffer.data(), 0, copy_size));
} else {
ASSERT_OK(target.read(buffer.data(), 0, copy_size));
}
} else {
// There's no special meaning behind the particular value of this
// constant. It just needs to result in a couple of writes into
// the vmo without populating it too densely.
static constexpr uint64_t kWriteInterval = 8 * ZX_PAGE_SIZE;
for (uint64_t offset = 0; offset < copy_size; offset += kWriteInterval) {
if (do_write) {
ASSERT_OK(target.write(buffer.data(), offset, PAGE_SIZE));
} else {
ASSERT_OK(target.read(buffer.data(), offset, PAGE_SIZE));
}
}
}
state->NextStep();
// The clone goes out of scope and is implicitly closed.
}
return true;
}
template <typename Func, typename... Args>
void RegisterVmoTest(const char* name, Func fn, Args... args) {
for (unsigned size_in_kbytes : {128, 512, 2048}) {
auto full_name = fbl::StringPrintf("%s/%ukbytes", name, size_in_kbytes);
perftest::RegisterTest(full_name.c_str(), fn, size_in_kbytes * 1024, args...);
}
}
void RegisterTests() {
for (bool do_write : {false, true}) {
const char* rw = do_write ? "Write" : "Read";
auto rw_name = fbl::StringPrintf("Vmo/%s", rw);
RegisterVmoTest(rw_name.c_str(), VmoReadOrWriteTest, do_write);
}
for (bool do_write : {false, true}) {
for (bool user_memcpy : {false, true}) {
const char* rw = do_write ? "Write" : "Read";
const char* user_kernel = user_memcpy ? "" : "/Kernel";
auto rw_name = fbl::StringPrintf("VmoMap/%s%s", rw, user_kernel);
RegisterVmoTest(rw_name.c_str(), VmoReadOrWriteMapTest, do_write, user_memcpy);
rw_name = fbl::StringPrintf("VmoMapRange/%s%s", rw, user_kernel);
RegisterVmoTest(rw_name.c_str(), VmoReadOrWriteMapRangeTest, do_write, user_memcpy);
}
}
for (bool map : {false, true}) {
auto clone_name = fbl::StringPrintf("Vmo/Clone%s", map ? "Map" : "");
RegisterVmoTest(clone_name.c_str(), [map](perftest::RepeatState* state, uint32_t size) {
return VmoCloneTest(state, size, map ? size : 0);
});
}
for (unsigned map_chunk_kb : {4, 64, 2048, 32768}) {
constexpr uint32_t vmo_size_kb = 32768;
auto name = fbl::StringPrintf("Vmo/CloneMap%usegments/%ukbytes", vmo_size_kb / map_chunk_kb,
vmo_size_kb);
perftest::RegisterTest(name.c_str(), VmoCloneTest, vmo_size_kb * 1024, map_chunk_kb * 1024);
}
for (bool do_write : {false, true}) {
for (bool do_target_clone : {false, true}) {
for (bool do_full_op : {false, true}) {
const char* rw = do_write ? "Write" : "Read";
const char* target = do_target_clone ? "Clone" : "Orig";
const char* density = do_full_op ? "All" : "Some";
auto clone_rw_name = fbl::StringPrintf("Vmo/Clone/%s%s%s", rw, target, density);
RegisterVmoTest(clone_rw_name.c_str(), VmoCloneReadOrWriteTest, do_write, do_target_clone,
do_full_op);
}
}
}
}
PERFTEST_CTOR(RegisterTests);
} // namespace