blob: 4d8af8ced8405e2fe5257b8d83169109d669ef15 [file] [log] [blame]
// Copyright 2020 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 "cpu_workloads.h"
#include <zircon/assert.h>
#include <zircon/compiler.h>
#include <cfloat>
#include <random>
#include <vector>
#include "compiler.h"
#include "cpu_stressor.h"
#include "util.h"
namespace hwstress {
namespace {
// Assert that the given condition is true.
//
// The error string should express the error.
//
// We use a macro to avoid having to evaluate the arguments in the
// (expected case) that the condition is true.
#define AssertThat(workload, condition, ...) \
do { \
if (unlikely(!(condition))) { \
fprintf(stderr, \
"\n" \
"*** FAILURE ***\n" \
"\n" \
"Workload '%s' CPU calculation failed:\n\n", \
workload); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, \
"\n" \
"This failure may indicate faulty hardware.\n\n" \
"\n"); \
fflush(stderr); \
ZX_PANIC("CPU calculation failed: possible hardware fault.\n"); \
} \
} while (false)
// Assert that the given values are equal, within an |epsilon| error.
void AssertEqual(const char* workload, double expected, double actual, double epsilon = 0.0) {
AssertThat(workload, std::abs(expected - actual) <= epsilon,
" Expected: %.17g (%s)\n"
" Actual: %.17g (%s)\n"
" Difference: %.17g > %.17g (***)\n",
expected, DoubleAsHex(expected).c_str(), actual, DoubleAsHex(actual).c_str(),
std::abs(expected - actual), epsilon);
}
// Assert that the given uint64_t's are equal.
void AssertEqual(const char* workload, uint64_t expected, uint64_t actual) {
AssertThat(workload, expected == actual,
" Expected: %20ld (%#016lx)\n"
" Actual: %20ld (%#016lx)\n",
expected, expected, actual, actual);
}
// Workload interface.
class Workload {
public:
virtual ~Workload() = default;
virtual void DoWork() = 0;
};
//
// The actual workloads.
//
// |memset| a small amount of memory.
//
// |memset| tends to be highly optimised for using all available memory
// bandwidth. We use a small enough buffer size to avoid spilling out
// of L1 cache.
class MemsetWorkload final : public Workload {
public:
MemsetWorkload() : memory_(std::make_unique<uint8_t[]>(kBufferSize)) {}
void DoWork() final {
memset(memory_.get(), 0xaa, kBufferSize);
HideMemoryFromCompiler(memory_.get());
memset(memory_.get(), 0x55, kBufferSize);
HideMemoryFromCompiler(memory_.get());
}
private:
std::unique_ptr<uint8_t[]> memory_;
static constexpr int kBufferSize = 8192;
};
// Calculate the trigonometric identity sin(x)**2 + cos(x)**2 == 1
// in a tight loop.
//
// Exercises floating point operations on the CPU, though mostly within
// the |sin| and |cos| functions.
class SinCosWorkload final : public Workload {
public:
void DoWork() final {
constexpr int kIterations = 10000;
double result = 0;
UNROLL_LOOP_4
for (int i = 0; i < kIterations; i++) {
// Calculate "sin(x)**2 + cos(x)**2", which is always "1.0". Hide
// the input from the compiler to prevent it pre-calculating
// anything.
double input = HideFromCompiler(i);
double a = sin(input);
double b = cos(input);
result += a * a + b * b;
}
AssertEqual("trigonometry", kIterations, result, DBL_EPSILON * kIterations);
}
};
// Calculate the n'th Fibonacci number using inefficient recursion.
uint64_t Fibonacci(uint64_t n) {
if (n <= 1)
return n;
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
// Calculate the Fibonacci sequence using recursion.
//
// Exercises call/return control flow.
class FibonacciWorkload final : public Workload {
public:
void DoWork() final {
uint64_t result = Fibonacci(HideFromCompiler(30));
AssertEqual("fibonacci", 832040, result);
}
};
// Perform a 16*16 matrix multiplication using floats.
//
// Exercises floating point operations.
class MatrixMultiplicationWorkload final : public Workload {
public:
MatrixMultiplicationWorkload() {
// Create a permutation matrix.
for (int i = 0; i < kSize; i++) {
permutation_.m[i][kSize - i - 1] = 1.0;
}
// Create a random matrix.
std::mt19937_64 generator{};
std::uniform_real_distribution<> dist(-1.0, 1.0);
for (int x = 0; x < kSize; x++) {
for (int y = 0; y < kSize; y++) {
random_.m[x][y] = dist(generator);
}
}
}
void DoWork() final {
// Multiply it 1000 times.
Matrix active = random_;
for (int n = 0; n < 1'000; n++) {
// Naïve matrix multiplication algorithm.
Matrix prev = active;
for (int x = 0; x < kSize; x++) {
for (int y = 0; y < kSize; y++) {
float r = 0;
for (int i = 0; i < kSize; i++) {
r += prev.m[i][y] * permutation_.m[x][i];
}
active.m[x][y] = r;
}
}
}
// Ensure the final result matches our random_ matrix.
for (int x = 0; x < kSize; x++) {
for (int y = 0; y < kSize; y++) {
AssertEqual("matrix", active.m[x][y], random_.m[x][y], /*epsilon=*/0.0);
}
}
}
private:
static constexpr int kSize = 16;
struct Matrix {
float m[kSize][kSize];
};
Matrix permutation_{};
Matrix random_{};
};
// Run the Mersenne Twister random number generator algorithm.
//
// This exercises integer bitwise operation and multiplication.
class MersenneTwisterWorkload final : public Workload {
public:
void DoWork() final {
std::mt19937_64 generator{};
// Iterate the generator.
uint64_t v;
UNROLL_LOOP_4
for (int i = 0; i < 10'000; i++) {
v = generator();
}
// The C++11 standard states that the 10,000th consecutive
// invocation of the mt19937_64 should be the following value.
AssertEqual("mersenne", v, 0x8a85'92f5'817e'd872);
}
};
// Run a mixed of the other workloads.
class MixedWorkload final : public Workload {
public:
void DoWork() final {
fibonacci_.DoWork();
matrix_.DoWork();
memset_.DoWork();
mersenee_.DoWork();
trigonometry_.DoWork();
}
private:
FibonacciWorkload fibonacci_;
MatrixMultiplicationWorkload matrix_;
MemsetWorkload memset_;
MersenneTwisterWorkload mersenee_;
SinCosWorkload trigonometry_;
};
// Convert the given Workload into a CpuWorkload.
template <typename W>
auto IterateWorkload() -> auto {
return [](WorkIndicator indicator) {
W workload{};
do {
workload.DoWork();
} while (!indicator.ShouldStop());
};
}
} // namespace
std::vector<CpuWorkload> GetCpuWorkloads() {
return std::vector<CpuWorkload>{{"fibonacci", IterateWorkload<FibonacciWorkload>()},
{"matrix", IterateWorkload<MatrixMultiplicationWorkload>()},
{"memset", IterateWorkload<MemsetWorkload>()},
{"mersenne", IterateWorkload<MersenneTwisterWorkload>()},
{"trigonometry", IterateWorkload<SinCosWorkload>()},
{"mixed", IterateWorkload<MixedWorkload>()}};
}
} // namespace hwstress