Skip to content

Commit

Permalink
Add NodeJs bindings benchmarks (#7004)
Browse files Browse the repository at this point in the history
  • Loading branch information
SiarheiFedartsou committed Jul 26, 2024
1 parent 7802f86 commit 84f12c7
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 7 deletions.
17 changes: 15 additions & 2 deletions .github/workflows/osrm-backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,11 @@ jobs:
key: v1-data-osm-pbf
restore-keys: |
v1-data-osm-pbf
- name: Use Node 20
if: ${{ matrix.NODE_PACKAGE_TESTS_ONLY == 'ON' }}
uses: actions/setup-node@v4
with:
node-version: 20
- name: Enable compiler cache
uses: actions/cache@v4
with:
Expand Down Expand Up @@ -722,18 +727,24 @@ jobs:
path: base
- name: Build Base Branch
run: |
cd base
npm ci --ignore-scripts
cd ..
mkdir base/build
cd base/build
cmake -DENABLE_CONAN=ON -DCMAKE_BUILD_TYPE=Release ..
cmake -DENABLE_CONAN=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_NODE_BINDINGS=ON ..
make -j$(nproc)
make -j$(nproc) benchmarks
cd ..
make -C test/data
- name: Build PR Branch
run: |
cd pr
npm ci --ignore-scripts
cd ..
mkdir -p pr/build
cd pr/build
cmake -DENABLE_CONAN=ON -DCMAKE_BUILD_TYPE=Release ..
cmake -DENABLE_CONAN=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_NODE_BINDINGS=ON ..
make -j$(nproc)
make -j$(nproc) benchmarks
cd ..
Expand All @@ -745,6 +756,7 @@ jobs:
run: |
sudo mount -t tmpfs -o size=4g none /opt/benchmarks
cp -rf pr/build /opt/benchmarks/build
cp -rf pr/lib /opt/benchmarks/lib
mkdir -p /opt/benchmarks/test
cp -rf pr/test/data /opt/benchmarks/test/data
cp -rf pr/profiles /opt/benchmarks/profiles
Expand All @@ -755,6 +767,7 @@ jobs:
run: |
sudo mount -t tmpfs -o size=4g none /opt/benchmarks
cp -rf base/build /opt/benchmarks/build
cp -rf base/lib /opt/benchmarks/lib
mkdir -p /opt/benchmarks/test
cp -rf base/test/data /opt/benchmarks/test/data
cp -rf base/profiles /opt/benchmarks/profiles
Expand Down
13 changes: 12 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"private": false,
"description": "The Open Source Routing Machine is a high performance routing engine written in C++ designed to run on OpenStreetMap data.",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.11"
"@mapbox/node-pre-gyp": "^1.0.11",
"seedrandom": "^3.0.5"
},
"browserify": {
"transform": [
Expand Down Expand Up @@ -57,19 +58,21 @@
"jsonpath": "^1.1.1",
"mkdirp": "^0.5.6",
"node-addon-api": "^5.0.0",
"node-cmake": "^2.5.1",
"node-timeout": "0.0.4",
"polyline": "^0.2.0",
"request": "^2.88.2",
"rimraf": "^2.7.1",
"tape": "^4.16.0",
"turf": "^3.0.14",
"uglify-js": "^3.17.0",
"xmlbuilder": "^4.2.1",
"node-cmake": "^2.5.1"
"xmlbuilder": "^4.2.1"
},
"main": "lib/index.js",
"binary": {
"napi_versions": [8],
"napi_versions": [
8
],
"module_name": "node_osrm",
"module_path": "./lib/binding_napi_v{napi_build_version}/",
"host": "https://github.com",
Expand Down
211 changes: 211 additions & 0 deletions scripts/ci/bench.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const seedrandom = require('seedrandom');


let RNG;

class GPSData {
constructor(gpsTracesFilePath) {
this.tracks = {};
this.coordinates = [];
this.trackIds = [];
this._loadGPSTraces(gpsTracesFilePath);
}

_loadGPSTraces(gpsTracesFilePath) {
const expandedPath = path.resolve(gpsTracesFilePath);
const data = fs.readFileSync(expandedPath, 'utf-8');
const lines = data.split('\n');
const headers = lines[0].split(',');

const latitudeIndex = headers.indexOf('Latitude');
const longitudeIndex = headers.indexOf('Longitude');
const trackIdIndex = headers.indexOf('TrackID');

for (let i = 1; i < lines.length; i++) {
if (lines[i].trim() === '') continue;
const row = lines[i].split(',');

const latitude = parseFloat(row[latitudeIndex]);
const longitude = parseFloat(row[longitudeIndex]);
const trackId = row[trackIdIndex];

const coord = [longitude, latitude];
this.coordinates.push(coord);

if (!this.tracks[trackId]) {
this.tracks[trackId] = [];
}
this.tracks[trackId].push(coord);
}

this.trackIds = Object.keys(this.tracks);
}

getRandomCoordinate() {
const randomIndex = Math.floor(RNG() * this.coordinates.length);
return this.coordinates[randomIndex];
}

getRandomTrack() {
const randomIndex = Math.floor(RNG() * this.trackIds.length);
const trackId = this.trackIds[randomIndex];
return this.tracks[trackId];
}
};

async function runOSRMMethod(osrm, method, coordinates) {
const time = await new Promise((resolve, reject) => {
const startTime = process.hrtime();
osrm[method]({coordinates}, (err, result) => {
if (err) {
if (['NoSegment', 'NoMatch', 'NoRoute', 'NoTrips'].includes(err.message)) {
resolve(null);
} else {

reject(err);
}
} else {
const endTime = process.hrtime(startTime);
resolve(endTime[0] + endTime[1] / 1e9);
}
});
});
return time;
}

async function nearest(osrm, gpsData) {
const times = [];
for (let i = 0; i < 1000; i++) {
const coord = gpsData.getRandomCoordinate();
times.push(await runOSRMMethod(osrm, 'nearest', [coord]));
}
return times;
}

async function route(osrm, gpsData) {
const times = [];
for (let i = 0; i < 1000; i++) {
const from = gpsData.getRandomCoordinate();
const to = gpsData.getRandomCoordinate();


times.push(await runOSRMMethod(osrm, 'route', [from, to]));
}
return times;
}

async function table(osrm, gpsData) {
const times = [];
for (let i = 0; i < 250; i++) {
const numPoints = Math.floor(RNG() * 3) + 15;
const coordinates = [];
for (let i = 0; i < numPoints; i++) {
coordinates.push(gpsData.getRandomCoordinate());
}


times.push(await runOSRMMethod(osrm, 'table', coordinates));
}
return times;
}

async function match(osrm, gpsData) {
const times = [];
for (let i = 0; i < 1000; i++) {
const numPoints = Math.floor(RNG() * 50) + 50;
const coordinates = gpsData.getRandomTrack().slice(0, numPoints);


times.push(await runOSRMMethod(osrm, 'match', coordinates));
}
return times;
}

async function trip(osrm, gpsData) {
const times = [];
for (let i = 0; i < 250; i++) {
const numPoints = Math.floor(RNG() * 2) + 5;
const coordinates = [];
for (let i = 0; i < numPoints; i++) {
coordinates.push(gpsData.getRandomCoordinate());
}


times.push(await runOSRMMethod(osrm, 'trip', coordinates));
}
return times;
}

function bootstrapConfidenceInterval(data, numSamples = 1000, confidenceLevel = 0.95) {
let means = [];
let dataLength = data.length;

for (let i = 0; i < numSamples; i++) {
let sample = [];
for (let j = 0; j < dataLength; j++) {
let randomIndex = Math.floor(RNG() * dataLength);
sample.push(data[randomIndex]);
}
let sampleMean = sample.reduce((a, b) => a + b, 0) / sample.length;
means.push(sampleMean);
}

means.sort((a, b) => a - b);
let lowerBoundIndex = Math.floor((1 - confidenceLevel) / 2 * numSamples);
let upperBoundIndex = Math.floor((1 + confidenceLevel) / 2 * numSamples);
let mean = means.reduce((a, b) => a + b, 0) / means.length;
let lowerBound = means[lowerBoundIndex];
let upperBound = means[upperBoundIndex];

return { mean: mean, lowerBound: lowerBound, upperBound: upperBound };
}

function calculateConfidenceInterval(data) {
let { mean, lowerBound, upperBound } = bootstrapConfidenceInterval(data);
let bestValue = Math.max(...data);
let errorMargin = (upperBound - lowerBound) / 2;

return { mean, errorMargin, bestValue };
}

async function main() {
const args = process.argv.slice(2);

const {OSRM} = require(args[0]);
const path = args[1];
const algorithm = args[2].toUpperCase();
const method = args[3];
const gpsTracesFilePath = args[4];
const iterations = parseInt(args[5]);

const gpsData = new GPSData(gpsTracesFilePath);
const osrm = new OSRM({path, algorithm});


const functions = {
route: route,
table: table,
nearest: nearest,
match: match,
trip: trip
};
const func = functions[method];
if (!func) {
throw new Error('Unknown method');
}
const allTimes = [];
for (let i = 0; i < iterations; i++) {
RNG = seedrandom(42);
allTimes.push((await func(osrm, gpsData)).filter(t => t !== null));
}

const opsPerSec = allTimes.map(times => times.length / times.reduce((a, b) => a + b, 0));
const { mean, errorMargin, bestValue } = calculateConfidenceInterval(opsPerSec);
console.log(`Ops: ${mean.toFixed(1)} ± ${errorMargin.toFixed(1)} ops/s. Best: ${bestValue.toFixed(1)} ops/s`);

}

main();
13 changes: 13 additions & 0 deletions scripts/ci/run_benchmarks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ function run_benchmarks_for_folder {
mkdir -p $RESULTS_FOLDER

BENCHMARKS_FOLDER="$BINARIES_FOLDER/src/benchmarks"

echo "Running match-bench MLD"
$BENCHMARKS_FOLDER/match-bench "$FOLDER/test/data/mld/monaco.osrm" mld > "$RESULTS_FOLDER/match_mld.bench"
echo "Running match-bench CH"
Expand Down Expand Up @@ -81,6 +82,18 @@ function run_benchmarks_for_folder {
echo "Running osrm-contract"
measure_peak_ram_and_time "$BINARIES_FOLDER/osrm-contract $FOLDER/data.osrm" "$RESULTS_FOLDER/osrm_contract.bench"


for ALGORITHM in ch mld; do
for BENCH in nearest table trip route match; do
echo "Running node $BENCH $ALGORITHM"
START=$(date +%s.%N)
node $SCRIPTS_FOLDER/scripts/ci/bench.js $FOLDER/lib/binding/node_osrm.node $FOLDER/data.osrm $ALGORITHM $BENCH $GPS_TRACES > "$RESULTS_FOLDER/node_${BENCH}_${ALGORITHM}.bench" 5
END=$(date +%s.%N)
DIFF=$(echo "$END - $START" | bc)
echo "Took: ${DIFF}s"
done
done

for ALGORITHM in ch mld; do
for BENCH in nearest table trip route match; do
echo "Running random $BENCH $ALGORITHM"
Expand Down

0 comments on commit 84f12c7

Please sign in to comment.