Skip to main content
2 of 3
edited tags; edited title
200_success
  • 144.1k
  • 22
  • 188
  • 473

Card game simulations, with a multithreaded version slower than the single-threaded one

I'm new to rust and I'm trying to build a card game simulation to find all of the best actions with all possible hands. This requires a lot of simulations and my first 3 million are already taking a huge amount of time. I want to show you 2 variants of my code: first, the one with no threading which takes about 35 minutes to complete, then the one with threading which takes about 50 minutes. I was wondering if there were any obvious places I could optimize my code. For some reason, the compiler didn't want me threading without cloning a lot, which, in addition to the amount of threads is probably the reason it's so slow. So here's the code without threading:

fn test_simulation(cards: &mut Cards, results: &mut Vec<Vec<Results>>, combo_number:u32){
let card = 5;
println!("Combo {}/3072", combo_number+1);
let percentage = 100*combo_number/3071;
println!("{:?}%", percentage);

let vs_card= VS_CARDS[card];
let mut action1_result = [0.0; 3];
let mut action2_result = [0.0; 3];
let mut action3_result = [0.0; 3];
let mut action4_result = [0.0; 3];

cards.vs_card = vs_card;

for i in 0..100{
    

    cards.recut();
 


    if SRR && .test_combo.len() == 2{
        let result =  cards.action1();
        surrender_result[0] = (result[0]+ action1_result[0]*i as f32)/(i as f32 + 1.0);
        surrender_result[1] = (result[1]+ action1_result[1]*i as f32)/(i as f32 + 1.0);
        surrender_result[2] = (result[2]+ action1_result[2]*i as f32)/(i as f32 + 1.0);
    }else{
        action1_result[0] = -1.0;
        action1_result[1] = 0.0;
        action1_result[2] = 0.0;
      
    }
    let result = cards.action2();
    action2_result[0] = (result[0]+ action2_result[0]*i as f32)/(i as f32 + 1.0);
    action2_result[1] = (result[1]+ action2_result[1]*i as f32)/(i as f32 + 1.0);
    action2_result[2] = (result[2]+ action2_result[2]*i as f32)/(i as f32 + 1.0);
    
    if D_ALL && cards.test_combo.len() == 2{
        let result = cards.action3();
        action3_result[0] = (result[0]+ action3_result[0]*i as f32)/(i as f32 + 1.0);
        action3_result[1] = (result[1]+ action3_result[1]*i as f32)/(i as f32 + 1.0);
        action3_result[2] = (result[2]+ action3_result[2]*i as f32)/(i as f32 + 1.0);
    }else{
        action3_result[0] = -1.0;
        action3_result[1] = 0.0;
        action3_result[2] = 0.0;
    }

    let result = cards.action4(&mut results[card]);
    action4_result[0] = (result[0]+ action4_result[0]*i as f32)/(i as f32 + 1.0);
    action4_result[1] = (result[1]+ action4_result[1]*i as f32)/(i as f32 + 1.0);
    action4_result[2] = (result[2]+ action4_result[2]*i as f32)/(i as f32 + 1.0);
    
}
let combo = &cards.test_combo;
let mut u8_combo = Card::combo_rank_to_value(combo);
u8_combo.sort();
results[card].push(Results::new(combo.to_vec() ,u8_combo, vs_card, action1_result, action2_result, action3_result,action4_result ));}





fn main() {
    let (mut all_cards, mut all_results) = initialize_all();

 

   // Will test against 12 possible opponent cards, right now using just 1
    for i in 0..all_cards.len(){
        
        test_simulation( &mut all_cards[i], &mut all_results, i as u32);
        
    };

    
    for i in 0..all_results[5].len() {

        if all_results[5][i].u8_combo.len() == 2{
            print!("{:#?} ", all_results[5][i].u8_combo);
            print!("{:#?} ", all_results[5][i].best_action);
            print!("{} ", all_results[5][i].best_action_value);
        }
        
    }
    

}

