perf(encoder): add worker pool

This commit is contained in:
wryk 2019-03-14 18:27:05 +01:00
parent 3229a00604
commit 57d4a8dd52
7 changed files with 160 additions and 112 deletions

36
package-lock.json generated
View file

@ -3898,6 +3898,11 @@
"loader-utils": "^0.2.16"
}
},
"generic-pool": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.6.1.tgz",
"integrity": "sha512-iMmD/pY4q0+V+f8o4twE9JPeqfNuX+gJAaIPB3B0W1lFkBOtTxBo6B0HxHPgGhzQA8jego7EWopcYq/UDJO2KA=="
},
"get-port": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz",
@ -5444,14 +5449,19 @@
"os-tmpdir": "^1.0.0"
}
},
"p-limit": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz",
"integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==",
"p-event": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/p-event/-/p-event-4.0.0.tgz",
"integrity": "sha512-5VKoUdeAfIlAXmASmre9vGlKjnwGwbr5Zwd3GZU2KmkaRgxEOeUUS5Oyh0qYMx8Y+0VTvvNjNIlqIvMpI/DKSw==",
"requires": {
"p-try": "^2.0.0"
"p-timeout": "^2.0.1"
}
},
"p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
},
"p-locate": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
@ -5478,10 +5488,13 @@
}
}
},
"p-try": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz",
"integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ=="
"p-timeout": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz",
"integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==",
"requires": {
"p-finally": "^1.0.0"
}
},
"pako": {
"version": "1.0.10",
@ -6406,11 +6419,6 @@
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"dev": true
},
"promises-progress": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/promises-progress/-/promises-progress-1.0.3.tgz",
"integrity": "sha512-qeVptRZbEqo/JhWVmaAGIG14QnAXXyLj7EI+HKhduqKCl3mMVslMeqxC3tPyxy3N7ffqwbe56vrxqweyKHe1Yg=="
},
"proto-list": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",

View file

@ -11,11 +11,11 @@
"dependencies": {
"@babel/runtime": "^7.3.4",
"eventemitter3": "^3.1.0",
"generic-pool": "^3.6.1",
"gif-writer": "^0.9.3",
"objectFitPolyfill": "^2.1.1",
"p-limit": "^2.2.0",
"p-event": "^4.0.0",
"postcss-modules": "^1.4.1",
"promises-progress": "^1.0.3",
"vue": "^2.6.6",
"vuex": "^3.0.1"
},

View file

@ -0,0 +1,79 @@
import EventEmitter from 'eventemitter3'
import {
GifWriter,
MedianCutColorReducer,
IndexedColorImage
} from 'gif-writer'
import { calcProgress } from '/services/util.js'
class OutputStream {
constructor () {
this.buffer = []
}
writeByte (b) {
this.buffer.push(b)
}
writeBytes (bb) {
Array.prototype.push.apply(this.buffer, bb)
}
}
export function write (indexedColorImages, imageWidth, imageHeight, delay) {
const emitter = new EventEmitter()
// yup, this is the browser nextTick implementation we are waiting for :facepalm:
Promise.resolve().then(() => {
emitter.emit('progress', 0)
const outputStream = new OutputStream()
const writer = new GifWriter(outputStream)
writer.writeHeader()
writer.writeLogicalScreenInfo({
width: imageWidth,
height: imageHeight
})
writer.writeLoopControlInfo(0)
indexedColorImages.forEach((indexedColorImage, index, { length }) => {
writer.writeTableBasedImageWithGraphicControl(indexedColorImage, { delayTimeInMS: delay })
emitter.emit('progress', calcProgress(0, 0.99, (index + 1) / length))
})
writer.writeTrailer()
emitter.emit('progress', 1)
emitter.emit('done', outputStream.buffer)
})
return emitter
}
export function convertImageDataToIndexedColorImage (imageData, paletteSize) {
const reducer = new MedianCutColorReducer(imageData, paletteSize)
const paletteData = reducer.process()
const data = Array.from(imageData.data)
const indexedColorImageData = []
for (let index = 0, length = data.length; index < length; index += 4) {
const [r, g, b] = data.slice(index, index + 4) // r,g,b,a
indexedColorImageData.push(reducer.map(r, g, b))
}
return new IndexedColorImage(
{
width: imageData.width,
height: imageData.height
},
indexedColorImageData,
paletteData
)
}

View file

@ -1,31 +1,49 @@
import { GifWriter } from 'gif-writer'
import pLimit from 'p-limit'
import genericPool from 'generic-pool'
import pEvent from 'p-event'
import { write } from '/services/encode-core.js'
import { promisesProgress, calcProgress } from '/services/util.js'
onmessage = handleMessage
const workerPool = genericPool.createPool({
create () {
return new Worker('/services/quantize-color.worker.js')
},
destroy (worker) {
worker.terminate()
}
}, {
min: 0,
max: 4
})
async function handleMessage (event) {
const { imageDataList, imageWidth, imageHeight, paletteSize, delayTime } = event.data
const outputStream = new OutputStream()
const writer = new GifWriter(outputStream)
postProgressMessage(0)
writer.writeHeader()
writer.writeLogicalScreenInfo({
width: imageWidth,
height: imageHeight
})
writer.writeLoopControlInfo(0)
console.time('quantization')
const limit = pLimit(8)
const promises = imageDataList
.map(imageData => limit(() => convertImageDataToIndexedColorImage(imageData, paletteSize)))
.map(async imageData => {
const worker = await workerPool.acquire()
const indexedColorImage = await new Promise((resolve, reject) => {
worker.onerror = reject
worker.onmessageerror = reject
worker.onmessage = event => {
resolve(event.data)
}
worker.postMessage({
imageData,
paletteSize
})
})
await workerPool.release(worker)
return indexedColorImage
})
const progressPromises = promisesProgress(promises, function (value) {
postProgressMessage(calcProgress(0, 0.9, value))
@ -35,65 +53,17 @@ async function handleMessage (event) {
console.timeEnd('quantization')
indexedColorImages.forEach((indexedColorImage, index, { length }) => {
writer.writeTableBasedImageWithGraphicControl(indexedColorImage, { delayTimeInMS: delayTime })
postProgressMessage(calcProgress(0.9, 1, (index + 1) / length))
})
console.time('write')
writer.writeTrailer()
const writing = write(indexedColorImages, imageWidth, imageHeight, delayTime)
postDoneMessage(outputStream.buffer)
}
writing.on('progress', value => postProgressMessage(calcProgress(0.9, 1, value)))
class OutputStream {
constructor () {
this.buffer = []
}
const buffer = await pEvent(writing, 'done')
writeByte (b) {
this.buffer.push(b)
}
console.timeEnd('write')
writeBytes (bb) {
Array.prototype.push.apply(this.buffer, bb)
}
}
function promisesProgress (promises, progress) {
let complete = 0
return promises.map(p => {
return p.then(x => {
progress(++complete / promises.length)
return p
})
})
}
function convertImageDataToIndexedColorImage (imageData, paletteSize) {
return new Promise((resolve, reject) => {
const worker = new Worker('/services/quantize-color.worker.js')
worker.onerror = reject
worker.onmessageerror = reject
worker.onmessage = event => {
resolve(event.data)
}
worker.postMessage({
imageData,
paletteSize
})
})
}
function calcProgress (from, to, value) {
return from + ((to - from) * value)
}
function calcProgress2 (from, to, steps, current) {
return from + ((to - from) / steps * current)
postDoneMessage(buffer)
}
function postProgressMessage (value) {

View file

@ -1,4 +1,4 @@
import { MedianCutColorReducer, IndexedColorImage } from 'gif-writer'
import { convertImageDataToIndexedColorImage } from '/services/encode-core.js'
onmessage = handleMessage
@ -9,26 +9,3 @@ function handleMessage (event) {
postMessage(indexedColorImage)
}
function convertImageDataToIndexedColorImage (imageData, paletteSize) {
const reducer = new MedianCutColorReducer(imageData, paletteSize)
const paletteData = reducer.process()
const data = Array.from(imageData.data)
const indexedColorImageData = []
for (let index = 0, length = data.length; index < length; index += 4) {
const [r, g, b, a] = data.slice(index, index + 4) // r,g,b,a
indexedColorImageData.push(reducer.map(r, g, b))
}
return new IndexedColorImage(
{
width: imageData.width,
height: imageData.height
},
indexedColorImageData,
paletteData
)
}

14
src/services/util.js Normal file
View file

@ -0,0 +1,14 @@
export function calcProgress (from, to, value) {
return from + ((to - from) * value)
}
export function promisesProgress (promises, progress) {
let complete = 0
return promises.map(p => {
return p.then(x => {
progress(++complete / promises.length)
return p
})
})
}

View file

@ -31,7 +31,7 @@ export default new Vuex.Store({
},
mutations: {
updateWelcomed (state, welcome) {
state.welcomed = welcome;
state.welcomed = welcome
},
startCamera (state, mediaStream) {
state.mediaStream = mediaStream