Skip to content

Commit

Permalink
Merge pull request #129 from leblowl/memoize-performance
Browse files Browse the repository at this point in the history
Improve performance of memoized validator functions.
  • Loading branch information
HerbCaudill authored Sep 21, 2024
2 parents 31daeec + 4e9691c commit 23f11af
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 19 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"test:pw": "pnpm -F @localfirst/automerge-repo-todos test:pw",
"test:pw:log": "pnpm -F @localfirst/automerge-repo-todos test:pw:log",
"test:pw:ui": "pnpm -F @localfirst/automerge-repo-todos test:pw:ui",
"bench": "cross-env DEBUG=localfirst*,automerge* DEBUG_COLORS=1 vitest bench",
"watch": "lerna watch -- lerna run build --scope=\\$LERNA_PACKAGE_NAME --include-dependents"
},
"devDependencies": {
Expand Down
41 changes: 41 additions & 0 deletions packages/auth/src/test/auth.benchmark.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { describe, bench } from 'vitest'
import * as Auth from '../index.js'

describe('auth', () => {
bench('a new member joining', () => {
const founderUsername = 'founder'
const founderContext = {
user: Auth.createUser(founderUsername, founderUsername),
device: Auth.createDevice({ userId: founderUsername, deviceName: 'laptop' }),
}
const teamName = 'Test'
const team = Auth.createTeam(teamName, founderContext)

// Add 100 test users

const usernames = Array.from({ length: 100 }, (_, i) => `user-${i}`)

for (const username of usernames) {
const user = Auth.createUser(username, username)
const device = Auth.createDevice({ userId: username, deviceName: 'dev/' + username })
team.addForTesting(user, [], Auth.redactDevice(device))
}

// Invite new user and have them join

const { seed } = team.inviteMember({ maxUses: 1000 })

const username = 'new-user'
const user = Auth.createUser(username, username)
const device = Auth.createDevice({ userId: username, deviceName: 'laptop' })
const proofOfInvitation = Auth.generateProof(seed)

team.admitMember(proofOfInvitation, Auth.redactKeys(user.keys), user.userName)

const serializedGraph = team.save()
const teamKeyring = team.teamKeyring()
const team2 = new Auth.Team({ source: serializedGraph, context: { user, device }, teamKeyring })

team2.join(teamKeyring)
})
})
8 changes: 6 additions & 2 deletions packages/crdx/src/validator/validate.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { type ValidationResult, type ValidatorSet } from './types.js'
import { memoize } from '@localfirst/shared'
import { type ValidationResult, type ValidatorSet } from './types.js'
import { fail, validators } from './validators.js'
import { VALID } from 'constants.js'
import { hashEncryptedLink } from 'graph/hashLink.js'
import { type Action, type Link, type Graph } from 'graph/types.js'
import { hash } from '@localfirst/crypto'

/**
* Runs a hash graph through a series of validators to ensure that it is correctly formed, has
* not been tampered with, etc.
*/
export const validate = <A extends Action, C>(
const _validate = <A extends Action, C>(
/** The hash graph to validate. */
graph: Graph<A, C>,

Expand Down Expand Up @@ -79,6 +81,8 @@ export const validate = <A extends Action, C>(
return VALID
}

export const validate = memoize(_validate, graph => hash('memoize', graph))

// merges multiple validator sets into one object
const merge = (validatorSets: ValidatorSet[]) =>
validatorSets.reduce((result, vs) => Object.assign(result, vs), {})
19 changes: 2 additions & 17 deletions packages/crdx/src/validator/validators.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { memoize } from '@localfirst/shared'
import { hash } from '@localfirst/crypto'
import { ROOT, VALID } from 'constants.js'
import { ROOT, VALID } from 'constants.js'
import { getRoot } from 'graph/getRoot.js'
import { hashEncryptedLink } from 'graph/hashLink.js'
import type { Graph, Link } from 'index.js'
import { ValidationError, type ValidatorSet } from './types.js'

const _validators: ValidatorSet = {
export const validators: ValidatorSet = {
/** Does this link's hash check out? */
validateHash(link, graph) {
const { hash } = link
Expand Down Expand Up @@ -82,15 +79,3 @@ export const fail = (msg: string, args?: any) => {
error: new ValidationError(msg, args),
}
}

const memoizeFunctionMap = (source: ValidatorSet) => {
const result = {} as ValidatorSet
const memoizeResolver = (link: Link<any, any>, graph: Graph<any, any>) => {
return `${hash('memoize', link)}:${hash('memoize', graph)}`
}

for (const key in source) result[key] = memoize(source[key], memoizeResolver)
return result
}

export const validators = memoizeFunctionMap(_validators)

0 comments on commit 23f11af

Please sign in to comment.