diff --git a/benchmark/compiler/compiler.mjs b/benchmark/compiler/compiler.mjs index 9bcf3ec..185c41c 100644 --- a/benchmark/compiler/compiler.mjs +++ b/benchmark/compiler/compiler.mjs @@ -55,7 +55,7 @@ function IfTreeBranch(branches, indent = 0) { for (let i = 0; i < branches.length; i++) { let branch = branches[i] - output += `${i > 0 ? 'else ': ''}if (str.charCodeAt(${indent}) === ${branch.char.charCodeAt(0)}) {` + output += `${i > 0 ? 'else ': ''}if (str.charCodeAt(${indent}) === ${branch.char.charCodeAt(0)}) { // ${branch.char}` if (branch.path) { output += '\n' + indentation + ` if (str.length === ${branch.path.length}) {` @@ -78,5 +78,39 @@ function IfTreeBranch(branches, indent = 0) { export function compileTreeIntoIfs(tree) { let output = IfTreeBranch(tree) output += '\nreturn null' - return new Function(output) + return new Function('str', output) +} + + +function IfTreeBranchBuffer(branches, indent = 0) { + let output = '' + let indentation = ''.padStart(indent * 2) + + for (let i = 0; i < branches.length; i++) { + let branch = branches[i] + output += `${i > 0 ? 'else ': ''}if (uint[${indent}] === ${branch.char.charCodeAt(0)}) { // ${branch.char}` + + if (branch.path) { + output += '\n' + indentation + ` if (uint.length === ${branch.path.length}) {` + output += '\n' + indentation + ` return "${branch.path}"` + output += '\n' + indentation + ` }` + } + if (branch.children.length) { + if (branch.path) { + output += ' else ' + } else { + output += '\n' + indentation + ' ' + } + output += IfTreeBranchBuffer(branch.children, indent + 1) + } + output += '\n' + indentation + '} ' + } + return output +} + +export function compileTreeIntoIfsWithBuffer(tree) { + let output = `var uint = Buffer.from(str)\n` + output += IfTreeBranchBuffer(tree) + output += '\nreturn null' + return new Function('str', output) } diff --git a/benchmark/compiler/utils.mjs b/benchmark/compiler/utils.mjs index 3b17afb..cbaf750 100644 --- a/benchmark/compiler/utils.mjs +++ b/benchmark/compiler/utils.mjs @@ -6,3 +6,28 @@ export function printTree(children, indent = 0) { printTree(child.children, indent + 1) } } + +export function printCurrentStatus(fn) { + let opt = %GetOptimizationStatus(fn) + console.log(`${opt.toString(2).padStart(17, '0').split('').join(' ')}`) +} +export function printStatusHelperText() { + console.log(`┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ +│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─╸ is function +│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───╸ is never optimized +│ │ │ │ │ │ │ │ │ │ │ │ │ │ └─────╸ is always optimized +│ │ │ │ │ │ │ │ │ │ │ │ │ └───────╸ is maybe deoptimized +│ │ │ │ │ │ │ │ │ │ │ │ └─────────╸ is optimized +│ │ │ │ │ │ │ │ │ │ │ └───────────╸ is optimized by TurboFan +│ │ │ │ │ │ │ │ │ │ └─────────────╸ is interpreted +│ │ │ │ │ │ │ │ │ └───────────────╸ is marked for optimization +│ │ │ │ │ │ │ │ └─────────────────╸ is marked for concurrent optimization +│ │ │ │ │ │ │ └───────────────────╸ is optimizing concurrently +│ │ │ │ │ │ └─────────────────────╸ is executing +│ │ │ │ │ └───────────────────────╸ topmost frame is turbo fanned +│ │ │ │ └─────────────────────────╸ lite mode +│ │ │ └───────────────────────────╸ marked for deoptimization +│ │ └─────────────────────────────╸ baseline +│ └───────────────────────────────╸ topmost frame is interpreted +└─────────────────────────────────╸ topmost frame is baseline`) +} \ No newline at end of file diff --git a/benchmark/strings.mjs b/benchmark/strings.mjs index 6b3233c..b7f69b4 100644 --- a/benchmark/strings.mjs +++ b/benchmark/strings.mjs @@ -1,19 +1,121 @@ -import { buildTree, compileTreeIntoIfs } from "./compiler/compiler.mjs" +import { buildTree, compileTreeIntoIfs, compileTreeIntoIfsWithBuffer } from "./compiler/compiler.mjs" +import { summary, run, bench } from 'mitata'; +import { printCurrentStatus, printStatusHelperText } from "./compiler/utils.mjs"; +import { FlaskaRouter } from "../flaska.mjs"; + +// Warmup (de-optimize `bench()` calls) +bench('noop', () => { }); +bench('noop2', () => { }); let paths = [ - 'test1', - 'test', - 'test3', - 'test5', - 'something', - 'sometimes', - 'else', - 'goes', - 'here', - 'too', + '/fsdafasfa', + '/ymreklhmse', + '/h34nmlaeknmgl', + '/asdgsdagas', + '/ahaewhweaaa', + '/adshashaea', + '/sdafasfsadfasdfas', + //'/gdfsfgsfdsgsdrgsregsergsregersgserersgsergersg', + //'/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', ] +let pathsBla = [ + '/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', + '/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', + '/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', + '/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', + '/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', + '/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', + '/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', + '/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', + '/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', + '/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', +] +let pathsBuffer = paths.map(x => new Uint8Array(Buffer.from(x))) let tree = buildTree(paths) +let noop = function() { } const ifTree = compileTreeIntoIfs(tree) -console.log(ifTree.toString()) \ No newline at end of file +// console.log(ifTree.toString()) +const ifTreeBuffer = compileTreeIntoIfsWithBuffer(tree) +// console.log(ifTreeBuffer.toString()) +const flaskaRouter = new FlaskaRouter() +for (let path of paths) { + flaskaRouter.addRoute(path, noop) +} + +const m = new Map(paths.map(x => [x, x])) +function mapFlat(str) { + return m.get(str) ?? null +} + +function toBuffer(str) { + return Buffer.from(str) +} +function toUint(str) { + return new Uint8Array(Buffer.from(str)) +} +function toManualArray(str) { + let length = str.length + let out = new Array(length) + for (let i = 0; i < length; i++) { + out[i] = str.charCodeAt(i) + } + return out +} +function allocBufferUnsafe(str) { + return Buffer.allocUnsafe(str.length) +} +function allocBufferSafe(str) { + return Buffer.alloc(str.length) +} +function allocUint8(str) { + return new Uint8Array(str.length) +} +function toManualArraySplitMap(str) { + return str.split('').map(x => x.charCodeAt(0)) +} + +let func1 = [mapFlat, toBuffer, toUint, toManualArray, toManualArraySplitMap, allocBufferUnsafe, allocBufferSafe, allocUint8, ifTree]; +for (var i = 0; i < 100000; i++) { + for (let fun of func1) { + paths.map(fun) + } +} +for (var i = 0; i < 100000; i++) { + for (let path of paths) { + flaskaRouter.match(path) + } +} +for (let fun of func1) { + printCurrentStatus(fun); +} +printCurrentStatus(flaskaRouter.match) +printStatusHelperText() + +summary(() => { + bench('if tree', function() { + return paths.map(ifTree) + }) + bench('if tree buffer edition', function() { + return paths.map(ifTreeBuffer) + }); + bench('flaskarouter', function() { + return paths.map(flaskaRouter.match.bind(flaskaRouter)) + }); + /*bench('map edition', function() { + return paths.map(mapFlat) + }); + bench('if tree pre buffer edition', function() { + return pathsBuffer.map(ifTreeBuffer) + }); + bench('toBuffer', () => pathsBla.map(toBuffer)); + bench('toUint', () => pathsBla.map(toUint)); + bench('toManualArraySplitMap', () => pathsBla.map(toManualArraySplitMap)) + bench('toManualArray', () => pathsBla.map(toManualArray))*/ + bench('allocBufferUnsafe', () => pathsBla.map(allocBufferUnsafe)) + bench('allocBufferSafe', () => pathsBla.map(allocBufferSafe)) + bench('allocUint8', () => pathsBla.map(allocUint8)) +}) + +run(); \ No newline at end of file diff --git a/flaska.mjs b/flaska.mjs index db51c89..da65cdf 100644 --- a/flaska.mjs +++ b/flaska.mjs @@ -431,7 +431,7 @@ export class FlaskaRouter { let isFullParam = false let branch = this.root - if (route.indexOf(':') < 0) { + if (route.indexOf(':') < 0 && false) { let name = route if (name.length > 1 && name[name.length - 1] === '/') { name = name.slice(0, -1)