From c5cfbdc67f89e79156c9fb52ff459f83db6eb0a7 Mon Sep 17 00:00:00 2001 From: Radek Oslejsek <oslejsek@fi.muni.cz> Date: Fri, 26 Feb 2021 10:27:47 +0100 Subject: [PATCH] Efficient concurrent thread-safe implementation of HausdorffDistMeshVisitor --- .../mesh/HausdorffDistMeshVisitor.java | 76 ++++++++++++++++--- .../cz/fidentis/analyst/mesh/MeshVisitor.java | 29 ++++--- 2 files changed, 86 insertions(+), 19 deletions(-) diff --git a/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/HausdorffDistMeshVisitor.java b/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/HausdorffDistMeshVisitor.java index f1f0700b..237cae9a 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/HausdorffDistMeshVisitor.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/visitors/mesh/HausdorffDistMeshVisitor.java @@ -8,14 +8,25 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Visitor for Hausdorff distance. * This visitor is instantiated on a single mesh facet or multiple facets. * When applied to other facets, it computes Huasdorff distance to them. + * <p> + * This visitor is thread-safe. A single instance of the visitor can be used + * to inspect multiple mesh models or facets simultaneously. + * </p> * * @author Matej Lukes * @author Radek Oslejsek @@ -23,7 +34,7 @@ import java.util.Set; public class HausdorffDistMeshVisitor extends MeshVisitor { private boolean relativeDistance; - private Map<MeshFacet, List<Double>> distances = new HashMap<>(); + private final Map<MeshFacet, List<Double>> distances = new HashMap<>(); /** * @param mainFacets Facets of which distance to other facets is computed. Must not be {@code null} @@ -31,7 +42,7 @@ public class HausdorffDistMeshVisitor extends MeshVisitor { * to the normal vectors of source facets (normal vectors have to present), * i.e., we can get negative distances. * @param concurrently If {@code true} and this visitor is thread-safe, then - * the visitor is applied concurrently on multiple mesf facets. + * the visitor is applied concurrently when inspecting multiple mesh facets. * @throws IllegalArgumentException if some parametr is wrong */ public HausdorffDistMeshVisitor(Set<MeshFacet> mainFacets, boolean relativeDistance, boolean concurrently) { @@ -51,7 +62,7 @@ public class HausdorffDistMeshVisitor extends MeshVisitor { * to the normal vectors of source facets (normal vectors have to present), * i.e., we can get negative distances. * @param concurrently If {@code true} and this visitor is thread-safe, then - * the visitor is applied concurrently on multiple mesf facets. + * the visitor is applied concurrently when inspecting multiple mesh facets. * @throws IllegalArgumentException if some parametr is wrong */ public HausdorffDistMeshVisitor(MeshFacet mainFacet, boolean relativeDistance, boolean concurrently) { @@ -68,7 +79,7 @@ public class HausdorffDistMeshVisitor extends MeshVisitor { * to the normal vectors of source facets (normal vectors have to present), * i.e., we can get negative distances. * @param concurrently If {@code true} and this visitor is thread-safe, then - * the visitor is applied concurrently on multiple mesf facets. + * the visitor is applied concurrently when inspecting multiple mesh facets. * @throws IllegalArgumentException if some parametr is wrong */ public HausdorffDistMeshVisitor(MeshModel mainModel, boolean relativeDistance, boolean concurrently) { @@ -80,18 +91,65 @@ public class HausdorffDistMeshVisitor extends MeshVisitor { @Override protected void visitMeshFacet(MeshFacet comparedFacet) { + int threads = Runtime.getRuntime().availableProcessors(); + ExecutorService executor = Executors.newFixedThreadPool(threads); + List<Future<MeshVisitor>> results = new LinkedList<>(); + for (Map.Entry<MeshFacet, List<Double>> entry: distances.entrySet()) { List<MeshPoint> vertices = entry.getKey().getVertices(); List<Double> distList = entry.getValue(); - boolean firstComparison = distList.isEmpty(); + if (concurrently()) { + for (int i = 0; i < vertices.size(); i++) { + Point2MeshVisitor visitor = new Point2MeshVisitor(vertices.get(i), relativeDistance, true); + comparedFacet.accept(visitor); + Future<MeshVisitor> result = executor.submit(visitor); // fork and continue + results.add(result); + } + updateFacetDistancesConcurrently(distList, results); + } else { + updateFacetDistancesSequentially(vertices, distList, comparedFacet); + } + } + } + + /** + * Sequentially updates distances of particular mesh. The update has to be + * exclusive (sychronized with multiple threads) + */ + protected synchronized void updateFacetDistancesSequentially( + List<MeshPoint> facetVertices, + List<Double> facetDistances, + MeshFacet comparedFacet) { + + boolean firstComparison = facetDistances.isEmpty(); - for (int i = 0; i < vertices.size(); i++) { - Point2MeshVisitor visitor = new Point2MeshVisitor(vertices.get(i), relativeDistance, concurrently()); - comparedFacet.accept(visitor); + for (int i = 0; i < facetVertices.size(); i++) { + Point2MeshVisitor visitor = new Point2MeshVisitor(facetVertices.get(i), relativeDistance, false); + comparedFacet.accept(visitor); + double dist = visitor.getDistance(); + updateDistances(facetDistances, firstComparison, dist, i); + } + } + + /** + * Sequentially updates distances of particular mesh. The update has to be + * exclusive (sychronized with multiple threads) + */ + protected synchronized void updateFacetDistancesConcurrently( + List<Double> facetDistances, + List<Future<MeshVisitor>> results) { + + boolean firstComparison = facetDistances.isEmpty(); + + try { + for (int i = 0; i < results.size(); i++) { // wait ntil all computations are finished + Point2MeshVisitor visitor = (Point2MeshVisitor) results.get(i).get(); double dist = visitor.getDistance(); - updateDistances(distList, firstComparison, dist, i); + updateDistances(facetDistances, firstComparison, dist, i); } + } catch (InterruptedException | ExecutionException ex) { + Logger.getLogger(HausdorffDistMeshVisitor.class.getName()).log(Level.SEVERE, null, ex); } } diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/MeshVisitor.java b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/MeshVisitor.java index 12315780..85f962a4 100644 --- a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/MeshVisitor.java +++ b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/MeshVisitor.java @@ -10,11 +10,14 @@ import java.util.concurrent.Callable; * Implement this interface whenever you want to define new algorithm over a mesh. * </p> * <p> - * There are two visitation strategies available. - * The {@link cz.fidentis.analyst.mesh.mesh.MeshVisitor#visitMeshFacet} inspects - * the facet immediately. On the contrary, - * the {@link cz.fidentis.analyst.mesh.MeshVisitor#visitConcurrently} enable to - * inspect multiple facets concurretly. The later has to be called from a code like this: + * The visitor can be instatiated either as sequential and concurrent. + * The {@link cz.fidentis.analyst.mesh.mesh.MeshVisitor#visit} inspection method + * of a sequential visitor is applied immediately. On the contrary, if the + * visitor is concurrent and thread-safe, then the methed only stores the inpected + * mesh facet for later concurrent inspection. To trigger the facet inspection, + * a {@link java.util.concurrent.ExecutorService} has to be used to call + * the {@link cz.fidentis.analyst.mesh.mesh.MeshVisitor#call} method asynchronously + * and then wait for results of all triggered visitors. * </p> * * @author Radek Oslejsek @@ -32,8 +35,12 @@ public abstract class MeshVisitor implements Callable<MeshVisitor> { /** * - * @param concurrently If {@code true} and this visitor is thread-safe, then - * the visitor is applied concurrently on multiple mesh facets. + * @param concurrently If {@code true} and the visitor is thread-safe, then + * the visitor is created as concurent + * (the {@link cz.fidentis.analyst.mesh.MeshVisitor#visit} is not executed + * immediately byt postponed for concurrent asynchronous execution). + * Otherwise, the visitor is created as sequential + * (the {@link cz.fidentis.analyst.mesh.MeshVisitor#visit} is executed immediately.) */ public MeshVisitor(boolean concurrently) { this.concurrently = concurrently; @@ -41,9 +48,9 @@ public abstract class MeshVisitor implements Callable<MeshVisitor> { /** * Returns {@code true} if the implementation is thread-safe and then - * a single visitor instance can be applied to multiple mesh facets simultaneously. + * <b>a single visitor instance</b> can be applied to multiple mesh facets simultaneously. * If the visitor is not thread-safe, the concurrent inspection - * via the {@link cz.fidentis.analyst.mesh.MeshVisitor#visitConcurrently} + * via the {@link cz.fidentis.analyst.mesh.MeshVisitor#visit} * is still possible, but with multiple visitor instances - one for each facet. * <p> * Thread-safe implementation means that any read or write from/to the visitor's @@ -75,7 +82,9 @@ public abstract class MeshVisitor implements Callable<MeshVisitor> { protected abstract void visitMeshFacet(MeshFacet facet); /** - * The main visitation method + * The main visitation method, either invoked immediately (if the visitor + * was created a sequentional) or postponed to later parallel execution + * (if the visitor is thread-safe and has been created a concurrent). * * @param facet Mesh facet to be visited. */ -- GitLab