Hi,
For my business requirements (basically we need flexibility to create new type of order, as structs, easily without to deliver entire platform, I need to use dyn Trait instead of enum of each order.
See below the elements of the code required to understand my concerns...
What is really insane is the performances using dynamic binding...
Structs (orders)
pub struct FooOrder<'a>
{
pub order_data: Option<OrderData<'a>>,
pub order_variant: Box<dyn TOrderType>,
pub order_id: u64,
pub basket_id: Option<AtomicCell<u32>>,
pub action_counter: AtomicCell<u16>,
}
impl<'a> FooOrder<'a> {
pub fn static_execute(&mut self){
self.order_id = 45;
let exec_data = ExecutionData{
order_quantity:45,
side: OrderSide::Buy,
last_execution_price:12.3,
average_price:45.5,
last_execution_quantity:14,
remaining_quantity:11,
cumulative_quantity:11
};
_=self.order_variant.execute(&exec_data);
}
}
pub trait TOrderType{
fn execute(&self, exec_data: &ExecutionData) ->Result<bool, OrderErrorMessages>;
fn update(&self, value: Box<dyn TOrderType>);
}
pub struct LimitOrderData {
pub price: f32,
pub quantity: u32,
pub tif: TimeInForce,
pub side: OrderSide
}
pub struct MarketOrderData {
pub quantity: u32,
pub tif: TimeInForce,
pub side: OrderSide
}
//there are lot of order types
pub struct ExecutionData {
pub side: OrderSide,
pub last_execution_price: f32,
pub average_price:f32,
pub last_execution_quantity: u32,
pub remaining_quantity: u32,
pub order_quantity: u32,
pub cumulative_quantity: u32
}
pub enum OrderSide{
Buy,
Sell
}
pub enum TimeInForce {
Day,
Gtc,
Gtd(u64),//expiration time
Ioc,
Fok,
Moo,
Loo,
Moc,
Loc
}
So, here the test:
fn test_dynamic_trait(){
let t0 = Instant::now();
for _it in 0..100_000_000{
let ord = FooOrder {
order_data:Some(OrderData::default()),
order_variant: Box::new(MarketOrderData{
quantity: 25,
tif: TimeInForce::Day,
side: OrderSide::Buy
}),
basket_id:Some(AtomicCell::new(0)),
order_id:25,
action_counter: AtomicCell::new(12)
};
_=ord.execute(&ExecutionData{
order_quantity:45,
side: OrderSide::Buy,
last_execution_price:12.3,
average_price:45.5,
last_execution_quantity:14,
remaining_quantity:11,
cumulative_quantity:11
});
}
let elapsed = t0.elapsed().as_secs_f64();
println!("test dynamic invoke Time elapsed: {:.3} sec. ", elapsed);
}
This takes 4.9 sec!!! 40 ns by dyn call. How is it possible? In C++, Java, C#, virtual methods takes, in my pc between, 0.8 and 2 ns by call.
Last versions of JIT are using PGO, but anyway, using old versions has well, the time for dynamic invocation are under 5ns.
Using static invocation, the time for the same example (using as order_variant an enum wrapping order structs) is 0.052 sec, so really as expected using rust.
(The time to create objects takes around 0.052 sec, the time to call methode is under 1ns)
There are way to understand what happens? I believe that the differences should be around x2 or x3 but not > x25...