View Javadoc
1   package net.bmahe.genetics4j.core.evaluation;
2   
3   import java.util.ArrayList;
4   import java.util.List;
5   import java.util.Objects;
6   import java.util.concurrent.CompletableFuture;
7   import java.util.concurrent.ExecutorService;
8   import java.util.concurrent.Executors;
9   
10  import org.apache.commons.lang3.Validate;
11  import org.apache.logging.log4j.LogManager;
12  import org.apache.logging.log4j.Logger;
13  
14  import net.bmahe.genetics4j.core.Fitness;
15  import net.bmahe.genetics4j.core.Genotype;
16  import net.bmahe.genetics4j.core.spec.EAConfiguration;
17  import net.bmahe.genetics4j.core.spec.EAExecutionContext;
18  
19  /**
20   * Virtual thread-based fitness evaluator that creates one virtual thread per individual evaluation.
21   *
22   * <p>This evaluator leverages Java 21+ virtual threads to provide massive parallelism for fitness evaluation without
23   * the overhead of traditional platform threads. Each genotype gets its own virtual thread, making this approach
24   * particularly suitable for:
25   *
26   * <ul>
27   * <li><strong>I/O-bound fitness functions</strong>: Database queries, web service calls, file operations</li>
28   * <li><strong>Large populations</strong>: Thousands of individuals without thread pool limitations</li>
29   * <li><strong>Complex simulations</strong>: Long-running evaluations that benefit from massive parallelism</li>
30   * <li><strong>External service integration</strong>: Fitness evaluations requiring network calls</li>
31   * </ul>
32   *
33   * <p>Unlike traditional thread pool-based evaluators, this implementation:
34   * <ul>
35   * <li>Creates one virtual thread per genotype (no partitioning)</li>
36   * <li>Leverages virtual thread's lightweight nature for maximum concurrency</li>
37   * <li>Automatically manages virtual thread lifecycle</li>
38   * <li>Provides optimal resource utilization for I/O-intensive workloads</li>
39   * </ul>
40   *
41   * <p>Performance characteristics:
42   * <ul>
43   * <li><strong>Memory overhead</strong>: ~200 bytes per virtual thread vs ~2MB per platform thread</li>
44   * <li><strong>Creation cost</strong>: Nearly zero compared to platform threads</li>
45   * <li><strong>Blocking behavior</strong>: Virtual threads don't block carrier threads during I/O</li>
46   * <li><strong>Scalability</strong>: Can handle millions of concurrent virtual threads</li>
47   * </ul>
48   *
49   * <p>This evaluator automatically creates and manages a virtual thread executor, eliminating the need for explicit
50   * thread pool configuration and management.
51   *
52   * @param <T> the type of fitness values produced, must be comparable for selection operations
53   * @see FitnessEvaluator
54   * @see FitnessEvaluatorSync
55   * @see net.bmahe.genetics4j.core.Fitness
56   */
57  public class FitnessEvaluatorVirtualThread<T extends Comparable<T>> implements FitnessEvaluator<T> {
58  	public static final Logger logger = LogManager.getLogger(FitnessEvaluatorVirtualThread.class);
59  
60  	private final EAExecutionContext<T> eaExecutionContext;
61  	private final EAConfiguration<T> eaConfiguration;
62  	private final ExecutorService virtualThreadExecutor;
63  
64  	public FitnessEvaluatorVirtualThread(final EAExecutionContext<T> _eaExecutionContext,
65  			final EAConfiguration<T> _eaConfiguration) {
66  		Objects.requireNonNull(_eaExecutionContext);
67  		Objects.requireNonNull(_eaConfiguration);
68  
69  		this.eaExecutionContext = _eaExecutionContext;
70  		this.eaConfiguration = _eaConfiguration;
71  		this.virtualThreadExecutor = Executors.newVirtualThreadPerTaskExecutor();
72  	}
73  
74  	@Override
75  	public void preEvaluation() {
76  		logger.debug("Pre-evaluation setup for virtual thread fitness evaluator");
77  	}
78  
79  	@Override
80  	public void postEvaluation() {
81  		logger.debug("Post-evaluation cleanup for virtual thread fitness evaluator");
82  	}
83  
84  	@Override
85  	public List<T> evaluate(final long generation, final List<Genotype> population) {
86  		Validate.isTrue(generation >= 0);
87  		Objects.requireNonNull(population);
88  
89  		if (population.isEmpty()) {
90  			return new ArrayList<>();
91  		}
92  
93  		logger.debug("Evaluating {} individuals using virtual threads", population.size());
94  
95  		final Fitness<T> fitness = eaConfiguration.fitness();
96  		final List<CompletableFuture<T>> evaluationTasks = new ArrayList<>(population.size());
97  
98  		// Create one virtual thread task per genotype - no partitioning needed
99  		for (final Genotype genotype : population) {
100 			final CompletableFuture<T> evaluationTask = CompletableFuture.supplyAsync(() -> {
101 				return fitness.compute(genotype);
102 			}, virtualThreadExecutor);
103 
104 			evaluationTasks.add(evaluationTask);
105 		}
106 
107 		// Wait for all evaluations to complete and collect results in order
108 		final List<T> fitnessScores = new ArrayList<>(population.size());
109 		for (final CompletableFuture<T> task : evaluationTasks) {
110 			fitnessScores.add(task.join());
111 		}
112 
113 		logger.debug("Completed evaluation of {} individuals", population.size());
114 		return fitnessScores;
115 	}
116 }