cachelib/cachebench/workload/WorkloadGenerator.cpp (148 lines of code) (raw):

/* * Copyright (c) Facebook, Inc. and its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "cachelib/cachebench/workload/WorkloadGenerator.h" #include <algorithm> #include <chrono> #include <iostream> namespace facebook { namespace cachelib { namespace cachebench { WorkloadGenerator::WorkloadGenerator(const StressorConfig& config) : config_{config} { for (const auto& c : config.poolDistributions) { if (c.keySizeRange.size() != c.keySizeRangeProbability.size() + 1) { throw std::invalid_argument( "Key size range and their probabilities do not match up. Check your " "test config."); } workloadDist_.push_back(WorkloadDistribution(c)); } if (config_.numKeys > std::numeric_limits<uint32_t>::max()) { throw std::invalid_argument(folly::sformat( "Too many keys specified: {}. Maximum allowed is 4 Billion.", config_.numKeys)); } generateReqs(); generateKeyDistributions(); } const Request& WorkloadGenerator::getReq(uint8_t poolId, std::mt19937_64& gen, std::optional<uint64_t>) { XDCHECK_LT(poolId, keyIndicesForPool_.size()); XDCHECK_LT(poolId, keyGenForPool_.size()); size_t idx = keyIndicesForPool_[poolId][keyGenForPool_[poolId](gen)]; auto op = static_cast<OpType>(workloadDist_[workloadIdx(poolId)].sampleOpDist(gen)); reqs_[idx].setOp(op); return reqs_[idx]; } void WorkloadGenerator::generateKeys() { uint8_t pid = 0; auto fn = [pid, this](size_t start, size_t end) { // All keys are printable lower case english alphabet. std::uniform_int_distribution<char> charDis('a', 'z'); std::mt19937_64 gen(folly::Random::rand64()); for (uint64_t i = start; i < end; i++) { size_t keySize = util::narrow_cast<size_t>(workloadDist_[pid].sampleKeySizeDist(gen)); keys_[i].resize(keySize); for (auto& c : keys_[i]) { c = charDis(gen); } } }; size_t totalKeys(0); std::chrono::seconds keyGenDuration(0); keys_.resize(config_.numKeys); for (size_t i = 0; i < config_.keyPoolDistribution.size(); i++) { pid = util::narrow_cast<uint8_t>(workloadIdx(i)); size_t numKeysForPool = firstKeyIndexForPool_[i + 1] - firstKeyIndexForPool_[i]; totalKeys += numKeysForPool; keyGenDuration += detail::executeParallel( fn, config_.numThreads, numKeysForPool, firstKeyIndexForPool_[i]); } auto startTime = std::chrono::steady_clock::now(); for (size_t i = 0; i < config_.keyPoolDistribution.size(); i++) { auto poolKeyBegin = keys_.begin() + firstKeyIndexForPool_[i]; // past the end iterator auto poolKeyEnd = keys_.begin() + (firstKeyIndexForPool_[i + 1]); std::sort(poolKeyBegin, poolKeyEnd); auto newEnd = std::unique(poolKeyBegin, poolKeyEnd); // update pool key boundary before invalidating iterators for (size_t j = i + 1; j < firstKeyIndexForPool_.size(); j++) { firstKeyIndexForPool_[j] -= std::distance(newEnd, poolKeyEnd); } totalKeys -= std::distance(newEnd, poolKeyEnd); keys_.erase(newEnd, poolKeyEnd); } auto sortDuration = std::chrono::duration_cast<std::chrono::seconds>( std::chrono::steady_clock::now() - startTime); std::cout << folly::sformat("Created {:,} keys in {:.2f} mins", totalKeys, (keyGenDuration + sortDuration).count() / 60.) << std::endl; } void WorkloadGenerator::generateReqs() { generateFirstKeyIndexForPool(); generateKeys(); std::mt19937_64 gen(folly::Random::rand64()); for (size_t i = 0; i < config_.keyPoolDistribution.size(); i++) { size_t idx = workloadIdx(i); for (size_t j = firstKeyIndexForPool_[i]; j < firstKeyIndexForPool_[i + 1]; j++) { std::vector<size_t> chainSizes; chainSizes.push_back( util::narrow_cast<size_t>(workloadDist_[idx].sampleValDist(gen))); int chainLen = util::narrow_cast<int>(workloadDist_[idx].sampleChainedLenDist(gen)); for (int k = 0; k < chainLen; k++) { chainSizes.push_back(util::narrow_cast<size_t>( workloadDist_[idx].sampleChainedValDist(gen))); } sizes_.emplace_back(chainSizes); auto reqSizes = sizes_.end() - 1; reqs_.emplace_back(keys_[j], reqSizes->begin(), reqSizes->end()); } } } void WorkloadGenerator::generateFirstKeyIndexForPool() { auto sumProb = std::accumulate(config_.keyPoolDistribution.begin(), config_.keyPoolDistribution.end(), 0.); auto accumProb = 0.; firstKeyIndexForPool_.push_back(0); for (auto prob : config_.keyPoolDistribution) { accumProb += prob; firstKeyIndexForPool_.push_back( util::narrow_cast<uint32_t>(config_.numKeys * accumProb / sumProb)); } } void WorkloadGenerator::generateKeyDistributions() { // We are trying to generate a gaussian distribution for each pool's part // in the overall cache ops. To keep the amount of memory finite, we only // generate a max of 4 billion op traces across all the pools and replay // the same when we need longer traces. std::chrono::seconds duration{0}; for (uint64_t i = 0; i < config_.opPoolDistribution.size(); i++) { auto left = firstKeyIndexForPool_[i]; auto right = firstKeyIndexForPool_[i + 1] - 1; size_t idx = workloadIdx(i); size_t numOpsForPool = std::min<size_t>( util::narrow_cast<size_t>(config_.numOps * config_.numThreads * config_.opPoolDistribution[i]), std::numeric_limits<uint32_t>::max()); std::cout << folly::sformat("Generating {:.2f}M sampled accesses", numOpsForPool / 1e6) << std::endl; keyGenForPool_.push_back(std::uniform_int_distribution<uint32_t>( 0, util::narrow_cast<uint32_t>(numOpsForPool) - 1)); keyIndicesForPool_.push_back(std::vector<uint32_t>(numOpsForPool)); duration += detail::executeParallel( [&, this](size_t start, size_t end) { std::mt19937_64 gen(folly::Random::rand64()); auto popDist = workloadDist_[idx].getPopDist(left, right); for (uint64_t j = start; j < end; j++) { keyIndicesForPool_[i][j] = util::narrow_cast<uint32_t>((*popDist)(gen)); } }, config_.numThreads, numOpsForPool); } std::cout << folly::sformat("Generated access patterns in {:.2f} mins", duration.count() / 60.) << std::endl; } } // namespace cachebench } // namespace cachelib } // namespace facebook