333209fa-0481-4a47-8399-e46cdc73ea81
This commit is contained in:
449
benchmarks/core/type_system_bench.cpp
Normal file
449
benchmarks/core/type_system_bench.cpp
Normal file
@@ -0,0 +1,449 @@
|
||||
#include <benchmark/benchmark.h>
|
||||
#include "types.h"
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
|
||||
namespace PokemonSim {
|
||||
|
||||
// Benchmark fixture for type system
|
||||
class TypeSystemBenchmark : public benchmark::Fixture {
|
||||
public:
|
||||
void SetUp(const benchmark::State& /*state*/) override {
|
||||
// Set up common test data
|
||||
SetupTestData();
|
||||
}
|
||||
|
||||
void TearDown(const benchmark::State& /*state*/) override {
|
||||
// Clean up
|
||||
}
|
||||
|
||||
void SetupTestData() {
|
||||
// Create a variety of Pokemon types for testing
|
||||
singleTypePokemon = {
|
||||
PokemonTypes(Type::FIRE),
|
||||
PokemonTypes(Type::WATER),
|
||||
PokemonTypes(Type::GRASS),
|
||||
PokemonTypes(Type::ELECTRIC),
|
||||
PokemonTypes(Type::NORMAL),
|
||||
PokemonTypes(Type::FIGHTING),
|
||||
PokemonTypes(Type::POISON),
|
||||
PokemonTypes(Type::GROUND),
|
||||
PokemonTypes(Type::FLYING),
|
||||
PokemonTypes(Type::PSYCHIC)
|
||||
};
|
||||
|
||||
dualTypePokemon = {
|
||||
PokemonTypes(Type::FIRE, Type::FLYING), // Charizard
|
||||
PokemonTypes(Type::WATER, Type::POISON), // Tentacruel
|
||||
PokemonTypes(Type::GRASS, Type::POISON), // Venusaur
|
||||
PokemonTypes(Type::ELECTRIC, Type::STEEL), // Magnezone
|
||||
PokemonTypes(Type::NORMAL, Type::FLYING), // Pidgeot
|
||||
PokemonTypes(Type::FIGHTING, Type::DARK), // Lucario
|
||||
PokemonTypes(Type::POISON, Type::DARK), // Drapion
|
||||
PokemonTypes(Type::GROUND, Type::ROCK), // Rhyperior
|
||||
PokemonTypes(Type::FLYING, Type::DRAGON), // Dragonite
|
||||
PokemonTypes(Type::PSYCHIC, Type::FAIRY) // Gardevoir
|
||||
};
|
||||
|
||||
// Create attack types for testing
|
||||
attackTypes = {
|
||||
Type::NORMAL, Type::FIRE, Type::WATER, Type::ELECTRIC, Type::GRASS,
|
||||
Type::ICE, Type::FIGHTING, Type::POISON, Type::GROUND, Type::FLYING,
|
||||
Type::PSYCHIC, Type::BUG, Type::ROCK, Type::GHOST, Type::DRAGON,
|
||||
Type::DARK, Type::STEEL, Type::FAIRY
|
||||
};
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<PokemonTypes> singleTypePokemon;
|
||||
std::vector<PokemonTypes> dualTypePokemon;
|
||||
std::vector<Type> attackTypes;
|
||||
};
|
||||
|
||||
// Benchmark type string conversion performance
|
||||
BENCHMARK_F(TypeSystemBenchmark, BM_StringToTypeConversion)(benchmark::State& state) {
|
||||
std::vector<std::string> typeStrings = {"fire", "water", "grass", "electric", "normal", "fighting", "poison"};
|
||||
|
||||
for (auto _ : state) {
|
||||
for (const auto& typeStr : typeStrings) {
|
||||
auto result = TypeUtils::stringToType(typeStr);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(TypeSystemBenchmark, BM_TypeToStringConversion)(benchmark::State& state) {
|
||||
for (auto _ : state) {
|
||||
for (size_t i = 0; i < attackTypes.size(); ++i) {
|
||||
auto result = TypeUtils::typeToString(attackTypes[i % attackTypes.size()]);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark type effectiveness lookup performance
|
||||
BENCHMARK_F(TypeSystemBenchmark, BM_TypeEffectivenessLookup)(benchmark::State& state) {
|
||||
for (auto _ : state) {
|
||||
for (size_t i = 0; i < attackTypes.size(); ++i) {
|
||||
for (size_t j = 0; j < attackTypes.size(); ++j) {
|
||||
auto result = TypeUtils::getTypeEffectiveness<Generation::GENERATION_1>(
|
||||
attackTypes[i % attackTypes.size()],
|
||||
attackTypes[j % attackTypes.size()]
|
||||
);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(TypeSystemBenchmark, BM_TypeEffectivenessLookupGen8)(benchmark::State& state) {
|
||||
for (auto _ : state) {
|
||||
for (size_t i = 0; i < attackTypes.size(); ++i) {
|
||||
for (size_t j = 0; j < attackTypes.size(); ++j) {
|
||||
auto result = TypeUtils::getTypeEffectiveness<Generation::GENERATION_8>(
|
||||
attackTypes[i % attackTypes.size()],
|
||||
attackTypes[j % attackTypes.size()]
|
||||
);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark damage multiplier calculation for single-type Pokemon
|
||||
BENCHMARK_F(TypeSystemBenchmark, BM_DamageMultiplierSingleType)(benchmark::State& state) {
|
||||
for (auto _ : state) {
|
||||
for (const auto& pokemon : singleTypePokemon) {
|
||||
for (const auto& attackType : attackTypes) {
|
||||
auto result = TypeUtils::calculateDamageMultiplier<Generation::GENERATION_1>(attackType, pokemon);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark damage multiplier calculation for dual-type Pokemon
|
||||
BENCHMARK_F(TypeSystemBenchmark, BM_DamageMultiplierDualType)(benchmark::State& state) {
|
||||
for (auto _ : state) {
|
||||
for (const auto& pokemon : dualTypePokemon) {
|
||||
for (const auto& attackType : attackTypes) {
|
||||
auto result = TypeUtils::calculateDamageMultiplier<Generation::GENERATION_1>(attackType, pokemon);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark the high-level damage calculation function
|
||||
BENCHMARK_F(TypeSystemBenchmark, BM_CalculateDamage)(benchmark::State& state) {
|
||||
const uint32_t baseDamage = 100;
|
||||
|
||||
for (auto _ : state) {
|
||||
for (const auto& pokemon : singleTypePokemon) {
|
||||
for (const auto& attackType : attackTypes) {
|
||||
auto result = calculateDamage<Generation::GENERATION_1>(baseDamage, attackType, pokemon);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(TypeSystemBenchmark, BM_CalculateDamageDualType)(benchmark::State& state) {
|
||||
const uint32_t baseDamage = 100;
|
||||
|
||||
for (auto _ : state) {
|
||||
for (const auto& pokemon : dualTypePokemon) {
|
||||
for (const auto& attackType : attackTypes) {
|
||||
auto result = calculateDamage<Generation::GENERATION_1>(baseDamage, attackType, pokemon);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark different generations
|
||||
BENCHMARK_F(TypeSystemBenchmark, BM_Generation1DamageCalc)(benchmark::State& state) {
|
||||
const uint32_t baseDamage = 100;
|
||||
PokemonTypes pokemon(Type::FIRE, Type::FLYING);
|
||||
|
||||
for (auto _ : state) {
|
||||
for (const auto& attackType : attackTypes) {
|
||||
auto result = calculateDamage<Generation::GENERATION_1>(baseDamage, attackType, pokemon);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(TypeSystemBenchmark, BM_Generation8DamageCalc)(benchmark::State& state) {
|
||||
const uint32_t baseDamage = 100;
|
||||
PokemonTypes pokemon(Type::FIRE, Type::FLYING);
|
||||
|
||||
for (auto _ : state) {
|
||||
for (const auto& attackType : attackTypes) {
|
||||
auto result = calculateDamage<Generation::GENERATION_8>(baseDamage, attackType, pokemon);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark PokemonTypes operations
|
||||
BENCHMARK_F(TypeSystemBenchmark, BM_PokemonTypesOperations)(benchmark::State& state) {
|
||||
for (auto _ : state) {
|
||||
PokemonTypes pokemon(Type::FIRE);
|
||||
|
||||
// Test setting types
|
||||
pokemon.setPrimary(Type::WATER);
|
||||
pokemon.setSecondary(Type::FLYING);
|
||||
|
||||
// Test getting types
|
||||
auto primary = pokemon.getPrimary();
|
||||
auto secondary = pokemon.getSecondary();
|
||||
auto hasSecondary = pokemon.hasSecondary();
|
||||
|
||||
benchmark::DoNotOptimize(primary);
|
||||
benchmark::DoNotOptimize(secondary);
|
||||
benchmark::DoNotOptimize(hasSecondary);
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark batch processing (simulating real game scenarios)
|
||||
BENCHMARK_F(TypeSystemBenchmark, BM_BatchDamageCalculation)(benchmark::State& state) {
|
||||
const size_t batchSize = state.range(0);
|
||||
const uint32_t baseDamage = 100;
|
||||
|
||||
// Prepare batch data
|
||||
std::vector<std::tuple<Type, PokemonTypes>> batch;
|
||||
batch.reserve(batchSize);
|
||||
|
||||
for (size_t i = 0; i < batchSize; ++i) {
|
||||
Type attackType = attackTypes[i % attackTypes.size()];
|
||||
PokemonTypes defendTypes = (i % 2 == 0) ?
|
||||
singleTypePokemon[i % singleTypePokemon.size()] :
|
||||
dualTypePokemon[i % dualTypePokemon.size()];
|
||||
batch.emplace_back(attackType, defendTypes);
|
||||
}
|
||||
|
||||
for (auto _ : state) {
|
||||
uint32_t totalDamage = 0;
|
||||
for (const auto& [attackType, defendTypes] : batch) {
|
||||
uint32_t damage = calculateDamage<Generation::GENERATION_1>(baseDamage, attackType, defendTypes);
|
||||
totalDamage += damage;
|
||||
}
|
||||
benchmark::DoNotOptimize(totalDamage);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_REGISTER_F(TypeSystemBenchmark, BM_BatchDamageCalculation)
|
||||
->Arg(10) // Small batch
|
||||
->Arg(100) // Medium batch
|
||||
->Arg(1000) // Large batch
|
||||
->Unit(benchmark::kMicrosecond);
|
||||
|
||||
// Memory access pattern benchmark
|
||||
BENCHMARK_F(TypeSystemBenchmark, BM_TypeChartMemoryAccess)(benchmark::State& state) {
|
||||
// Test memory access patterns for type chart lookups
|
||||
const auto& chart = TypeChartTraits<Generation::GENERATION_1>::getTypeChart();
|
||||
|
||||
for (auto _ : state) {
|
||||
// Sequential access pattern
|
||||
for (size_t i = 0; i < chart.size(); ++i) {
|
||||
auto multiplier = chart[i];
|
||||
benchmark::DoNotOptimize(multiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(TypeSystemBenchmark, BM_TypeChartRandomAccess)(benchmark::State& state) {
|
||||
const auto& chart = TypeChartTraits<Generation::GENERATION_1>::getTypeChart();
|
||||
const size_t chartSize = chart.size();
|
||||
|
||||
// Generate random indices
|
||||
std::vector<size_t> randomIndices;
|
||||
randomIndices.reserve(1000);
|
||||
|
||||
std::mt19937 gen(42); // Fixed seed for reproducibility
|
||||
std::uniform_int_distribution<size_t> dist(0, chartSize - 1);
|
||||
|
||||
for (size_t i = 0; i < 1000; ++i) {
|
||||
randomIndices.push_back(dist(gen));
|
||||
}
|
||||
|
||||
for (auto _ : state) {
|
||||
// Random access pattern
|
||||
for (size_t idx : randomIndices) {
|
||||
auto multiplier = chart[idx];
|
||||
benchmark::DoNotOptimize(multiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cache-friendly vs cache-unfriendly access patterns
|
||||
BENCHMARK_F(TypeSystemBenchmark, BM_CacheFriendlyLookups)(benchmark::State& state) {
|
||||
// Cache-friendly: sequential type lookups
|
||||
for (auto _ : state) {
|
||||
for (size_t i = 0; i < attackTypes.size(); ++i) {
|
||||
for (size_t j = 0; j < attackTypes.size(); ++j) {
|
||||
// Predictable access pattern
|
||||
size_t attackIdx = i;
|
||||
size_t defendIdx = j;
|
||||
size_t index = attackIdx * static_cast<size_t>(Type::TYPE_COUNT) + defendIdx;
|
||||
|
||||
auto result = TypeUtils::getTypeEffectiveness<Generation::GENERATION_1>(
|
||||
attackTypes[attackIdx % attackTypes.size()],
|
||||
attackTypes[defendIdx % attackTypes.size()]
|
||||
);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(TypeSystemBenchmark, BM_CacheUnfriendlyLookups)(benchmark::State& state) {
|
||||
// Cache-unfriendly: random type lookups
|
||||
std::vector<std::pair<size_t, size_t>> randomPairs;
|
||||
randomPairs.reserve(1000);
|
||||
|
||||
std::mt19937 gen(42);
|
||||
std::uniform_int_distribution<size_t> dist(0, attackTypes.size() - 1);
|
||||
|
||||
for (size_t i = 0; i < 1000; ++i) {
|
||||
randomPairs.emplace_back(dist(gen), dist(gen));
|
||||
}
|
||||
|
||||
for (auto _ : state) {
|
||||
for (const auto& [attackIdx, defendIdx] : randomPairs) {
|
||||
auto result = TypeUtils::getTypeEffectiveness<Generation::GENERATION_1>(
|
||||
attackTypes[attackIdx],
|
||||
attackTypes[defendIdx]
|
||||
);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Individual benchmarks for standalone functions
|
||||
static void BM_TypeEffectivenessFunction(benchmark::State& state) {
|
||||
Type attackType = Type::WATER;
|
||||
Type defendType = Type::FIRE;
|
||||
|
||||
for (auto _ : state) {
|
||||
auto result = TypeUtils::getTypeEffectiveness<Generation::GENERATION_1>(attackType, defendType);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
}
|
||||
|
||||
static void BM_DamageMultiplierFunction(benchmark::State& state) {
|
||||
Type attackType = Type::WATER;
|
||||
PokemonTypes defender(Type::FIRE, Type::FLYING);
|
||||
|
||||
for (auto _ : state) {
|
||||
auto result = TypeUtils::calculateDamageMultiplier<Generation::GENERATION_1>(attackType, defender);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
}
|
||||
|
||||
static void BM_CalculateDamageFunction(benchmark::State& state) {
|
||||
uint32_t baseDamage = 100;
|
||||
Type attackType = Type::WATER;
|
||||
PokemonTypes defender(Type::FIRE, Type::FLYING);
|
||||
|
||||
for (auto _ : state) {
|
||||
auto result = calculateDamage<Generation::GENERATION_1>(baseDamage, attackType, defender);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_TypeEffectivenessFunction)
|
||||
->Unit(benchmark::kNanosecond)
|
||||
->Iterations(1000000);
|
||||
|
||||
BENCHMARK(BM_DamageMultiplierFunction)
|
||||
->Unit(benchmark::kNanosecond)
|
||||
->Iterations(1000000);
|
||||
|
||||
BENCHMARK(BM_CalculateDamageFunction)
|
||||
->Unit(benchmark::kNanosecond)
|
||||
->Iterations(1000000);
|
||||
|
||||
// Complex scenario benchmark (simulating a full battle turn)
|
||||
static void BM_BattleTurnSimulation(benchmark::State& state) {
|
||||
// Simulate a battle turn with multiple Pokemon and moves
|
||||
const size_t numAttackers = 6; // Full team
|
||||
const size_t numDefenders = 6; // Full opposing team
|
||||
const size_t movesPerTurn = 4; // Multiple moves per Pokemon
|
||||
|
||||
struct BattlePokemon {
|
||||
PokemonTypes types;
|
||||
std::vector<Type> moves;
|
||||
};
|
||||
|
||||
std::vector<BattlePokemon> attackers;
|
||||
std::vector<BattlePokemon> defenders;
|
||||
|
||||
// Setup realistic Pokemon teams
|
||||
std::vector<std::pair<Type, Type>> attackerTypes = {
|
||||
{Type::FIRE, Type::FLYING}, // Charizard
|
||||
{Type::WATER, Type::NONE}, // Blastoise
|
||||
{Type::GRASS, Type::POISON}, // Venusaur
|
||||
{Type::ELECTRIC, Type::NONE}, // Raichu
|
||||
{Type::FIGHTING, Type::NONE}, // Machamp
|
||||
{Type::NORMAL, Type::FLYING} // Pidgeot
|
||||
};
|
||||
|
||||
std::vector<std::pair<Type, Type>> defenderTypes = {
|
||||
{Type::WATER, Type::POISON}, // Tentacruel
|
||||
{Type::ROCK, Type::GROUND}, // Rhyperior
|
||||
{Type::ELECTRIC, Type::STEEL}, // Magnezone
|
||||
{Type::PSYCHIC, Type::NONE}, // Alakazam
|
||||
{Type::DARK, Type::NONE}, // Umbreon
|
||||
{Type::FAIRY, Type::NONE} // Sylveon
|
||||
};
|
||||
|
||||
// Setup moves for each Pokemon
|
||||
std::vector<std::vector<Type>> moveSets = {
|
||||
{Type::FIRE, Type::FLYING, Type::DRAGON, Type::NORMAL}, // Charizard moves
|
||||
{Type::WATER, Type::NORMAL, Type::ICE, Type::FIGHTING}, // Blastoise moves
|
||||
{Type::GRASS, Type::POISON, Type::GROUND, Type::NORMAL}, // Venusaur moves
|
||||
{Type::ELECTRIC, Type::NORMAL, Type::PSYCHIC, Type::DARK}, // Raichu moves
|
||||
{Type::FIGHTING, Type::NORMAL, Type::ROCK, Type::GROUND}, // Machamp moves
|
||||
{Type::FLYING, Type::NORMAL, Type::GRASS, Type::ICE} // Pidgeot moves
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < numAttackers; ++i) {
|
||||
attackers.push_back({
|
||||
PokemonTypes(attackerTypes[i].first, attackerTypes[i].second),
|
||||
moveSets[i]
|
||||
});
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < numDefenders; ++i) {
|
||||
defenders.push_back({
|
||||
PokemonTypes(defenderTypes[i].first, defenderTypes[i].second),
|
||||
moveSets[i % moveSets.size()] // Reuse movesets for simplicity
|
||||
});
|
||||
}
|
||||
|
||||
for (auto _ : state) {
|
||||
uint32_t totalDamage = 0;
|
||||
|
||||
// Simulate one full battle turn
|
||||
for (const auto& attacker : attackers) {
|
||||
for (const auto& defender : defenders) {
|
||||
for (const auto& move : attacker.moves) {
|
||||
// Calculate damage for each move against each defender
|
||||
uint32_t damage = calculateDamage<Generation::GENERATION_1>(100, move, defender.types);
|
||||
totalDamage += damage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
benchmark::DoNotOptimize(totalDamage);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_BattleTurnSimulation)
|
||||
->Unit(benchmark::kMicrosecond)
|
||||
->Iterations(100);
|
||||
|
||||
} // namespace PokemonSim
|
||||
Reference in New Issue
Block a user