Skip to content

Commit

Permalink
Use string interning for core timers
Browse files Browse the repository at this point in the history
  • Loading branch information
cjdsellers committed Jan 3, 2024
1 parent b8e2194 commit 4195358
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 91 deletions.
1 change: 1 addition & 0 deletions nautilus_core/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions nautilus_core/backtest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ nautilus-common = { path = "../common" }
nautilus-core = { path = "../core" }
nautilus-model = { path = "../model" }
pyo3 = { workspace = true, optional = true }
ustr = { workspace = true }

[dev-dependencies]
tempfile = { workspace = true }
Expand Down
10 changes: 7 additions & 3 deletions nautilus_core/backtest/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ mod tests {
use nautilus_core::uuid::UUID4;
use pyo3::{types::PyList, Py, Python};
use rstest::*;
use ustr::Ustr;

use super::*;

Expand All @@ -126,9 +127,12 @@ mod tests {

let mut accumulator = TimeEventAccumulator::new();

let time_event1 = TimeEvent::new("TEST_EVENT_1", UUID4::new(), 100, 100).unwrap();
let time_event2 = TimeEvent::new("TEST_EVENT_2", UUID4::new(), 300, 300).unwrap();
let time_event3 = TimeEvent::new("TEST_EVENT_3", UUID4::new(), 200, 200).unwrap();
let time_event1 =
TimeEvent::new(Ustr::from("TEST_EVENT_1"), UUID4::new(), 100, 100).unwrap();
let time_event2 =
TimeEvent::new(Ustr::from("TEST_EVENT_2"), UUID4::new(), 300, 300).unwrap();
let time_event3 =
TimeEvent::new(Ustr::from("TEST_EVENT_3"), UUID4::new(), 200, 200).unwrap();

// Note: as_ptr returns a borrowed pointer. It is valid as long
// as the object is in scope. In this case `callback_ptr` is valid
Expand Down
113 changes: 54 additions & 59 deletions nautilus_core/common/src/clock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use nautilus_core::{
correctness::check_valid_string,
time::{AtomicTime, ClockMode, UnixNanos},
};
use ustr::Ustr;

use crate::{
handlers::EventHandler,
Expand All @@ -44,7 +45,7 @@ pub trait Clock {
/// callback gets used to handle generated events.
fn set_time_alert_ns(
&mut self,
name: String,
name: &str,
alert_time_ns: UnixNanos,
callback: Option<EventHandler>,
);
Expand All @@ -54,7 +55,7 @@ pub trait Clock {
/// used to handle generated event.
fn set_timer_ns(
&mut self,
name: String,
name: &str,
interval_ns: u64,
start_time_ns: UnixNanos,
stop_time_ns: Option<UnixNanos>,
Expand All @@ -72,9 +73,9 @@ pub trait Clock {
)]
pub struct TestClock {
time: AtomicTime,
timers: HashMap<String, TestTimer>,
timers: HashMap<Ustr, TestTimer>,
default_callback: Option<EventHandler>,
callbacks: HashMap<String, EventHandler>,
callbacks: HashMap<Ustr, EventHandler>,
}

impl TestClock {
Expand All @@ -93,7 +94,7 @@ impl TestClock {
}

#[must_use]
pub fn get_timers(&self) -> &HashMap<String, TestTimer> {
pub fn get_timers(&self) -> &HashMap<Ustr, TestTimer> {
&self.timers
}

Expand Down Expand Up @@ -125,15 +126,11 @@ impl TestClock {
events
.into_iter()
.map(|event| {
let handler = self
.callbacks
.get(event.name.as_str())
.cloned()
.unwrap_or_else(|| {
// If callback_py is None, use the default_callback_py
// TODO: clone for now
self.default_callback.clone().unwrap()
});
let handler = self.callbacks.get(&event.name).cloned().unwrap_or_else(|| {
// If callback_py is None, use the default_callback_py
// TODO: clone for now
self.default_callback.clone().unwrap()
});
TimeEventHandler {
event,
callback_ptr: handler.as_ptr(),
Expand Down Expand Up @@ -179,66 +176,63 @@ impl Clock for TestClock {

fn set_time_alert_ns(
&mut self,
name: String,
name: &str,
alert_time_ns: UnixNanos,
callback: Option<EventHandler>,
) {
check_valid_string(&name, "`Timer` name").unwrap();
check_valid_string(name, "`Timer` name").unwrap();
assert!(
callback.is_some() | self.default_callback.is_some(),
"All Python callbacks were `None`"
);

let name_ustr = Ustr::from(name);
match callback {
Some(callback_py) => self.callbacks.insert(name.clone(), callback_py),
Some(callback_py) => self.callbacks.insert(name_ustr, callback_py),
None => None,
};

// TODO: should the atomic clock be shared
// currently share timestamp nanoseconds
let time_ns = self.time.get_time_ns();
let timer = TestTimer::new(
name.clone(),
alert_time_ns - time_ns,
time_ns,
Some(alert_time_ns),
);
self.timers.insert(name, timer);
let timer = TestTimer::new(name, alert_time_ns - time_ns, time_ns, Some(alert_time_ns));
self.timers.insert(name_ustr, timer);
}

fn set_timer_ns(
&mut self,
name: String,
name: &str,
interval_ns: u64,
start_time_ns: UnixNanos,
stop_time_ns: Option<UnixNanos>,
callback: Option<EventHandler>,
) {
check_valid_string(&name, "`Timer` name").unwrap();
check_valid_string(name, "`Timer` name").unwrap();
assert!(
callback.is_some() | self.default_callback.is_some(),
"All Python callbacks were `None`"
);

let name_ustr = Ustr::from(name);
match callback {
Some(callback_py) => self.callbacks.insert(name.clone(), callback_py),
Some(callback_py) => self.callbacks.insert(name_ustr, callback_py),
None => None,
};

let timer = TestTimer::new(name.clone(), interval_ns, start_time_ns, stop_time_ns);
self.timers.insert(name, timer);
let timer = TestTimer::new(name, interval_ns, start_time_ns, stop_time_ns);
self.timers.insert(name_ustr, timer);
}

fn next_time_ns(&self, name: &str) -> UnixNanos {
let timer = self.timers.get(name);
let timer = self.timers.get(&Ustr::from(name));
match timer {
None => 0,
Some(timer) => timer.next_time_ns,
}
}

fn cancel_timer(&mut self, name: &str) {
let timer = self.timers.remove(name);
let timer = self.timers.remove(&Ustr::from(name));
match timer {
None => {}
Some(mut timer) => timer.cancel(),
Expand All @@ -259,9 +253,9 @@ impl Clock for TestClock {
)]
pub struct LiveClock {
internal: AtomicTime,
timers: HashMap<String, TestTimer>,
timers: HashMap<Ustr, TestTimer>,
default_callback: Option<EventHandler>,
callbacks: HashMap<String, EventHandler>,
callbacks: HashMap<Ustr, EventHandler>,
}

impl LiveClock {
Expand Down Expand Up @@ -312,65 +306,62 @@ impl Clock for LiveClock {

fn set_time_alert_ns(
&mut self,
name: String,
name: &str,
mut alert_time_ns: UnixNanos,
callback: Option<EventHandler>,
) {
check_valid_string(&name, "`Timer` name").unwrap();
check_valid_string(name, "`Timer` name").unwrap();
assert!(
callback.is_some() | self.default_callback.is_some(),
"All Python callbacks were `None`"
);

let name_ustr = Ustr::from(name);
match callback {
Some(callback_py) => self.callbacks.insert(name.clone(), callback_py),
Some(callback_py) => self.callbacks.insert(name_ustr, callback_py),
None => None,
};

let ts_now = self.get_time_ns();
alert_time_ns = std::cmp::max(alert_time_ns, ts_now);
let timer = TestTimer::new(
name.clone(),
alert_time_ns - ts_now,
ts_now,
Some(alert_time_ns),
);
self.timers.insert(name, timer);
let timer = TestTimer::new(name, alert_time_ns - ts_now, ts_now, Some(alert_time_ns));
self.timers.insert(name_ustr, timer);
}

fn set_timer_ns(
&mut self,
name: String,
name: &str,
interval_ns: u64,
start_time_ns: UnixNanos,
stop_time_ns: Option<UnixNanos>,
callback: Option<EventHandler>,
) {
check_valid_string(&name, "`Timer` name").unwrap();
check_valid_string(name, "`Timer` name").unwrap();
assert!(
callback.is_some() | self.default_callback.is_some(),
"All Python callbacks were `None`"
);

let name_ustr = Ustr::from(name);
match callback {
Some(callback) => self.callbacks.insert(name.clone(), callback),
Some(callback) => self.callbacks.insert(name_ustr, callback),
None => None,
};

let timer = TestTimer::new(name.clone(), interval_ns, start_time_ns, stop_time_ns);
self.timers.insert(name, timer);
let timer = TestTimer::new(name, interval_ns, start_time_ns, stop_time_ns);
self.timers.insert(name_ustr, timer);
}

fn next_time_ns(&self, name: &str) -> UnixNanos {
let timer = self.timers.get(name);
let timer = self.timers.get(&Ustr::from(name));
match timer {
None => 0,
Some(timer) => timer.next_time_ns,
}
}

fn cancel_timer(&mut self, name: &str) {
let timer = self.timers.remove(name);
let timer = self.timers.remove(&Ustr::from(name));
match timer {
None => {}
Some(mut timer) => timer.cancel(),
Expand Down Expand Up @@ -421,9 +412,10 @@ mod tests {
let handler = EventHandler::new(Some(py_append), None);
test_clock.register_default_handler(handler);

test_clock.set_timer_ns(String::from("TEST_TIME1"), 10, 0, None, None);
let timer_name = "TEST_TIME1";
test_clock.set_timer_ns(timer_name, 10, 0, None, None);

assert_eq!(test_clock.timer_names(), ["TEST_TIME1"]);
assert_eq!(test_clock.timer_names(), [timer_name]);
assert_eq!(test_clock.timer_count(), 1);
});
}
Expand All @@ -438,8 +430,9 @@ mod tests {
let handler = EventHandler::new(Some(py_append), None);
test_clock.register_default_handler(handler);

test_clock.set_timer_ns(String::from("TEST_TIME1"), 10, 0, None, None);
test_clock.cancel_timer(String::from("TEST_TIME1").as_str());
let timer_name = "TEST_TIME1";
test_clock.set_timer_ns(timer_name, 10, 0, None, None);
test_clock.cancel_timer(timer_name);

assert!(test_clock.timer_names().is_empty());
assert_eq!(test_clock.timer_count(), 0);
Expand All @@ -456,7 +449,8 @@ mod tests {
let handler = EventHandler::new(Some(py_append), None);
test_clock.register_default_handler(handler);

test_clock.set_timer_ns(String::from("TEST_TIME1"), 10, 0, None, None);
let timer_name = "TEST_TIME1";
test_clock.set_timer_ns(timer_name, 10, 0, None, None);
test_clock.cancel_timers();

assert!(test_clock.timer_names().is_empty());
Expand All @@ -474,10 +468,11 @@ mod tests {
let handler = EventHandler::new(Some(py_append), None);
test_clock.register_default_handler(handler);

test_clock.set_timer_ns(String::from("TEST_TIME1"), 1, 1, Some(3), None);
let timer_name = "TEST_TIME1";
test_clock.set_timer_ns(timer_name, 1, 1, Some(3), None);
test_clock.advance_time(2, true);

assert_eq!(test_clock.timer_names(), ["TEST_TIME1"]);
assert_eq!(test_clock.timer_names(), [timer_name]);
assert_eq!(test_clock.timer_count(), 1);
});
}
Expand All @@ -492,7 +487,7 @@ mod tests {
let handler = EventHandler::new(Some(py_append), None);
test_clock.register_default_handler(handler);

test_clock.set_timer_ns(String::from("TEST_TIME1"), 2, 0, Some(3), None);
test_clock.set_timer_ns("TEST_TIME1", 2, 0, Some(3), None);
test_clock.advance_time(3, true);

assert_eq!(test_clock.timer_names().len(), 1);
Expand All @@ -511,7 +506,7 @@ mod tests {
let handler = EventHandler::new(Some(py_append), None);
test_clock.register_default_handler(handler);

test_clock.set_timer_ns(String::from("TEST_TIME1"), 2, 0, Some(3), None);
test_clock.set_timer_ns("TEST_TIME1", 2, 0, Some(3), None);
test_clock.advance_time(3, false);

assert_eq!(test_clock.timer_names().len(), 1);
Expand Down
4 changes: 2 additions & 2 deletions nautilus_core/common/src/ffi/clock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ pub unsafe extern "C" fn test_clock_set_time_alert_ns(
});
let handler = EventHandler::new(callback_py.clone(), None);

clock.set_time_alert_ns(name, alert_time_ns, callback_py.map(|_| handler));
clock.set_time_alert_ns(&name, alert_time_ns, callback_py.map(|_| handler));
}

/// # Safety
Expand Down Expand Up @@ -181,7 +181,7 @@ pub unsafe extern "C" fn test_clock_set_timer_ns(
let handler = EventHandler::new(callback_py.clone(), None);

clock.set_timer_ns(
name,
&name,
interval_ns,
start_time_ns,
stop_time_ns,
Expand Down
4 changes: 2 additions & 2 deletions nautilus_core/common/src/ffi/timer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
use std::ffi::c_char;

use nautilus_core::{
ffi::string::{cstr_to_string, str_to_cstr},
ffi::string::{cstr_to_ustr, str_to_cstr},
uuid::UUID4,
};

Expand All @@ -32,7 +32,7 @@ pub unsafe extern "C" fn time_event_new(
ts_event: u64,
ts_init: u64,
) -> TimeEvent {
TimeEvent::new(&cstr_to_string(name_ptr), event_id, ts_event, ts_init).unwrap()
TimeEvent::new(cstr_to_ustr(name_ptr), event_id, ts_event, ts_init).unwrap()
}

/// Returns a [`TimeEvent`] as a C string pointer.
Expand Down
Loading

0 comments on commit 4195358

Please sign in to comment.