FitnessEvaluatorVirtualThread.java
package net.bmahe.genetics4j.core.evaluation;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.commons.lang3.Validate;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.bmahe.genetics4j.core.Fitness;
import net.bmahe.genetics4j.core.Genotype;
import net.bmahe.genetics4j.core.spec.EAConfiguration;
import net.bmahe.genetics4j.core.spec.EAExecutionContext;
/**
* Virtual thread-based fitness evaluator that creates one virtual thread per individual evaluation.
*
* <p>This evaluator leverages Java 21+ virtual threads to provide massive parallelism for fitness evaluation without
* the overhead of traditional platform threads. Each genotype gets its own virtual thread, making this approach
* particularly suitable for:
*
* <ul>
* <li><strong>I/O-bound fitness functions</strong>: Database queries, web service calls, file operations</li>
* <li><strong>Large populations</strong>: Thousands of individuals without thread pool limitations</li>
* <li><strong>Complex simulations</strong>: Long-running evaluations that benefit from massive parallelism</li>
* <li><strong>External service integration</strong>: Fitness evaluations requiring network calls</li>
* </ul>
*
* <p>Unlike traditional thread pool-based evaluators, this implementation:
* <ul>
* <li>Creates one virtual thread per genotype (no partitioning)</li>
* <li>Leverages virtual thread's lightweight nature for maximum concurrency</li>
* <li>Automatically manages virtual thread lifecycle</li>
* <li>Provides optimal resource utilization for I/O-intensive workloads</li>
* </ul>
*
* <p>Performance characteristics:
* <ul>
* <li><strong>Memory overhead</strong>: ~200 bytes per virtual thread vs ~2MB per platform thread</li>
* <li><strong>Creation cost</strong>: Nearly zero compared to platform threads</li>
* <li><strong>Blocking behavior</strong>: Virtual threads don't block carrier threads during I/O</li>
* <li><strong>Scalability</strong>: Can handle millions of concurrent virtual threads</li>
* </ul>
*
* <p>This evaluator automatically creates and manages a virtual thread executor, eliminating the need for explicit
* thread pool configuration and management.
*
* @param <T> the type of fitness values produced, must be comparable for selection operations
* @see FitnessEvaluator
* @see FitnessEvaluatorSync
* @see net.bmahe.genetics4j.core.Fitness
*/
public class FitnessEvaluatorVirtualThread<T extends Comparable<T>> implements FitnessEvaluator<T> {
public static final Logger logger = LogManager.getLogger(FitnessEvaluatorVirtualThread.class);
private final EAExecutionContext<T> eaExecutionContext;
private final EAConfiguration<T> eaConfiguration;
private final ExecutorService virtualThreadExecutor;
public FitnessEvaluatorVirtualThread(final EAExecutionContext<T> _eaExecutionContext,
final EAConfiguration<T> _eaConfiguration) {
Objects.requireNonNull(_eaExecutionContext);
Objects.requireNonNull(_eaConfiguration);
this.eaExecutionContext = _eaExecutionContext;
this.eaConfiguration = _eaConfiguration;
this.virtualThreadExecutor = Executors.newVirtualThreadPerTaskExecutor();
}
@Override
public void preEvaluation() {
logger.debug("Pre-evaluation setup for virtual thread fitness evaluator");
}
@Override
public void postEvaluation() {
logger.debug("Post-evaluation cleanup for virtual thread fitness evaluator");
}
@Override
public List<T> evaluate(final long generation, final List<Genotype> population) {
Validate.isTrue(generation >= 0);
Objects.requireNonNull(population);
if (population.isEmpty()) {
return new ArrayList<>();
}
logger.debug("Evaluating {} individuals using virtual threads", population.size());
final Fitness<T> fitness = eaConfiguration.fitness();
final List<CompletableFuture<T>> evaluationTasks = new ArrayList<>(population.size());
// Create one virtual thread task per genotype - no partitioning needed
for (final Genotype genotype : population) {
final CompletableFuture<T> evaluationTask = CompletableFuture.supplyAsync(() -> {
return fitness.compute(genotype);
}, virtualThreadExecutor);
evaluationTasks.add(evaluationTask);
}
// Wait for all evaluations to complete and collect results in order
final List<T> fitnessScores = new ArrayList<>(population.size());
for (final CompletableFuture<T> task : evaluationTasks) {
fitnessScores.add(task.join());
}
logger.debug("Completed evaluation of {} individuals", population.size());
return fitnessScores;
}
}