And here is the slow one with threading and a mess of Arc Mutexes:

    fn test_simulation(cards: &mut Cards, results: &mut Vec<Vec<Results>>, combo_number:u32){
    let mut this_cards = cards.clone();
    let card = 5;
    let current_results = results[5].clone();

    println!("Combo {}/3072", combo_number+1);
    let percentage = 100*combo_number/3071;
    println!("{:?}%", percentage);

    let vs_card= VS_CARDS[card];
    let action1_result = Arc::new(Mutex::new([0.0; 3]));
    let action2_result = Arc::new(Mutex::new([0.0; 3]));
    let action3_result = Arc::new(Mutex::new([0.0; 3]));
    let action4_result = Arc::new(Mutex::new([0.0; 3]));
    let mut handles = vec![];

    this_cards.vs_card = vs_card;

    let  this_cards = Arc::new(Mutex::new(this_cards));
    let  current_results = Arc::new(Mutex::new(current_results));
    for i in 0..100{
        let  this_cards = Arc::clone(&this_cards);
        let  current_results = Arc::clone(&current_results);
        this_cards.lock().unwrap().recut();
        let action1_result = Arc::clone(&action1_result);
        let action2_result = Arc::clone(&action2_result);
        let action3_result = Arc::clone(&action3_result);
        let action4_result = Arc::clone(&action4_result);
        
        let handle = thread::spawn(move || {
        
            let mut action1_result = action1_result.lock().unwrap();
            let mut action2_result = action2_result.lock().unwrap();
            let mut action3_result = action3_result.lock().unwrap();
            let mut action4_result = action4_result.lock().unwrap();
            if SRR && this_cards.lock().unwrap().test_combo_length == 2{
                let act1_res =  this_cards.lock().unwrap().surrender();
                
                action1_result[0] = (act1_res[0]+ action1_result[0]*i as f32)/(i as f32 + 1.0);
                action1_result[1] = (act1_res[1]+ action1_result[1]*i as f32)/(i as f32 + 1.0);
                action1_result[2] = (act1_res[2]+ action1_result[2]*i as f32)/(i as f32 + 1.0);
            }else{
                action1_result[0] = -1.0;
                action1_result[1] = 0.0;
                action1_result[2] = 0.0;
          
                }
            let act2_res = this_shoe.lock().unwrap().stand();
            action2_result[0] = (act2_res[0]+ action2_result[0]*i as f32)/(i as f32 + 1.0);
            action2_result[1] = (act2_res[1]+ action2_result[1]*i as f32)/(i as f32 + 1.0);
            action2_result[2] = (act2_res[2]+ action2_result[2]*i as f32)/(i as f32 + 1.0);
            
            if D_ALL && this_cards.lock().unwrap().test_combo_length == 2{
                let act3_res = this_cards.lock().unwrap().double();
                action3_result[0] = (act3_res[0]+ action3_result[0]*i as f32)/(i as f32 + 1.0);
                action3_result[1] = (act3_res[1]+ action3_result[1]*i as f32)/(i as f32 + 1.0);
                action3_result[2] = (act3_res[2]+ action3_result[2]*i as f32)/(i as f32 + 1.0);
            }else{
                action3_result[0] = -1.0;
                action3_result[1] = 0.0;
                action3_result[2] = 0.0;
            }
    
            let act4_res = this_shoe.lock().unwrap().hit(&mut current_results.lock().unwrap());
            action4_result[0] = (act4_res[0]+ action4_result[0]*i as f32)/(i as f32 + 1.0);
            action4_result[1] = (act4_res[1]+ action4_result[1]*i as f32)/(i as f32 + 1.0);
            action4_result[2] = (act4_res[2]+ action4_result[2]*i as f32)/(i as f32 + 1.0);


            
            
        });
        handles.push(handle);

    }
    for handle in handles {
        handle.join().unwrap();
    }

    let combo = &cards.test_combo;
    let mut u8_combo = Card::combo_rank_to_value(combo);

    u8_combo.sort();
   
    results[card].push(Results::new(combo.to_vec() ,u8_combo, vs_card, *action1_result.lock().unwrap(), *action2_result.lock().unwrap(), *action3_result.lock().unwrap(), *action4_result.lock().unwrap()));
   }

  fn main() {
        let (mut all_cards, mut all_results) = initialize_all();
    
     
    
       // Will test against 12 possible opponent cards, right now using just 1
        for i in 0..all_cards.len(){
            
            test_simulation( &mut all_cards[i], &mut all_results, i as u32);
            
        };
    
        
        for i in 0..all_results[5].len() {
    
            if all_results[5][i].u8_combo.len() == 2{
                print!("{:#?} ", all_results[5][i].u8_combo);
                print!("{:#?} ", all_results[5][i].best_action);
                print!("{} ", all_results[5][i].best_action_value);
            }
            
        }
        
    
    }

I've thought of using a channel to just send all results to the main thread to combine there but I don't know if it would be any faster. For more information: in each action call, the action is run 10 times. This is to insure that each card combo has each action done to it 1000 times, in order to have somewhat reliable results.