Add NodeJs bindings benchmarks (#7004)

This commit is contained in:
Siarhei Fedartsou 2024-07-26 19:36:43 +02:00 committed by GitHub
parent 7802f86bd6
commit 84f12c7c6d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 258 additions and 7 deletions

View File

@ -672,6 +672,11 @@ jobs:
key: v1-data-osm-pbf key: v1-data-osm-pbf
restore-keys: | restore-keys: |
v1-data-osm-pbf 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 - name: Enable compiler cache
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
@ -722,18 +727,24 @@ jobs:
path: base path: base
- name: Build Base Branch - name: Build Base Branch
run: | run: |
cd base
npm ci --ignore-scripts
cd ..
mkdir base/build mkdir base/build
cd 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)
make -j$(nproc) benchmarks make -j$(nproc) benchmarks
cd .. cd ..
make -C test/data make -C test/data
- name: Build PR Branch - name: Build PR Branch
run: | run: |
cd pr
npm ci --ignore-scripts
cd ..
mkdir -p pr/build mkdir -p pr/build
cd 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)
make -j$(nproc) benchmarks make -j$(nproc) benchmarks
cd .. cd ..
@ -745,6 +756,7 @@ jobs:
run: | run: |
sudo mount -t tmpfs -o size=4g none /opt/benchmarks sudo mount -t tmpfs -o size=4g none /opt/benchmarks
cp -rf pr/build /opt/benchmarks/build cp -rf pr/build /opt/benchmarks/build
cp -rf pr/lib /opt/benchmarks/lib
mkdir -p /opt/benchmarks/test mkdir -p /opt/benchmarks/test
cp -rf pr/test/data /opt/benchmarks/test/data cp -rf pr/test/data /opt/benchmarks/test/data
cp -rf pr/profiles /opt/benchmarks/profiles cp -rf pr/profiles /opt/benchmarks/profiles
@ -755,6 +767,7 @@ jobs:
run: | run: |
sudo mount -t tmpfs -o size=4g none /opt/benchmarks sudo mount -t tmpfs -o size=4g none /opt/benchmarks
cp -rf base/build /opt/benchmarks/build cp -rf base/build /opt/benchmarks/build
cp -rf base/lib /opt/benchmarks/lib
mkdir -p /opt/benchmarks/test mkdir -p /opt/benchmarks/test
cp -rf base/test/data /opt/benchmarks/test/data cp -rf base/test/data /opt/benchmarks/test/data
cp -rf base/profiles /opt/benchmarks/profiles cp -rf base/profiles /opt/benchmarks/profiles

13
package-lock.json generated
View File

@ -10,7 +10,8 @@
"hasInstallScript": true, "hasInstallScript": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"@mapbox/node-pre-gyp": "^1.0.11" "@mapbox/node-pre-gyp": "^1.0.11",
"seedrandom": "^3.0.5"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.18.10", "@babel/cli": "^7.18.10",
@ -14652,6 +14653,11 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"devOptional": true "devOptional": true
}, },
"node_modules/seedrandom": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
},
"node_modules/semver": { "node_modules/semver": {
"version": "5.7.2", "version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
@ -30296,6 +30302,11 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"devOptional": true "devOptional": true
}, },
"seedrandom": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
},
"semver": { "semver": {
"version": "5.7.2", "version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",

View File

@ -4,7 +4,8 @@
"private": false, "private": false,
"description": "The Open Source Routing Machine is a high performance routing engine written in C++ designed to run on OpenStreetMap data.", "description": "The Open Source Routing Machine is a high performance routing engine written in C++ designed to run on OpenStreetMap data.",
"dependencies": { "dependencies": {
"@mapbox/node-pre-gyp": "^1.0.11" "@mapbox/node-pre-gyp": "^1.0.11",
"seedrandom": "^3.0.5"
}, },
"browserify": { "browserify": {
"transform": [ "transform": [
@ -57,6 +58,7 @@
"jsonpath": "^1.1.1", "jsonpath": "^1.1.1",
"mkdirp": "^0.5.6", "mkdirp": "^0.5.6",
"node-addon-api": "^5.0.0", "node-addon-api": "^5.0.0",
"node-cmake": "^2.5.1",
"node-timeout": "0.0.4", "node-timeout": "0.0.4",
"polyline": "^0.2.0", "polyline": "^0.2.0",
"request": "^2.88.2", "request": "^2.88.2",
@ -64,12 +66,13 @@
"tape": "^4.16.0", "tape": "^4.16.0",
"turf": "^3.0.14", "turf": "^3.0.14",
"uglify-js": "^3.17.0", "uglify-js": "^3.17.0",
"xmlbuilder": "^4.2.1", "xmlbuilder": "^4.2.1"
"node-cmake": "^2.5.1"
}, },
"main": "lib/index.js", "main": "lib/index.js",
"binary": { "binary": {
"napi_versions": [8], "napi_versions": [
8
],
"module_name": "node_osrm", "module_name": "node_osrm",
"module_path": "./lib/binding_napi_v{napi_build_version}/", "module_path": "./lib/binding_napi_v{napi_build_version}/",
"host": "https://github.com", "host": "https://github.com",

211
scripts/ci/bench.js Normal file
View File

@ -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();

View File

@ -53,6 +53,7 @@ function run_benchmarks_for_folder {
mkdir -p $RESULTS_FOLDER mkdir -p $RESULTS_FOLDER
BENCHMARKS_FOLDER="$BINARIES_FOLDER/src/benchmarks" BENCHMARKS_FOLDER="$BINARIES_FOLDER/src/benchmarks"
echo "Running match-bench MLD" echo "Running match-bench MLD"
$BENCHMARKS_FOLDER/match-bench "$FOLDER/test/data/mld/monaco.osrm" mld > "$RESULTS_FOLDER/match_mld.bench" $BENCHMARKS_FOLDER/match-bench "$FOLDER/test/data/mld/monaco.osrm" mld > "$RESULTS_FOLDER/match_mld.bench"
echo "Running match-bench CH" echo "Running match-bench CH"
@ -81,6 +82,18 @@ function run_benchmarks_for_folder {
echo "Running osrm-contract" echo "Running osrm-contract"
measure_peak_ram_and_time "$BINARIES_FOLDER/osrm-contract $FOLDER/data.osrm" "$RESULTS_FOLDER/osrm_contract.bench" 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 ALGORITHM in ch mld; do
for BENCH in nearest table trip route match; do for BENCH in nearest table trip route match; do
echo "Running random $BENCH $ALGORITHM" echo "Running random $BENCH $ALGORITHM"