// Copyright 2025 DeepMind Technologies Limited // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'jasmine'; import { MainModule, DoubleBuffer } from "../dist/mujoco_wasm_benchmark" import loadMujoco from "../dist/mujoco_wasm_benchmark.js" describe('MuJoCo WASM Benchmark Tests', () => { let mujoco: MainModule; function isNumberArraySorted(arr: number[]): boolean { for (let i = 0; i < arr.length - 1; i++) { if (arr[i] > arr[i + 1]) { return false; } } return true; } function verifySorted(state: any, kind: string): void { let array: number[] = []; if (kind === 'NumberArray') { array = state as number[]; } else { const bufferView = (state as DoubleBuffer).GetView(); array = Array.from(bufferView) as number[]; } const sorted = isNumberArraySorted(array); if (!sorted) { console.error(` Verification failed for ${kind}. Array was ${array}`); } } function runBenchmark(iterations: number, items: number, kind: string) { let state: any; let func: (state: any) => any; if (kind === 'NumberArray') { state = new Array(items).map(() => Math.random()) func = (state) => mujoco.SortNumberArray(state); } else if (kind === 'DoubleBuffer') { state = mujoco.DoubleBuffer.FromArray( new Array(items).map(() => Math.random())); func = (state) => mujoco.SortDoubleBuffer(state); } else { throw new Error(`Unsupported benchmark type: ${kind}`); } // Warmup JIT compiler to get stable results for (let i = 0; i < 100; i++) { const sortedState = func(state); verifySorted(sortedState, kind); } // Measurement const totalStartTime = performance.now(); for (let i = 0; i < iterations; i++) { func(state); } const totalEndTime = performance.now(); if (kind === 'DoubleBuffer') { state.delete(); } // Report results const totalTime = totalEndTime - totalStartTime; const avgTimeMilliseconds = totalTime / iterations; console.log(`Benchmark: "Sort ${kind} ${iterations} iterations with ${ items} items" - AVG time per call: ${ avgTimeMilliseconds.toFixed(2)} ms`); return { totalTime, avgTimeMilliseconds, } } beforeAll(async () => { mujoco = await loadMujoco(); }); it('should benchmark NumberArray and DoubleBuffer and compare results', () => { const na1 = runBenchmark(100, 1_000, 'NumberArray'); const na2 = runBenchmark(100, 400_000, 'NumberArray'); const db1 = runBenchmark(100, 1_000, 'DoubleBuffer'); const db2 = runBenchmark(100, 400_000, 'DoubleBuffer'); // The actual time should be much faster than that but the intention of // the check is to catch huge regressions without a flakey test const _100ms = 100; expect(db2.avgTimeMilliseconds).toBeLessThan(_100ms); expect(db1.totalTime).toBeLessThan(na1.totalTime); expect(db2.totalTime).toBeLessThan(na2.totalTime); expect(db1.avgTimeMilliseconds).toBeLessThan(na1.avgTimeMilliseconds); expect(db2.avgTimeMilliseconds).toBeLessThan(na2.avgTimeMilliseconds); }); });