perf: use relaxed IDB transactions and manually commit (#218)
* perf: use relaxed IDB transactions and manually commit * fix: reduce time spent in benchmark * fix: add missing txn.commit() * fix: small syntax change
This commit is contained in:
parent
e89ed42716
commit
d732610e7f
|
@ -73,3 +73,19 @@ jobs:
|
|||
path: test/benchmark/second-load.results.json
|
||||
pr-bench-name: this-change
|
||||
base-bench-name: tip-of-tree
|
||||
|
||||
# database-interactions
|
||||
- name: Benchmark database-interactions
|
||||
run: |
|
||||
cd test/benchmark
|
||||
../../node_modules/.bin/tach \
|
||||
--config ./database-interactions.tachometer.json \
|
||||
--json-file ./database-interactions.results.json
|
||||
|
||||
- name: Report database-interactions
|
||||
uses: andrewiggins/tachometer-reporter-action@v2
|
||||
with:
|
||||
report-id: emoji-picker-element-database-interactions
|
||||
path: test/benchmark/database-interactions.results.json
|
||||
pr-bench-name: this-change
|
||||
base-bench-name: tip-of-tree
|
|
@ -50,18 +50,20 @@ export function openDatabase (dbName) {
|
|||
|
||||
export function dbPromise (db, storeName, readOnlyOrReadWrite, cb) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const tx = db.transaction(storeName, readOnlyOrReadWrite)
|
||||
// Use relaxed durability because neither the emoji data nor the favorites/preferred skin tone
|
||||
// are really irreplaceable data. IndexedDB is just a cache in this case.
|
||||
const txn = db.transaction(storeName, readOnlyOrReadWrite, { durability: 'relaxed' })
|
||||
const store = typeof storeName === 'string'
|
||||
? tx.objectStore(storeName)
|
||||
: storeName.map(name => tx.objectStore(name))
|
||||
? txn.objectStore(storeName)
|
||||
: storeName.map(name => txn.objectStore(name))
|
||||
let res
|
||||
cb(store, (result) => {
|
||||
cb(store, txn, (result) => {
|
||||
res = result
|
||||
})
|
||||
|
||||
tx.oncomplete = () => resolve(res)
|
||||
txn.oncomplete = () => resolve(res)
|
||||
/* istanbul ignore next */
|
||||
tx.onerror = () => reject(tx.error)
|
||||
txn.onerror = () => reject(txn.error)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
} from './constants'
|
||||
import { transformEmojiData } from './utils/transformEmojiData'
|
||||
import { extractTokens } from './utils/extractTokens'
|
||||
import { getAllIDB, getIDB } from './idbUtil'
|
||||
import { commit, getAllIDB, getIDB } from './idbUtil'
|
||||
import { findCommonMembers } from './utils/findCommonMembers'
|
||||
import { normalizeTokens } from './utils/normalizeTokens'
|
||||
|
||||
|
@ -38,7 +38,7 @@ async function doFullDatabaseScanForSingleResult (db, predicate) {
|
|||
// console.log(performance.getEntriesByName('total').slice(-1)[0].duration)
|
||||
// })()
|
||||
const BATCH_SIZE = 50 // Typically around 150ms for 6x slowdown in Chrome for above benchmark
|
||||
return dbPromise(db, STORE_EMOJI, MODE_READONLY, (emojiStore, cb) => {
|
||||
return dbPromise(db, STORE_EMOJI, MODE_READONLY, (emojiStore, txn, cb) => {
|
||||
let lastKey
|
||||
|
||||
const processNextBatch = () => {
|
||||
|
@ -64,7 +64,7 @@ export async function loadData (db, emojiData, url, eTag) {
|
|||
performance.mark('loadData')
|
||||
try {
|
||||
const transformedData = transformEmojiData(emojiData)
|
||||
await dbPromise(db, [STORE_EMOJI, STORE_KEYVALUE], MODE_READWRITE, ([emojiStore, metaStore]) => {
|
||||
await dbPromise(db, [STORE_EMOJI, STORE_KEYVALUE], MODE_READWRITE, ([emojiStore, metaStore], txn) => {
|
||||
let oldETag
|
||||
let oldUrl
|
||||
let todo = 0
|
||||
|
@ -88,6 +88,7 @@ export async function loadData (db, emojiData, url, eTag) {
|
|||
}
|
||||
metaStore.put(eTag, KEY_ETAG)
|
||||
metaStore.put(url, KEY_URL)
|
||||
commit(txn)
|
||||
performance.mark('commitAllData')
|
||||
}
|
||||
|
||||
|
@ -108,7 +109,7 @@ export async function loadData (db, emojiData, url, eTag) {
|
|||
}
|
||||
|
||||
export async function getEmojiByGroup (db, group) {
|
||||
return dbPromise(db, STORE_EMOJI, MODE_READONLY, (emojiStore, cb) => {
|
||||
return dbPromise(db, STORE_EMOJI, MODE_READONLY, (emojiStore, txn, cb) => {
|
||||
const range = IDBKeyRange.bound([group, 0], [group + 1, 0], false, true)
|
||||
getAllIDB(emojiStore.index(INDEX_GROUP_AND_ORDER), range, cb)
|
||||
})
|
||||
|
@ -121,7 +122,7 @@ export async function getEmojiBySearchQuery (db, query) {
|
|||
return []
|
||||
}
|
||||
|
||||
return dbPromise(db, STORE_EMOJI, MODE_READONLY, (emojiStore, cb) => {
|
||||
return dbPromise(db, STORE_EMOJI, MODE_READONLY, (emojiStore, txn, cb) => {
|
||||
// get all results that contain all tokens (i.e. an AND query)
|
||||
const intermediateResults = []
|
||||
|
||||
|
@ -171,7 +172,7 @@ export async function getEmojiByShortcode (db, shortcode) {
|
|||
}
|
||||
|
||||
export async function getEmojiByUnicode (db, unicode) {
|
||||
return dbPromise(db, STORE_EMOJI, MODE_READONLY, (emojiStore, cb) => (
|
||||
return dbPromise(db, STORE_EMOJI, MODE_READONLY, (emojiStore, txn, cb) => (
|
||||
getIDB(emojiStore, unicode, result => {
|
||||
if (result) {
|
||||
return cb(result)
|
||||
|
@ -182,30 +183,32 @@ export async function getEmojiByUnicode (db, unicode) {
|
|||
}
|
||||
|
||||
export function get (db, storeName, key) {
|
||||
return dbPromise(db, storeName, MODE_READONLY, (store, cb) => (
|
||||
return dbPromise(db, storeName, MODE_READONLY, (store, txn, cb) => (
|
||||
getIDB(store, key, cb)
|
||||
))
|
||||
}
|
||||
|
||||
export function set (db, storeName, key, value) {
|
||||
return dbPromise(db, storeName, MODE_READWRITE, (store) => (
|
||||
return dbPromise(db, storeName, MODE_READWRITE, (store, txn) => {
|
||||
store.put(value, key)
|
||||
))
|
||||
commit(txn)
|
||||
})
|
||||
}
|
||||
|
||||
export function incrementFavoriteEmojiCount (db, unicode) {
|
||||
return dbPromise(db, STORE_FAVORITES, MODE_READWRITE, (store) => {
|
||||
getIDB(store, unicode, result => (
|
||||
return dbPromise(db, STORE_FAVORITES, MODE_READWRITE, (store, txn) => (
|
||||
getIDB(store, unicode, result => {
|
||||
store.put((result || 0) + 1, unicode)
|
||||
))
|
||||
})
|
||||
commit(txn)
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
export function getTopFavoriteEmoji (db, customEmojiIndex, limit) {
|
||||
if (limit === 0) {
|
||||
return []
|
||||
}
|
||||
return dbPromise(db, [STORE_FAVORITES, STORE_EMOJI], MODE_READONLY, ([favoritesStore, emojiStore], cb) => {
|
||||
return dbPromise(db, [STORE_FAVORITES, STORE_EMOJI], MODE_READONLY, ([favoritesStore, emojiStore], txn, cb) => {
|
||||
const results = []
|
||||
favoritesStore.index(INDEX_COUNT).openCursor(undefined, 'prev').onsuccess = e => {
|
||||
const cursor = e.target.result
|
||||
|
|
|
@ -11,3 +11,10 @@ export function getIDB (store, key, cb) {
|
|||
export function getAllIDB (store, key, cb) {
|
||||
callStore(store, 'getAll', key, cb)
|
||||
}
|
||||
|
||||
export function commit (txn) {
|
||||
/* istanbul ignore else */
|
||||
if (txn.commit) {
|
||||
txn.commit()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ performance.mark = function (name) {
|
|||
performance.measure = function (name, start) {
|
||||
if (name === 'initialLoad' && start === 'initialLoad') {
|
||||
// test to make sure the picker loaded with no errors
|
||||
const hasErrors = !!document.querySelector('emoji-picker')
|
||||
const hasErrors = document.querySelector('emoji-picker') && document.querySelector('emoji-picker')
|
||||
.shadowRoot.querySelector('.message:not(.gone)')
|
||||
if (hasErrors) {
|
||||
console.error('picker is showing an error message')
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<script type="module" src="./benchmark.js"></script>
|
||||
<script type="module">
|
||||
import Database from 'emoji-picker-element/database'
|
||||
|
||||
performance.mark('initialLoad')
|
||||
const dataSource = '/data.json'
|
||||
const database = new Database({ dataSource })
|
||||
await database.ready()
|
||||
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await database.getEmojiByUnicodeOrName('💥')
|
||||
await database.getEmojiBySearchQuery('boom')
|
||||
await database.getEmojiByShortcode('boom')
|
||||
await database.getEmojiByGroup(1)
|
||||
await database.getPreferredSkinTone()
|
||||
await database.getTopFavoriteEmoji(10)
|
||||
await database.incrementFavoriteEmojiCount('💥')
|
||||
await database.setPreferredSkinTone(0)
|
||||
}
|
||||
|
||||
performance.measure('initialLoad', 'initialLoad')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"$schema": "https://raw.githubusercontent.com/Polymer/tachometer/master/config.schema.json",
|
||||
"sampleSize": 50,
|
||||
"timeout": 5,
|
||||
"horizons": ["10%"],
|
||||
"benchmarks": [
|
||||
{
|
||||
"url": "./database-interactions.html",
|
||||
"browser": {
|
||||
"name": "chrome",
|
||||
"headless": true
|
||||
},
|
||||
"measurement": [
|
||||
{
|
||||
"mode": "performance",
|
||||
"entryName": "benchmark-total"
|
||||
}
|
||||
],
|
||||
"expand": [
|
||||
{
|
||||
"name": "this-change"
|
||||
},
|
||||
{
|
||||
"name": "tip-of-tree",
|
||||
"packageVersions": {
|
||||
"label": "tip-of-tree",
|
||||
"dependencies": {
|
||||
"emoji-picker-element": {
|
||||
"kind": "git",
|
||||
"repo": "https://github.com/nolanlawson/emoji-picker-element.git",
|
||||
"ref": "master",
|
||||
"setupCommands": [
|
||||
"yarn --immutable --ignore-scripts",
|
||||
"PERF=1 yarn build:rollup"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue