Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
implementing parallelization of CH preparation, fixes #479
  • Loading branch information
Peter committed Sep 14, 2015
1 parent db6f9cf commit 61f3b1a
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 38 deletions.
4 changes: 3 additions & 1 deletion config-example.properties
Expand Up @@ -41,7 +41,9 @@ graph.dataaccess=RAM_STORE
# #
# Disable the speed-up mode (contraction hierarchies, CH) via enabling the flexibility mode: # Disable the speed-up mode (contraction hierarchies, CH) via enabling the flexibility mode:
# prepare.chWeighting=no # 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 ##### ##### Web #####
# if you want to support jsonp response type you need to add it explicitely here. By default it is disabled for # if you want to support jsonp response type you need to add it explicitely here. By default it is disabled for
Expand Down
60 changes: 55 additions & 5 deletions core/src/main/java/com/graphhopper/GraphHopper.java
Expand Up @@ -39,6 +39,8 @@
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
import java.util.Map.Entry; 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. * Easy to use access point to configure import and (offline) routing.
Expand Down Expand Up @@ -80,6 +82,8 @@ public class GraphHopper implements GraphHopperAPI
private boolean doPrepare = true; private boolean doPrepare = true;
private boolean chEnabled = true; private boolean chEnabled = true;
private String chWeightingStr = "fastest"; private String chWeightingStr = "fastest";
private int chPrepareThreads = -1;
private ExecutorService chPreparePool;
private int preparePeriodicUpdates = -1; private int preparePeriodicUpdates = -1;
private int prepareLazyUpdates = -1; private int prepareLazyUpdates = -1;
private int prepareNeighborUpdates = -1; private int prepareNeighborUpdates = -1;
Expand All @@ -96,6 +100,7 @@ public class GraphHopper implements GraphHopperAPI


public GraphHopper() public GraphHopper()
{ {
setCHPrepareThreads(1);
} }


/** /**
Expand Down Expand Up @@ -304,6 +309,22 @@ public String getCHWeighting()
return chWeightingStr; 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 * 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. * the full usage of CH use setCHEnable(false) instead.
Expand Down Expand Up @@ -565,6 +586,7 @@ public GraphHopper init( CmdArgs args )


// prepare CH // prepare CH
doPrepare = args.getBool("prepare.doPrepare", doPrepare); doPrepare = args.getBool("prepare.doPrepare", doPrepare);
setCHPrepareThreads(args.getInt("prepare.threads", chPrepareThreads));


String tmpCHWeighting = args.get("prepare.chWeighting", "fastest"); String tmpCHWeighting = args.get("prepare.chWeighting", "fastest");
chEnabled = "fastest".equals(tmpCHWeighting) || "shortest".equals(tmpCHWeighting); chEnabled = "fastest".equals(tmpCHWeighting) || "shortest".equals(tmpCHWeighting);
Expand Down Expand Up @@ -815,9 +837,9 @@ protected void createCHPreparations()
if (algoFactories.isEmpty()) if (algoFactories.isEmpty())
throw new IllegalStateException("No algorithm factories found. Call load before?"); throw new IllegalStateException("No algorithm factories found. Call load before?");


Set<Weighting> set = new LinkedHashSet<Weighting>(algoFactories.keySet()); Set<Weighting> orderedSet = new LinkedHashSet<Weighting>(algoFactories.keySet());
algoFactories.clear(); algoFactories.clear();
for (Weighting weighting : set) for (Weighting weighting : orderedSet)
{ {
PrepareContractionHierarchies tmpPrepareCH = new PrepareContractionHierarchies( PrepareContractionHierarchies tmpPrepareCH = new PrepareContractionHierarchies(
new GHDirectory("", DAType.RAM_INT), ghStorage, ghStorage.getGraph(CHGraph.class, weighting), new GHDirectory("", DAType.RAM_INT), ghStorage, ghStorage.getGraph(CHGraph.class, weighting),
Expand Down Expand Up @@ -1108,18 +1130,46 @@ protected void prepare()
if (tmpPrepare) if (tmpPrepare)
{ {
ensureWriteAccess(); 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(); ghStorage.freeze();


int counter = 0; int counter = 0;
for (Entry<Weighting, RoutingAlgorithmFactory> entry : algoFactories.entrySet()) for (final Entry<Weighting, RoutingAlgorithmFactory> entry : algoFactories.entrySet())
{ {
logger.info((++counter) + "/" + algoFactories.entrySet().size() + " calling prepare.doWork for " + entry.getKey() + " ... (" + Helper.getMemInfo() + ")"); logger.info((++counter) + "/" + algoFactories.entrySet().size() + " calling prepare.doWork for " + entry.getKey() + " ... (" + Helper.getMemInfo() + ")");
if (!(entry.getValue() instanceof PrepareContractionHierarchies)) if (!(entry.getValue() instanceof PrepareContractionHierarchies))
throw new IllegalStateException("RoutingAlgorithmFactory is not suited for CH preparation " + entry.getValue()); 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); ghStorage.getProperties().put("prepare.done", tmpPrepare);
} }
Expand Down
Expand Up @@ -97,7 +97,7 @@ public PrepareContractionHierarchies( Directory dir, GraphHopperStorage ghStorag
levelFilter = new LevelEdgeFilter(prepareGraph); levelFilter = new LevelEdgeFilter(prepareGraph);


prepareWeighting = new PreparationWeighting(weighting); prepareWeighting = new PreparationWeighting(weighting);
originalEdges = dir.find("original_edges_" + prepareGraph.weightingToFileName(weighting)); originalEdges = dir.find("original_edges_" + CHGraphImpl.weightingToFileName(weighting));
originalEdges.create(1000); originalEdges.create(1000);
} }


Expand Down Expand Up @@ -400,6 +400,11 @@ public double getNeighborTime()
return neighborTime; return neighborTime;
} }


public Weighting getWeighting()
{
return prepareGraph.getWeighting();
}

public void close() public void close()
{ {
prepareAlgo.close(); prepareAlgo.close();
Expand Down
Expand Up @@ -46,9 +46,6 @@ public class CarFlagEncoder extends AbstractFlagEncoder
*/ */
protected final Map<String, Integer> defaultSpeedMap = new HashMap<String, Integer>(); protected final Map<String, Integer> defaultSpeedMap = new HashMap<String, Integer>();


/**
* Should be only instantied via EncodingManager
*/
public CarFlagEncoder() public CarFlagEncoder()
{ {
this(5, 5, 0); this(5, 5, 0);
Expand Down
Expand Up @@ -37,6 +37,11 @@ public class MotorcycleFlagEncoder extends CarFlagEncoder
private final HashSet<String> avoidSet = new HashSet<String>(); private final HashSet<String> avoidSet = new HashSet<String>();
private final HashSet<String> preferSet = new HashSet<String>(); private final HashSet<String> preferSet = new HashSet<String>();


public MotorcycleFlagEncoder()
{
this(5, 5, 0);
}

public MotorcycleFlagEncoder( PMap properties ) public MotorcycleFlagEncoder( PMap properties )
{ {
this( this(
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/com/graphhopper/storage/BaseGraph.java
Expand Up @@ -367,7 +367,7 @@ void setSegmentSize( int bytes )
extStorage.setSegmentSize(bytes); extStorage.setSegmentSize(bytes);
} }


void freeze() synchronized void freeze()
{ {
if (isFrozen()) if (isFrozen())
throw new IllegalStateException("base graph already frozen"); throw new IllegalStateException("base graph already frozen");
Expand All @@ -376,7 +376,7 @@ void freeze()
listener.freeze(); listener.freeze();
} }


boolean isFrozen() synchronized boolean isFrozen()
{ {
return frozen; return frozen;
} }
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/com/graphhopper/storage/CHGraphImpl.java
Expand Up @@ -64,7 +64,7 @@ public class CHGraphImpl implements CHGraph, Storable<CHGraph>


this.weighting = w; this.weighting = w;
this.baseGraph = baseGraph; this.baseGraph = baseGraph;
final String name = weightingToFileName(w); final String name = CHGraphImpl.weightingToFileName(w);
this.nodesCH = dir.find("nodes_ch_" + name); this.nodesCH = dir.find("nodes_ch_" + name);
this.shortcuts = dir.find("shortcuts_" + name); this.shortcuts = dir.find("shortcuts_" + name);
this.chEdgeAccess = new EdgeAccess(shortcuts, baseGraph.bitUtil) this.chEdgeAccess = new EdgeAccess(shortcuts, baseGraph.bitUtil)
Expand Down Expand Up @@ -139,7 +139,7 @@ public final Weighting getWeighting()
/** /**
* Replaces all characters which are not numbers, characters or underscores with underscores * 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+", "_"); return w.toString().toLowerCase().replaceAll("\\W+", "_");
} }
Expand Down
Expand Up @@ -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. * 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 * ch graphs on top to initilize themself
*/ */
public void freeze() public synchronized void freeze()
{ {
if (!baseGraph.isFrozen()) if (!baseGraph.isFrozen())
baseGraph.freeze(); baseGraph.freeze();
Expand Down
73 changes: 50 additions & 23 deletions core/src/test/java/com/graphhopper/GraphHopperTest.java
Expand Up @@ -19,11 +19,11 @@


import com.graphhopper.reader.DataReader; import com.graphhopper.reader.DataReader;
import com.graphhopper.routing.*; import com.graphhopper.routing.*;
import com.graphhopper.routing.ch.PrepareContractionHierarchies;
import com.graphhopper.routing.util.*; import com.graphhopper.routing.util.*;
import com.graphhopper.storage.*; import com.graphhopper.storage.*;
import com.graphhopper.storage.index.QueryResult; import com.graphhopper.storage.index.QueryResult;
import com.graphhopper.util.CmdArgs; import com.graphhopper.util.CmdArgs;
import com.graphhopper.util.GHUtility;
import com.graphhopper.util.Helper; import com.graphhopper.util.Helper;
import com.graphhopper.util.Instruction; import com.graphhopper.util.Instruction;
import com.graphhopper.util.shapes.GHPoint; import com.graphhopper.util.shapes.GHPoint;
Expand All @@ -33,6 +33,7 @@


import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
Expand Down Expand Up @@ -592,8 +593,7 @@ public void testGetPathsDirectionEnforcement1()
{ {
// Test enforce start direction // Test enforce start direction
// Note: This Test does not pass for CH enabled // Note: This Test does not pass for CH enabled

instance = createSquareGraphInstance(false);
GraphHopper instance = initSquareGraphInstance(false);


// Start in middle of edge 4-5 // Start in middle of edge 4-5
GHPoint start = new GHPoint(0.0015, 0.002); GHPoint start = new GHPoint(0.0015, 0.002);
Expand All @@ -614,8 +614,7 @@ public void testGetPathsDirectionEnforcement1()
public void testGetPathsDirectionEnforcement2() public void testGetPathsDirectionEnforcement2()
{ {
// Test enforce start & end direction // Test enforce start & end direction

instance = createSquareGraphInstance(false);
GraphHopper instance = initSquareGraphInstance(false);


// Start in middle of edge 4-5 // Start in middle of edge 4-5
GHPoint start = new GHPoint(0.0015, 0.002); GHPoint start = new GHPoint(0.0015, 0.002);
Expand Down Expand Up @@ -644,7 +643,7 @@ public void testGetPathsDirectionEnforcement2()
@Test @Test
public void testGetPathsDirectionEnforcement3() public void testGetPathsDirectionEnforcement3()
{ {
GraphHopper instance = initSquareGraphInstance(false); instance = createSquareGraphInstance(false);


// Start in middle of edge 4-5 // Start in middle of edge 4-5
GHPoint start = new GHPoint(0.0015, 0.002); GHPoint start = new GHPoint(0.0015, 0.002);
Expand All @@ -666,8 +665,7 @@ public void testGetPathsDirectionEnforcement3()
public void testGetPathsDirectionEnforcement4() public void testGetPathsDirectionEnforcement4()
{ {
// Test straight via routing // Test straight via routing

instance = createSquareGraphInstance(false);
GraphHopper instance = initSquareGraphInstance(false);


// Start in middle of edge 4-5 // Start in middle of edge 4-5
GHPoint start = new GHPoint(0.0015, 0.002); GHPoint start = new GHPoint(0.0015, 0.002);
Expand All @@ -693,8 +691,7 @@ public void testGetPathsDirectionEnforcement4()
public void testGetPathsDirectionEnforcement5() public void testGetPathsDirectionEnforcement5()
{ {
// Test independence of previous enforcement for subsequent pathes // Test independence of previous enforcement for subsequent pathes

instance = createSquareGraphInstance(false);
GraphHopper instance = initSquareGraphInstance(false);


// Start in middle of edge 4-5 // Start in middle of edge 4-5
GHPoint start = new GHPoint(0.0015, 0.002); GHPoint start = new GHPoint(0.0015, 0.002);
Expand All @@ -720,8 +717,7 @@ public void testGetPathsDirectionEnforcement5()
public void testGetPathsDirectionEnforcement6() public void testGetPathsDirectionEnforcement6()
{ {
// Test if query results at tower nodes are ignored // Test if query results at tower nodes are ignored

instance = createSquareGraphInstance(false);
GraphHopper instance = initSquareGraphInstance(false);


// QueryPoints directly on TowerNodes // QueryPoints directly on TowerNodes
GHPoint start = new GHPoint(0, 0); GHPoint start = new GHPoint(0, 0);
Expand All @@ -741,7 +737,7 @@ public void testGetPathsDirectionEnforcement6()
}, paths.get(1).calcNodes().toArray()); }, paths.get(1).calcNodes().toArray());
} }


private GraphHopper initSquareGraphInstance( boolean withCH ) private GraphHopper createSquareGraphInstance( boolean withCH )
{ {
CarFlagEncoder carEncoder = new CarFlagEncoder(); CarFlagEncoder carEncoder = new CarFlagEncoder();
EncodingManager encodingManager = new EncodingManager(carEncoder); EncodingManager encodingManager = new EncodingManager(carEncoder);
Expand Down Expand Up @@ -780,15 +776,15 @@ private GraphHopper initSquareGraphInstance( boolean withCH )
g.edge(5, 8, 110, true); g.edge(5, 8, 110, true);
g.edge(7, 8, 110, true); g.edge(7, 8, 110, true);


instance = new GraphHopper(). GraphHopper tmp = new GraphHopper().
putAlgorithmFactory(weighting, null). putAlgorithmFactory(weighting, null).
setCHEnable(withCH). setCHEnable(withCH).
setCHWeighting("fastest"). setCHWeighting("fastest").
setEncodingManager(encodingManager); setEncodingManager(encodingManager);
instance.setGraphHopperStorage(g); tmp.setGraphHopperStorage(g);
instance.postProcessing(); tmp.postProcessing();


return instance; return tmp;
} }


@Test @Test
Expand All @@ -797,20 +793,20 @@ public void testCustomFactoryForNoneCH()
CarFlagEncoder carEncoder = new CarFlagEncoder(); CarFlagEncoder carEncoder = new CarFlagEncoder();
EncodingManager em = new EncodingManager(carEncoder); EncodingManager em = new EncodingManager(carEncoder);
Weighting weighting = new FastestWeighting(carEncoder); Weighting weighting = new FastestWeighting(carEncoder);
GraphHopper closableInstance = new GraphHopper().setStoreOnFlush(true). instance = new GraphHopper().setStoreOnFlush(false).
setCHEnable(false). setCHEnable(false).
setEncodingManager(em). setEncodingManager(em).
setGraphHopperLocation(ghLoc). setGraphHopperLocation(ghLoc).
setOSMFile(testOsm); setOSMFile(testOsm);
RoutingAlgorithmFactory af = new RoutingAlgorithmFactorySimple(); RoutingAlgorithmFactory af = new RoutingAlgorithmFactorySimple();
closableInstance.putAlgorithmFactory(weighting, af); instance.putAlgorithmFactory(weighting, af);
closableInstance.importOrLoad(); instance.importOrLoad();


assertTrue(af == closableInstance.getAlgorithmFactory(weighting)); assertTrue(af == instance.getAlgorithmFactory(weighting));


// test that hints are passwed to algorithm opts // test that hints are passwed to algorithm opts
final AtomicInteger cnt = new AtomicInteger(0); final AtomicInteger cnt = new AtomicInteger(0);
closableInstance.putAlgorithmFactory(weighting, new RoutingAlgorithmFactorySimple() instance.putAlgorithmFactory(weighting, new RoutingAlgorithmFactorySimple()
{ {
@Override @Override
public RoutingAlgorithm createAlgo( Graph g, AlgorithmOptions opts ) public RoutingAlgorithm createAlgo( Graph g, AlgorithmOptions opts )
Expand All @@ -822,7 +818,38 @@ public RoutingAlgorithm createAlgo( Graph g, AlgorithmOptions opts )
}); });
GHRequest req = new GHRequest(51.2492152, 9.4317166, 51.2, 9.4); GHRequest req = new GHRequest(51.2492152, 9.4317166, 51.2, 9.4);
req.getHints().put("test", false); req.getHints().put("test", false);
closableInstance.route(req); instance.route(req);
assertEquals(1, cnt.get()); 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();
}
}
} }

0 comments on commit 61f3b1a

Please sign in to comment.