diff --git a/config-example.properties b/config-example.properties index 9464954028f..8f6ba6912f2 100644 --- a/config-example.properties +++ b/config-example.properties @@ -41,7 +41,9 @@ graph.dataaccess=RAM_STORE # # Disable the speed-up mode (contraction hierarchies, CH) via enabling the flexibility mode: # prepare.chWeighting=no - +# +# To make preparation faster for multiple flagEncoders you can increase the default threads if you have enough RAM +# prepare.threads=1 ##### Web ##### # if you want to support jsonp response type you need to add it explicitely here. By default it is disabled for diff --git a/core/src/main/java/com/graphhopper/GraphHopper.java b/core/src/main/java/com/graphhopper/GraphHopper.java index ff61582d21d..c4eccc96dff 100644 --- a/core/src/main/java/com/graphhopper/GraphHopper.java +++ b/core/src/main/java/com/graphhopper/GraphHopper.java @@ -39,6 +39,8 @@ import java.text.SimpleDateFormat; import java.util.*; import java.util.Map.Entry; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; /** * Easy to use access point to configure import and (offline) routing. @@ -80,6 +82,8 @@ public class GraphHopper implements GraphHopperAPI private boolean doPrepare = true; private boolean chEnabled = true; private String chWeightingStr = "fastest"; + private int chPrepareThreads = -1; + private ExecutorService chPreparePool; private int preparePeriodicUpdates = -1; private int prepareLazyUpdates = -1; private int prepareNeighborUpdates = -1; @@ -96,6 +100,7 @@ public class GraphHopper implements GraphHopperAPI public GraphHopper() { + setCHPrepareThreads(1); } /** @@ -304,6 +309,22 @@ public String getCHWeighting() return chWeightingStr; } + /** + * This method changes the number of threads used for preparation on import. Default is 1. Make + * sure that you have enough memory to increase this number! + */ + public GraphHopper setCHPrepareThreads( int prepareThreads ) + { + this.chPrepareThreads = prepareThreads; + this.chPreparePool = java.util.concurrent.Executors.newSingleThreadExecutor(); + return this; + } + + public int getCHPrepareThreads() + { + return chPrepareThreads; + } + /** * Disables the "CH-preparation" preparation only. Use only if you know what you do. To disable * the full usage of CH use setCHEnable(false) instead. @@ -565,6 +586,7 @@ public GraphHopper init( CmdArgs args ) // prepare CH doPrepare = args.getBool("prepare.doPrepare", doPrepare); + setCHPrepareThreads(args.getInt("prepare.threads", chPrepareThreads)); String tmpCHWeighting = args.get("prepare.chWeighting", "fastest"); chEnabled = "fastest".equals(tmpCHWeighting) || "shortest".equals(tmpCHWeighting); @@ -815,9 +837,9 @@ protected void createCHPreparations() if (algoFactories.isEmpty()) throw new IllegalStateException("No algorithm factories found. Call load before?"); - Set set = new LinkedHashSet(algoFactories.keySet()); + Set orderedSet = new LinkedHashSet(algoFactories.keySet()); algoFactories.clear(); - for (Weighting weighting : set) + for (Weighting weighting : orderedSet) { PrepareContractionHierarchies tmpPrepareCH = new PrepareContractionHierarchies( new GHDirectory("", DAType.RAM_INT), ghStorage, ghStorage.getGraph(CHGraph.class, weighting), @@ -1108,18 +1130,46 @@ protected void prepare() if (tmpPrepare) { ensureWriteAccess(); + + if (chPrepareThreads > 1 && dataAccessType.isMMap() && !dataAccessType.isSynched()) + throw new IllegalStateException("You cannot execute CH preparation in parallel for MMAP without synching! Specify MMAP_SYNC or use 1 thread only"); + ghStorage.freeze(); int counter = 0; - for (Entry entry : algoFactories.entrySet()) + for (final Entry entry : algoFactories.entrySet()) { logger.info((++counter) + "/" + algoFactories.entrySet().size() + " calling prepare.doWork for " + entry.getKey() + " ... (" + Helper.getMemInfo() + ")"); if (!(entry.getValue() instanceof PrepareContractionHierarchies)) throw new IllegalStateException("RoutingAlgorithmFactory is not suited for CH preparation " + entry.getValue()); - ((PrepareContractionHierarchies) entry.getValue()).doWork(); + final String name = CHGraphImpl.weightingToFileName(entry.getKey()); + chPreparePool.execute(new Runnable() + { + @Override + public void run() + { + // toString is not taken into account so we need to cheat, see http://stackoverflow.com/q/6113746/194609 for other options + Thread.currentThread().setName(name); + + PrepareContractionHierarchies pch = (PrepareContractionHierarchies) entry.getValue(); + pch.doWork(); + ghStorage.getProperties().put("prepare.date." + name, formatDateTime(new Date())); + } + }); + } + + chPreparePool.shutdown(); + try + { + if (!chPreparePool.awaitTermination(Integer.MAX_VALUE, TimeUnit.DAYS)) + chPreparePool.shutdownNow(); + + } catch (InterruptedException ie) + { + chPreparePool.shutdownNow(); + Thread.currentThread().interrupt(); } - ghStorage.getProperties().put("prepare.date", formatDateTime(new Date())); } ghStorage.getProperties().put("prepare.done", tmpPrepare); } diff --git a/core/src/main/java/com/graphhopper/routing/ch/PrepareContractionHierarchies.java b/core/src/main/java/com/graphhopper/routing/ch/PrepareContractionHierarchies.java index c516a28ce9f..237e159dc27 100644 --- a/core/src/main/java/com/graphhopper/routing/ch/PrepareContractionHierarchies.java +++ b/core/src/main/java/com/graphhopper/routing/ch/PrepareContractionHierarchies.java @@ -97,7 +97,7 @@ public PrepareContractionHierarchies( Directory dir, GraphHopperStorage ghStorag levelFilter = new LevelEdgeFilter(prepareGraph); prepareWeighting = new PreparationWeighting(weighting); - originalEdges = dir.find("original_edges_" + prepareGraph.weightingToFileName(weighting)); + originalEdges = dir.find("original_edges_" + CHGraphImpl.weightingToFileName(weighting)); originalEdges.create(1000); } @@ -400,6 +400,11 @@ public double getNeighborTime() return neighborTime; } + public Weighting getWeighting() + { + return prepareGraph.getWeighting(); + } + public void close() { prepareAlgo.close(); diff --git a/core/src/main/java/com/graphhopper/routing/util/CarFlagEncoder.java b/core/src/main/java/com/graphhopper/routing/util/CarFlagEncoder.java index d785ea0d341..f46df520dbf 100644 --- a/core/src/main/java/com/graphhopper/routing/util/CarFlagEncoder.java +++ b/core/src/main/java/com/graphhopper/routing/util/CarFlagEncoder.java @@ -46,9 +46,6 @@ public class CarFlagEncoder extends AbstractFlagEncoder */ protected final Map defaultSpeedMap = new HashMap(); - /** - * Should be only instantied via EncodingManager - */ public CarFlagEncoder() { this(5, 5, 0); diff --git a/core/src/main/java/com/graphhopper/routing/util/MotorcycleFlagEncoder.java b/core/src/main/java/com/graphhopper/routing/util/MotorcycleFlagEncoder.java index c5e0c585da4..3d166949d2b 100644 --- a/core/src/main/java/com/graphhopper/routing/util/MotorcycleFlagEncoder.java +++ b/core/src/main/java/com/graphhopper/routing/util/MotorcycleFlagEncoder.java @@ -37,6 +37,11 @@ public class MotorcycleFlagEncoder extends CarFlagEncoder private final HashSet avoidSet = new HashSet(); private final HashSet preferSet = new HashSet(); + public MotorcycleFlagEncoder() + { + this(5, 5, 0); + } + public MotorcycleFlagEncoder( PMap properties ) { this( diff --git a/core/src/main/java/com/graphhopper/storage/BaseGraph.java b/core/src/main/java/com/graphhopper/storage/BaseGraph.java index 9dfe52fbb35..04ad9f3ffdf 100644 --- a/core/src/main/java/com/graphhopper/storage/BaseGraph.java +++ b/core/src/main/java/com/graphhopper/storage/BaseGraph.java @@ -367,7 +367,7 @@ void setSegmentSize( int bytes ) extStorage.setSegmentSize(bytes); } - void freeze() + synchronized void freeze() { if (isFrozen()) throw new IllegalStateException("base graph already frozen"); @@ -376,7 +376,7 @@ void freeze() listener.freeze(); } - boolean isFrozen() + synchronized boolean isFrozen() { return frozen; } diff --git a/core/src/main/java/com/graphhopper/storage/CHGraphImpl.java b/core/src/main/java/com/graphhopper/storage/CHGraphImpl.java index 64155fb13e4..1f4f4a0ed71 100644 --- a/core/src/main/java/com/graphhopper/storage/CHGraphImpl.java +++ b/core/src/main/java/com/graphhopper/storage/CHGraphImpl.java @@ -64,7 +64,7 @@ public class CHGraphImpl implements CHGraph, Storable this.weighting = w; this.baseGraph = baseGraph; - final String name = weightingToFileName(w); + final String name = CHGraphImpl.weightingToFileName(w); this.nodesCH = dir.find("nodes_ch_" + name); this.shortcuts = dir.find("shortcuts_" + name); this.chEdgeAccess = new EdgeAccess(shortcuts, baseGraph.bitUtil) @@ -139,7 +139,7 @@ public final Weighting getWeighting() /** * Replaces all characters which are not numbers, characters or underscores with underscores */ - public String weightingToFileName( Weighting w ) + public static String weightingToFileName( Weighting w ) { return w.toString().toLowerCase().replaceAll("\\W+", "_"); } diff --git a/core/src/main/java/com/graphhopper/storage/GraphHopperStorage.java b/core/src/main/java/com/graphhopper/storage/GraphHopperStorage.java index 553294aebde..7c70837c46f 100644 --- a/core/src/main/java/com/graphhopper/storage/GraphHopperStorage.java +++ b/core/src/main/java/com/graphhopper/storage/GraphHopperStorage.java @@ -335,7 +335,7 @@ public long getCapacity() * Avoid that edges and nodes of the base graph are further modified. Necessary as hook for e.g. * ch graphs on top to initilize themself */ - public void freeze() + public synchronized void freeze() { if (!baseGraph.isFrozen()) baseGraph.freeze(); diff --git a/core/src/test/java/com/graphhopper/GraphHopperTest.java b/core/src/test/java/com/graphhopper/GraphHopperTest.java index aea15810836..77e46423527 100644 --- a/core/src/test/java/com/graphhopper/GraphHopperTest.java +++ b/core/src/test/java/com/graphhopper/GraphHopperTest.java @@ -19,11 +19,11 @@ import com.graphhopper.reader.DataReader; import com.graphhopper.routing.*; +import com.graphhopper.routing.ch.PrepareContractionHierarchies; import com.graphhopper.routing.util.*; import com.graphhopper.storage.*; import com.graphhopper.storage.index.QueryResult; import com.graphhopper.util.CmdArgs; -import com.graphhopper.util.GHUtility; import com.graphhopper.util.Helper; import com.graphhopper.util.Instruction; import com.graphhopper.util.shapes.GHPoint; @@ -33,6 +33,7 @@ import java.io.File; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -592,8 +593,7 @@ public void testGetPathsDirectionEnforcement1() { // Test enforce start direction // Note: This Test does not pass for CH enabled - - GraphHopper instance = initSquareGraphInstance(false); + instance = createSquareGraphInstance(false); // Start in middle of edge 4-5 GHPoint start = new GHPoint(0.0015, 0.002); @@ -614,8 +614,7 @@ public void testGetPathsDirectionEnforcement1() public void testGetPathsDirectionEnforcement2() { // Test enforce start & end direction - - GraphHopper instance = initSquareGraphInstance(false); + instance = createSquareGraphInstance(false); // Start in middle of edge 4-5 GHPoint start = new GHPoint(0.0015, 0.002); @@ -644,7 +643,7 @@ public void testGetPathsDirectionEnforcement2() @Test public void testGetPathsDirectionEnforcement3() { - GraphHopper instance = initSquareGraphInstance(false); + instance = createSquareGraphInstance(false); // Start in middle of edge 4-5 GHPoint start = new GHPoint(0.0015, 0.002); @@ -666,8 +665,7 @@ public void testGetPathsDirectionEnforcement3() public void testGetPathsDirectionEnforcement4() { // Test straight via routing - - GraphHopper instance = initSquareGraphInstance(false); + instance = createSquareGraphInstance(false); // Start in middle of edge 4-5 GHPoint start = new GHPoint(0.0015, 0.002); @@ -693,8 +691,7 @@ public void testGetPathsDirectionEnforcement4() public void testGetPathsDirectionEnforcement5() { // Test independence of previous enforcement for subsequent pathes - - GraphHopper instance = initSquareGraphInstance(false); + instance = createSquareGraphInstance(false); // Start in middle of edge 4-5 GHPoint start = new GHPoint(0.0015, 0.002); @@ -720,8 +717,7 @@ public void testGetPathsDirectionEnforcement5() public void testGetPathsDirectionEnforcement6() { // Test if query results at tower nodes are ignored - - GraphHopper instance = initSquareGraphInstance(false); + instance = createSquareGraphInstance(false); // QueryPoints directly on TowerNodes GHPoint start = new GHPoint(0, 0); @@ -741,7 +737,7 @@ public void testGetPathsDirectionEnforcement6() }, paths.get(1).calcNodes().toArray()); } - private GraphHopper initSquareGraphInstance( boolean withCH ) + private GraphHopper createSquareGraphInstance( boolean withCH ) { CarFlagEncoder carEncoder = new CarFlagEncoder(); EncodingManager encodingManager = new EncodingManager(carEncoder); @@ -780,15 +776,15 @@ private GraphHopper initSquareGraphInstance( boolean withCH ) g.edge(5, 8, 110, true); g.edge(7, 8, 110, true); - instance = new GraphHopper(). + GraphHopper tmp = new GraphHopper(). putAlgorithmFactory(weighting, null). setCHEnable(withCH). setCHWeighting("fastest"). setEncodingManager(encodingManager); - instance.setGraphHopperStorage(g); - instance.postProcessing(); + tmp.setGraphHopperStorage(g); + tmp.postProcessing(); - return instance; + return tmp; } @Test @@ -797,20 +793,20 @@ public void testCustomFactoryForNoneCH() CarFlagEncoder carEncoder = new CarFlagEncoder(); EncodingManager em = new EncodingManager(carEncoder); Weighting weighting = new FastestWeighting(carEncoder); - GraphHopper closableInstance = new GraphHopper().setStoreOnFlush(true). + instance = new GraphHopper().setStoreOnFlush(false). setCHEnable(false). setEncodingManager(em). setGraphHopperLocation(ghLoc). setOSMFile(testOsm); RoutingAlgorithmFactory af = new RoutingAlgorithmFactorySimple(); - closableInstance.putAlgorithmFactory(weighting, af); - closableInstance.importOrLoad(); + instance.putAlgorithmFactory(weighting, af); + instance.importOrLoad(); - assertTrue(af == closableInstance.getAlgorithmFactory(weighting)); + assertTrue(af == instance.getAlgorithmFactory(weighting)); // test that hints are passwed to algorithm opts final AtomicInteger cnt = new AtomicInteger(0); - closableInstance.putAlgorithmFactory(weighting, new RoutingAlgorithmFactorySimple() + instance.putAlgorithmFactory(weighting, new RoutingAlgorithmFactorySimple() { @Override public RoutingAlgorithm createAlgo( Graph g, AlgorithmOptions opts ) @@ -822,7 +818,38 @@ public RoutingAlgorithm createAlgo( Graph g, AlgorithmOptions opts ) }); GHRequest req = new GHRequest(51.2492152, 9.4317166, 51.2, 9.4); req.getHints().put("test", false); - closableInstance.route(req); + instance.route(req); assertEquals(1, cnt.get()); } + + @Test + public void testMultipleCHPreparationsInParallel() + { + // try all parallelization modes + for (int threadCount = 1; threadCount < 6; threadCount++) + { + EncodingManager em = new EncodingManager(Arrays.asList(new CarFlagEncoder(), new MotorcycleFlagEncoder(), + new MountainBikeFlagEncoder(), new RacingBikeFlagEncoder(), new FootFlagEncoder()), + 8); + + GraphHopper tmpGH = new GraphHopper().setStoreOnFlush(false). + setEncodingManager(em). + setGraphHopperLocation(ghLoc). + setOSMFile(testOsm).setCHPrepareThreads(threadCount); + + tmpGH.importOrLoad(); + + assertEquals(5, tmpGH.getAlgorithmFactories().size()); + for (RoutingAlgorithmFactory raf : tmpGH.getAlgorithmFactories()) + { + PrepareContractionHierarchies pch = (PrepareContractionHierarchies) raf; + assertTrue("Preparation wasn't run! [" + threadCount + "]", pch.isPrepared()); + + String key = "prepare.date." + CHGraphImpl.weightingToFileName(pch.getWeighting()); + String value = tmpGH.getGraphHopperStorage().getProperties().get(key); + assertTrue(key + " should contain finish time/date [" + threadCount + "]", !value.isEmpty()); + } + tmpGH.close(); + } + } }