Skip to content

Instantly share code, notes, and snippets.

@Krinkle
Last active March 18, 2019 23:16
Show Gist options
  • Save Krinkle/eae60734429fc06c3ec4b99d0fff5a60 to your computer and use it in GitHub Desktop.
Save Krinkle/eae60734429fc06c3ec4b99d0fff5a60 to your computer and use it in GitHub Desktop.
Distribute exponential task sizes via Node.js cluster workers.
/**
* 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