Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: improve performance #38

Merged
merged 12 commits into from
Jan 8, 2018
Merged
110 changes: 88 additions & 22 deletions __performance_tests__/todo.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import immerProxy, {setAutoFreeze as setAutoFreezeProxy} from ".."
import immerEs5, {setAutoFreeze as setAutoFreezeEs5} from "../es5"
import cloneDeep from "lodash.clonedeep"
import {List, Record} from "immutable"
import deepFreeze from "deep-freeze"

function freeze(x) {
Object.freeze(x)
return x
}

describe("performance", () => {
const MAX = 100000
Expand All @@ -21,14 +27,7 @@ describe("performance", () => {
}

// Produce the frozen bazeState
frozenBazeState = baseState.map(todo => {
const newTodo = {...todo}
newTodo.someThingCompletelyIrrelevant = todo.someThingCompletelyIrrelevant.slice()
Object.freeze(newTodo.someThingCompletelyIrrelevant)
Object.freeze(newTodo)
return newTodo
})
Object.freeze(frozenBazeState)
frozenBazeState = deepFreeze(cloneDeep(baseState))

// generate immutalbeJS base state
const todoRecord = Record({
Expand All @@ -43,20 +42,41 @@ describe("performance", () => {
test(name, fn)
}

measure("just mutate", () => {
{
const draft = cloneDeep(baseState)
measure("just mutate", () => {
for (let i = 0; i < MAX * MODIFY_FACTOR; i++) {
draft[i].done = true
}
})
}

{
const draft = cloneDeep(baseState)
measure("just mutate, freeze", () => {
for (let i = 0; i < MAX * MODIFY_FACTOR; i++) {
draft[i].done = true
}
deepFreeze(draft)
})
}

measure("deepclone, then mutate", () => {
const draft = cloneDeep(baseState)
for (let i = 0; i < MAX * MODIFY_FACTOR; i++) {
baseState[i].done = true
draft[i].done = true
}
})

measure("deepclone, then mutate", () => {
measure("deepclone, then mutate, then freeze", () => {
const draft = cloneDeep(baseState)
for (let i = 0; i < MAX * MODIFY_FACTOR; i++) {
draft[i].done = true
}
deepFreeze(draft)
})

measure("handcrafted reducer", () => {
measure("handcrafted reducer (no freeze)", () => {
const nextState = [
...baseState.slice(0, MAX * MODIFY_FACTOR).map(todo => ({
...todo,
Expand All @@ -66,6 +86,42 @@ describe("performance", () => {
]
})

measure("handcrafted reducer (with freeze)", () => {
const nextState = freeze([
...baseState.slice(0, MAX * MODIFY_FACTOR).map(todo =>
freeze({
...todo,
done: true
})
),
...baseState.slice(MAX * MODIFY_FACTOR)
])
})

measure("naive handcrafted reducer (without freeze)", () => {
const nextState = baseState.map((todo, index) => {
if (index < MAX * MODIFY_FACTOR)
return {
...todo,
done: true
}
else return todo
})
})

measure("naive handcrafted reducer (with freeze)", () => {
const nextState = deepFreeze(
baseState.map((todo, index) => {
if (index < MAX * MODIFY_FACTOR)
return {
...todo,
done: true
}
else return todo
})
)
})

measure("immutableJS", () => {
let state = immutableJsBaseState
state.withMutations(state => {
Expand All @@ -75,13 +131,14 @@ describe("performance", () => {
})
})

measure("immer (proxy) - with autofreeze", () => {
setAutoFreezeProxy(true)
immerProxy(frozenBazeState, draft => {
for (let i = 0; i < MAX * MODIFY_FACTOR; i++) {
draft[i].done = true
}
})
measure("immutableJS + toJS", () => {
let state = immutableJsBaseState
.withMutations(state => {
for (let i = 0; i < MAX * MODIFY_FACTOR; i++) {
state.setIn([i, "done"], true)
}
})
.toJS()
})

measure("immer (proxy) - without autofreeze", () => {
Expand All @@ -94,9 +151,9 @@ describe("performance", () => {
setAutoFreezeProxy(true)
})

measure("immer (es5) - with autofreeze", () => {
setAutoFreezeEs5(true)
immerEs5(frozenBazeState, draft => {
measure("immer (proxy) - with autofreeze", () => {
setAutoFreezeProxy(true)
immerProxy(frozenBazeState, draft => {
for (let i = 0; i < MAX * MODIFY_FACTOR; i++) {
draft[i].done = true
}
Expand All @@ -112,4 +169,13 @@ describe("performance", () => {
})
setAutoFreezeEs5(true)
})

measure("immer (es5) - with autofreeze", () => {
setAutoFreezeEs5(true)
immerEs5(frozenBazeState, draft => {
for (let i = 0; i < MAX * MODIFY_FACTOR; i++) {
draft[i].done = true
}
})
})
})
49 changes: 40 additions & 9 deletions __tests__/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,31 @@ function runBaseTest(name, lib, freeze) {
expect(nextState.nested).toBe(baseState.nested)
})

it("deep change bubbles up", () => {
const nextState = immer(baseState, s => {
s.anObject.nested.yummie = false
if (
("should preserve type",
() => {
const nextState = immer(baseState, s => {
expect(Array.isArray(s)).toBe(true)
expect(s.protoType).toBe(Object)
s.anArray.push(3)
s.aProp = "hello world"
expect(Array.isArray(s)).toBe(true)
expect(s.protoType).toBe(Object)
})
expect(Array.isArray(nextState)).toBe(true)
expect(nextState.protoType).toBe(Object)
})
)
it("deep change bubbles up", () => {
const nextState = immer(baseState, s => {
s.anObject.nested.yummie = false
})
expect(nextState).not.toBe(baseState)
expect(nextState.anObject).not.toBe(baseState.anObject)
expect(baseState.anObject.nested.yummie).toBe(true)
expect(nextState.anObject.nested.yummie).toBe(false)
expect(nextState.anArray).toBe(baseState.anArray)
})
expect(nextState).not.toBe(baseState)
expect(nextState.anObject).not.toBe(baseState.anObject)
expect(baseState.anObject.nested.yummie).toBe(true)
expect(nextState.anObject.nested.yummie).toBe(false)
expect(nextState.anArray).toBe(baseState.anArray)
})

it("can add props", () => {
const nextState = immer(baseState, s => {
Expand Down Expand Up @@ -133,6 +148,16 @@ function runBaseTest(name, lib, freeze) {
])
})

it("can delete array items", () => {
const nextState = immer(baseState, s => {
s.anArray.length = 3
})
expect(nextState).not.toBe(baseState)
expect(nextState.anObject).toBe(baseState.anObject)
expect(nextState.anArray).not.toBe(baseState.anArray)
expect(nextState.anArray).toEqual([3, 2, {c: 3}])
})

it("should support sorting arrays", () => {
const nextState = immer(baseState, s => {
s.anArray[2].c = 4
Expand Down Expand Up @@ -223,6 +248,7 @@ function runBaseTest(name, lib, freeze) {
delete s.anObject
obj.coffee = true
s.messy = obj
debugger
})
expect(nextState).not.toBe(baseState)
expect(nextState.anArray).toBe(baseState.anArray)
Expand Down Expand Up @@ -330,3 +356,8 @@ function enumerableOnly(x) {
// this can be done better...
return JSON.parse(JSON.stringify(x))
}

// TODO: test problem scenarios
// nesting immmer
// non-trees
// complex objects / functions
Loading