use crate::jit::CompilationLevel;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BranchOutcome {
Taken,
NotTaken,
}
#[derive(Debug, Clone)]
pub struct TypeProfileEntry {
pub class_name: String,
pub count: u64,
}
#[derive(Debug, Clone, Default)]
pub struct CallSiteProfile {
pub total_calls: u64,
pub type_profiles: Vec<TypeProfileEntry>,
pub is_monomorphic: bool,
pub is_megamorphic: bool,
}
impl CallSiteProfile {
const MEGAMORPHIC_THRESHOLD: usize = 4;
pub fn record_call(&mut self, receiver_class: &str) {
self.total_calls += 1;
if let Some(entry) = self
.type_profiles
.iter_mut()
.find(|e| e.class_name == receiver_class)
{
entry.count += 1;
} else {
self.type_profiles.push(TypeProfileEntry {
class_name: receiver_class.to_string(),
count: 1,
});
}
self.is_monomorphic = self.type_profiles.len() == 1;
self.is_megamorphic = self.type_profiles.len() >= Self::MEGAMORPHIC_THRESHOLD;
}
pub fn dominant_type(&self) -> Option<&str> {
self.type_profiles
.iter()
.max_by_key(|e| e.count)
.map(|e| e.class_name.as_str())
}
pub fn dominant_type_frequency(&self) -> f64 {
if self.total_calls == 0 {
return 0.0;
}
self.type_profiles
.iter()
.map(|e| e.count)
.max()
.map(|max| max as f64 / self.total_calls as f64)
.unwrap_or(0.0)
}
}
#[derive(Debug, Clone, Default)]
pub struct BranchProfile {
pub taken_count: u64,
pub not_taken_count: u64,
}
impl BranchProfile {
pub fn record(&mut self, outcome: BranchOutcome) {
match outcome {
BranchOutcome::Taken => self.taken_count += 1,
BranchOutcome::NotTaken => self.not_taken_count += 1,
}
}
pub fn total(&self) -> u64 {
self.taken_count + self.not_taken_count
}
pub fn taken_probability(&self) -> f64 {
let total = self.total();
if total == 0 {
0.5
} else {
self.taken_count as f64 / total as f64
}
}
pub fn is_biased(&self) -> bool {
let p = self.taken_probability();
p > 0.9 || p < 0.1
}
}
#[derive(Debug, Clone, Default)]
pub struct MethodPgoProfile {
pub invocation_count: u64,
pub branch_profiles: HashMap<usize, BranchProfile>,
pub call_site_profiles: HashMap<usize, CallSiteProfile>,
pub hotness_score: f64,
}
impl MethodPgoProfile {
pub fn record_invocation(&mut self) {
self.invocation_count += 1;
self.update_hotness();
}
pub fn record_branch(&mut self, pc: usize, outcome: BranchOutcome) {
self.branch_profiles.entry(pc).or_default().record(outcome);
}
pub fn record_call_site(&mut self, pc: usize, receiver_class: &str) {
self.call_site_profiles
.entry(pc)
.or_default()
.record_call(receiver_class);
}
fn update_hotness(&mut self) {
let branch_bonus: f64 = self
.branch_profiles
.values()
.filter(|b| b.is_biased())
.count() as f64
* 10.0;
self.hotness_score = self.invocation_count as f64 + branch_bonus;
}
pub fn recommended_level(&self) -> CompilationLevel {
if self.invocation_count >= 10_000 {
CompilationLevel::Optimized
} else if self.invocation_count >= 100 {
CompilationLevel::Baseline
} else {
CompilationLevel::Interpreter
}
}
}
pub struct PgoManager {
profiles: HashMap<String, MethodPgoProfile>,
pub enabled: bool,
pub use_profiles: bool,
}
impl PgoManager {
pub fn new() -> Self {
Self {
profiles: HashMap::new(),
enabled: true,
use_profiles: true,
}
}
fn method_key(class_name: &str, method_name: &str) -> String {
format!("{}.{}", class_name, method_name)
}
pub fn record_invocation(&mut self, class_name: &str, method_name: &str) {
if !self.enabled {
return;
}
let key = Self::method_key(class_name, method_name);
self.profiles.entry(key).or_default().record_invocation();
}
pub fn record_branch(
&mut self,
class_name: &str,
method_name: &str,
pc: usize,
outcome: BranchOutcome,
) {
if !self.enabled {
return;
}
let key = Self::method_key(class_name, method_name);
self.profiles
.entry(key)
.or_default()
.record_branch(pc, outcome);
}
pub fn record_call_site(
&mut self,
class_name: &str,
method_name: &str,
pc: usize,
receiver_class: &str,
) {
if !self.enabled {
return;
}
let key = Self::method_key(class_name, method_name);
self.profiles
.entry(key)
.or_default()
.record_call_site(pc, receiver_class);
}
pub fn get_profile(&self, class_name: &str, method_name: &str) -> Option<&MethodPgoProfile> {
let key = Self::method_key(class_name, method_name);
self.profiles.get(&key)
}
pub fn recommended_level(
&self,
class_name: &str,
method_name: &str,
) -> CompilationLevel {
if !self.use_profiles {
return CompilationLevel::Interpreter;
}
self.get_profile(class_name, method_name)
.map(|p| p.recommended_level())
.unwrap_or(CompilationLevel::Interpreter)
}
pub fn hottest_methods(&self, top_n: usize) -> Vec<(&str, f64)> {
let mut methods: Vec<(&str, f64)> = self
.profiles
.iter()
.map(|(k, p)| (k.as_str(), p.hotness_score))
.collect();
methods.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
methods.into_iter().take(top_n).collect()
}
pub fn is_monomorphic_call_site(
&self,
class_name: &str,
method_name: &str,
pc: usize,
) -> bool {
self.get_profile(class_name, method_name)
.and_then(|p| p.call_site_profiles.get(&pc))
.map(|cs| cs.is_monomorphic)
.unwrap_or(false)
}
pub fn dominant_receiver(
&self,
class_name: &str,
method_name: &str,
pc: usize,
) -> Option<String> {
self.get_profile(class_name, method_name)
.and_then(|p| p.call_site_profiles.get(&pc))
.and_then(|cs| cs.dominant_type())
.map(|s| s.to_string())
}
pub fn export_summary(&self) -> Vec<PgoMethodSummary> {
self.profiles
.iter()
.map(|(key, profile)| PgoMethodSummary {
method_key: key.clone(),
invocation_count: profile.invocation_count,
hotness_score: profile.hotness_score,
recommended_level: profile.recommended_level(),
monomorphic_call_sites: profile
.call_site_profiles
.values()
.filter(|cs| cs.is_monomorphic)
.count(),
biased_branches: profile
.branch_profiles
.values()
.filter(|b| b.is_biased())
.count(),
})
.collect()
}
pub fn stats(&self) -> PgoStats {
let total_methods = self.profiles.len();
let optimized = self
.profiles
.values()
.filter(|p| p.recommended_level() == CompilationLevel::Optimized)
.count();
let baseline = self
.profiles
.values()
.filter(|p| p.recommended_level() == CompilationLevel::Baseline)
.count();
PgoStats {
total_methods_profiled: total_methods,
methods_at_optimized: optimized,
methods_at_baseline: baseline,
methods_at_interpreter: total_methods - optimized - baseline,
}
}
}
impl Default for PgoManager {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct PgoMethodSummary {
pub method_key: String,
pub invocation_count: u64,
pub hotness_score: f64,
pub recommended_level: CompilationLevel,
pub monomorphic_call_sites: usize,
pub biased_branches: usize,
}
#[derive(Debug, Clone)]
pub struct PgoStats {
pub total_methods_profiled: usize,
pub methods_at_optimized: usize,
pub methods_at_baseline: usize,
pub methods_at_interpreter: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pgo_manager_creation() {
let pgo = PgoManager::new();
assert!(pgo.enabled);
assert!(pgo.use_profiles);
}
#[test]
fn test_record_invocation() {
let mut pgo = PgoManager::new();
pgo.record_invocation("Test", "method");
let profile = pgo.get_profile("Test", "method").unwrap();
assert_eq!(profile.invocation_count, 1);
}
#[test]
fn test_recommended_level_interpreter() {
let mut pgo = PgoManager::new();
pgo.record_invocation("Test", "cold");
assert_eq!(
pgo.recommended_level("Test", "cold"),
CompilationLevel::Interpreter
);
}
#[test]
fn test_recommended_level_baseline() {
let mut pgo = PgoManager::new();
for _ in 0..200 {
pgo.record_invocation("Test", "warm");
}
assert_eq!(
pgo.recommended_level("Test", "warm"),
CompilationLevel::Baseline
);
}
#[test]
fn test_recommended_level_optimized() {
let mut pgo = PgoManager::new();
for _ in 0..15_000 {
pgo.record_invocation("Test", "hot");
}
assert_eq!(
pgo.recommended_level("Test", "hot"),
CompilationLevel::Optimized
);
}
#[test]
fn test_branch_profile() {
let mut pgo = PgoManager::new();
for _ in 0..95 {
pgo.record_branch("Test", "m", 10, BranchOutcome::Taken);
}
for _ in 0..5 {
pgo.record_branch("Test", "m", 10, BranchOutcome::NotTaken);
}
let profile = pgo.get_profile("Test", "m").unwrap();
let branch = &profile.branch_profiles[&10];
assert!(branch.is_biased());
assert!(branch.taken_probability() > 0.9);
}
#[test]
fn test_call_site_monomorphic() {
let mut pgo = PgoManager::new();
for _ in 0..100 {
pgo.record_call_site("Test", "m", 20, "java/lang/String");
}
assert!(pgo.is_monomorphic_call_site("Test", "m", 20));
assert_eq!(
pgo.dominant_receiver("Test", "m", 20),
Some("java/lang/String".to_string())
);
}
#[test]
fn test_call_site_megamorphic() {
let mut pgo = PgoManager::new();
for i in 0..5 {
pgo.record_call_site("Test", "m", 30, &format!("Class{}", i));
}
let profile = pgo.get_profile("Test", "m").unwrap();
assert!(profile.call_site_profiles[&30].is_megamorphic);
}
#[test]
fn test_hottest_methods() {
let mut pgo = PgoManager::new();
for _ in 0..1000 {
pgo.record_invocation("Test", "hot");
}
for _ in 0..10 {
pgo.record_invocation("Test", "cold");
}
let hottest = pgo.hottest_methods(1);
assert_eq!(hottest.len(), 1);
assert_eq!(hottest[0].0, "Test.hot");
}
}