Last active
March 18, 2019 23:16
-
-
Save Krinkle/eae60734429fc06c3ec4b99d0fff5a60 to your computer and use it in GitHub Desktop.
Distribute exponential task sizes via Node.js cluster workers.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Requires Node 6.0+ | |
* | |
* Author: Timo Tijhof (2018). | |
* License: Public domain. | |
*/ | |
const cluster = require('cluster'); | |
let pidLabel = cluster.isMaster ? 'master' : 'worker'; | |
if (cluster.isMaster) { | |
log('creating workers...'); | |
let max = 1000 * 1000 * 1000; // 1 billion; // Number.MAX_SAFE_INTEGER | |
// Calls to getFactorSum() for numbers smaller than this | |
// run fast enough that it is not worth the master-worker overhead. | |
// So to ensure a quick start, distribute only ranges for which | |
// the sum exceeds this. Once we reach numbers higher than this, | |
// each work unit will represent a single number only. | |
let taskMinSize = 100 * 1000 * 1000; // 100 million | |
let wuf = new TaskFactory(2, max, taskMinSize); | |
let i = require('os').cpus().length; | |
while (i--) { | |
let worker = cluster.fork(); | |
} | |
cluster.on('online', (worker) => { | |
// Assign the new worker its first task | |
let task = wuf.createTask(); | |
if (task === false) { | |
worker.disconnect(); | |
} else { | |
worker.send(task); | |
} | |
}); | |
cluster.on('message', (worker, message) => { | |
// Worker finished a task, assign its next task | |
let task = wuf.createTask(); | |
if (task === false) { | |
worker.disconnect(); | |
} else { | |
worker.send(task); | |
} | |
}); | |
cluster.on('exit', (worker, code, signal) => { | |
log(`worker process ${worker.process.pid} exited`); | |
}); | |
} else { | |
// Worker process | |
process.on('message', function (message) { | |
//log(`incoming message: ${message.type}`); | |
switch (message.type) { | |
case 'single': | |
processSingleNumber(message.num); | |
process.send( { type: 'done' } ); | |
break; | |
case 'range': | |
processNumberRange(message.start, message.end); | |
process.send( { type: 'done' } ); | |
break; | |
default: | |
log(`unknown message type: ${message.type}`); | |
} | |
}); | |
} | |
/** | |
* @param {string} text | |
*/ | |
function log(text) { | |
console.log(`[${process.pid} ${pidLabel}] ${text}`); | |
} | |
/** @return {Object} */ | |
function createSingleTask(num) { | |
return { type: 'single', num: num }; | |
} | |
/** @return {Object} */ | |
function createRangeTask(start, end) { | |
return { type: 'range', start: start, end: end }; | |
} | |
function TaskFactory(beginAt, endAt, size) { | |
let offset = beginAt; | |
/* @return {Object|false} Task object, or false to indicate no more tasks. */ | |
this.createTask = function () { | |
this.checkMilestone(); | |
if (offset >= endAt) { | |
return false; | |
} | |
if (offset > size) { | |
let num = offset; | |
// [optim] Only even numbers | |
offset += 2; | |
return createSingleTask(num); | |
} | |
let start = offset; | |
// [optim] Only even numbers | |
let end = start + 2; | |
let sum = start + end; | |
while (sum < size && end < endAt) { | |
end += 2; | |
sum += end; | |
} | |
offset = end + 2; | |
return createRangeTask(start, end); | |
} | |
// Steps of 0.01%, allow for 10,000 milestones | |
let milestoneChunk = Math.max(1, Math.floor((endAt - beginAt) / 10000)); | |
let nextMilestoneN = 1; | |
let nextMilestone = beginAt + milestoneChunk; | |
this.checkMilestone = function () { | |
if (offset >= nextMilestone) { | |
log('progress ' + ((nextMilestoneN / 10000) * 100).toFixed(2) + '%'); | |
nextMilestoneN++; | |
nextMilestone += milestoneChunk; | |
} | |
}; | |
} | |
/** | |
* @param {number} num | |
* @return {number} Sum of num's factors | |
*/ | |
function getFactorSum(num) { | |
let fs = 1; // [optim] Assume `n % 1` | |
for (let i = 2; i < num; i++) { | |
if (num % i === 0) { | |
fs += i; | |
} | |
} | |
return fs; | |
} | |
/** | |
* @param {number} num | |
*/ | |
function processSingleNumber(num) { | |
if (getFactorSum(i) === i) { | |
log('perfect number: ' + i); | |
} | |
} | |
/** | |
* @param {number} start | |
* @param {number} end | |
*/ | |
function processNumberRange(start, end) { | |
// [optim] Only even numbers | |
for (let i = start; i <= end; i += 2) { | |
if (getFactorSum(i) === i) { | |
log('perfect number: ' + i); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment