Используя ожидают прорыва долговременных процессов


У меня есть Node.js приложение / веб-API, которая работает в службе приложений Azure с помощью одного процессора.

Одна из функций должна работать в течение длительного времени, возможно, нескольких десятков секунд, в то время как сервер должен продолжить обрабатывать другие запросы, т. е. функция не должна преграждать.

Моя идея заключается в использовании await new Promise(resolve => setTimeout(resolve, 0)); в конце каждого цикла для повторной постановки на microtask в конце очереди, давая другим пользователям возможность получить в ответ тоже.

Код обработки данных с использованием node-tfidf пакет такой:

const Tfidf = require('node-tfidf');
const tfidf = new Tfidf();

for (let document of documents) {
  tfidf.addDocument(document);

  // Break the blocking code:
  await new Promise(resolve => setTimeout(resolve, 0));
}

for (let keyword of keywords) {
  tfidf.tfidfs(keyword, function(i, measure) {
      results.add({keyword, i, measure});
  });

  // Break the blocking code:
  await new Promise(resolve => setTimeout(resolve, 0));
}

Я хочу получить ваши комментарии о том, используя обещают + функции setTimeout-это разумный способ разорвать долгосрочные задачи, а также наличия большой недостаток производительности (возможно, разбить его каждые 10 циклов?).



Комментарии
2 ответа

Номер для улучшения.

Есть накладные расходы, связанные с прерыванием потока выполнения, ждать хотя это относительно мелкие.

Как представлено в вопросе есть небольшая комната для улучшения.

Проблемы с реализацией.

Наваливать закрытие

Как вы реализовали это может быть улучшено

await new Promise(resolve => setTimeout(resolve, 0));

Вы создаете новую функцию каждой итерации. Это означает, что появилась новая закрытие создается для каждой итерации, что занимает кучу пространства, ненужные расходы, которых можно избежать, предварительно определив функцию (см. уточнения ниже).

Нет нулевой ожидания

В8 (и другие браузеры) дроссель тайм-аут, это не 0мс. Что это за V8 на версии узла вы используете я не знаю, как значение регулирования претерпела изменений, и вполне может сделать это снова.

Так что если вы переходите в 100 раз и минимальное время ожидания 1 мс тайм-аут будет добавить 100 мс к общему времени для завершения итерации.

Соотношение времени итерации на время тайм-аут должен быть под контролем. Если функция не принимает 1 мс, а время ожидания добавляет 1мс двойном времени, чтобы выполнить функцию. Однако, если функция принимает 100мс, чтобы сделать одну итерацию, то время ожидания увеличивает время для завершения всего 1%.

Как вы предлагаете делать ждать Через сколько-то итераций будет улучшить соотношение. Но не зная время каждой итерации вы не можете быть уверены в разумной считать.

Предложенное улучшение.

В await может быть улучшена с помощью объекта для обработки обещания, основанные на времени, а не числа итераций.

Внутри цикла вы используете await как следует

await idler.idle();  

Которая возвращает обещание или нет в зависимости от idler параметры. Если время с момента последнего простоя контексте больше idler.interval обещание возвращается и контекста выполнения имеет значение idle (С учетом событий). Если время меньше, чем idler.interval тогда undefined возвращается и выполнение продолжается без прерывания.

Следующее является общей целью прерывания позволяют в ожидании событий, которые необходимо обработать. Больные называют это idler за неимением лучшего имени.

// idler.interval is min time between idle execution context in ms
// idler.start() sets the timer to now (not really needed for small values
// of idler.interval. Should not be called inside a process
// idler.idle() Request idle promise. Returns promise if time since last idle
// is greater than idler.interval. Else returns undefined.
export const idler = (() => {
var lastIdle = 0;
var interval = 3; // ms
function timeout(ready) { setTimeout(ready, 0) }
return {
idle() {
var now = performance.now();
if (now - lastIdle > interval) {
lastIdle = now;
return new Promise(timeout);
}
},
start() { lastIdle = performance.now() },
set interval(val) { interval = val >= 1 ? val : 1 },
get interval() { return interval },
};
})();

Интерфейс обеспечивает возможность управления периодичность (время) ожидания в контексте выполнение отложенных событий для выполнения. Время простоя не может быть непосредственно контролируемое (вы можете создать 0 задержки тайм-аута с помощью сообщения о событии, но с этим есть некоторые проблемы)

Используя вышеуказанные прост и может работать на параллельных итераторы. См. пример ниже код, как его можно использовать.

Это лишь предположение, как ваши конкретные обстоятельства мне неизвестны.

Тестирование параллелизма

Следующий пример использует регулируемой нагрузкой workLoad(time) что блоки контекста выполнения для фиксированного time.

Он запускает несколько параллельных итераторы, которые называют workLoad фиксированное число раз. Итераторы выполняются параллельно (Таймшер) и показывают, как idler может управлять выполнение переключения контекста, давая вам некоторые статистические данные о накладных.

Существует также линеечку, которая обеспечивает дополнительные события.

Цель-играть с настройками, чтобы они соответствовали ожидаемым среды и рабочих нагрузок. Тогда можно найти оптимальные настройки для ваших нужд.



const iterations = 100;  // process iteration count
const load = 10; // in ms Blocking load per iteration
const processes = 6; // Number of iteration concurrent processes
const tickTime = 1000; // ms between tick calls
const idlerInterval = 20;// ms Min interval between idle execution context
const ids = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

/* for stats */
var tickerStart;
var totalTime = 0;
var processCount = 0;
var expectedTime = 0;
var tickCpuTime = 0;

const idler = (()=>{
var lastIdle = 0;
var interval = 3; // ms
function timeout(ready) { setTimeout(ready,0) }
const API = {
idle(){
var now = performance.now();
if(now - lastIdle > interval){
lastIdle = now;
return new Promise(timeout);
}
},
start() { lastIdle = performance.now() },
set interval(val) { interval = val >= 1 ? val : 1 },
get interval() { return interval },
}
return API;
})();

// The work function
async function longFunction(cycles = 100, load = 10, id){
processCount += 1;
expectedTime += cycles * load;
const now = performance.now();
while(cycles--){
workLoad(load);
await idler.idle();
}
const time = performance.now() - now;
processCount -= 1;
results(time, id);
}

function ticker(){
const now = performance.now();
if(processCount > 0){
log("Tick " + (performance.now()-tickStart).toFixed(0) + " ms ");
setTimeout(ticker, tickTime)
}
tickCpuTime += performance.now() - now;
}

log(`Starting ${processes} iterators with ${iterations*load}ms workload each.`);
idler.interval = idlerInterval;
log(`Idler interval ${idlerInterval}ms.`);
idler.start();
var tickStart = performance.now();
for(let i = 0; i < processes; i++){
longFunction(iterations, load, ids[i]);
}
ticker();

/*========================================================================
helper functions not related to answer
========================================================================*/

function log(data){
datalog.innerHTML = `<div>${data}</div>`+datalog.innerHTML;
}
function results(time,id){
log("Id '" + id + "' took : "+(time.toFixed(3))+"ms to complete.");
if(processCount === 0) {
var totalTime = (performance.now() - tickStart) - tickCpuTime;
log("===================================================");
log("Expected time : " + (expectedTime).toFixed(0) + "ms");
log("Actual time : " + (totalTime).toFixed(0) + "ms");
log("Idler total overhead : " + (totalTime - expectedTime).toFixed(3) + "ms")
const overhead = (totalTime - expectedTime)/(processes * iterations);
log("Idler per iteration overhead : " + (overhead).toFixed(3) + "ms " + (overhead / load * 100).toFixed(2) + "% of iteration time");
log("Ticker took : " + tickCpuTime.toFixed(3) +"ms");
log("===================================================");
}
}
function workLoad(time = 1){ // blocks for time in ms
if(!isNaN(time)){
var till = performance.now() + Number(time);
while(performance.now() < till);
}
}


#datalog {
font-family : consola;
font-size : 12px;
color : #0F0;
}
body {
background : black;
}

<div id="datalog"></div>


Примечания:


  • Вы должны вырезать и вставить в чистую среду. Бежать на данной странице, добавляет неизвестный дополнительное выполнение контекста.

  • В log функция медленный (использует разметку вставки) и его время был главным образом проигнорирован. Результаты могут быть чуть выше, чем фактическое.

3
ответ дан 20 марта 2018 в 01:03 Источник Поделиться

Я опасаюсь, что это может не быть лучший подход, в зависимости от масштаба вашего сервера приложений, направленных на:


  • Вам придется загромождать свой код с помощью этих инструкций. Особенно больше времени детализации вы хотели бы получить, тем более заявления, которые вы должны вставить.

  • Договор, что каждый вычислительный лицо, регулярно выдает его исчисления времени не исполняется в одном месте, вместо этого оно само все лицо. Если один объект висит, вся система зависает. (На самом деле, это одна из причин общего назначения операционные системы с вытеснением.)

Несколько человек и я исследовал этот тогда в основе потока-Программирование реализации для JS: https://github.com/jpaulm/jsfbp.

Я рассматриваю подход действителен для небольших сред, но предпочел бы другой путь на глобальный слой, например


  • реальные темы
    Есть несколько расширений/плагинов для Node.js для этого. Может быть, кто-то может прокомментировать, насколько чисто и легко.

  • процесс нереста

Опять же, в отдельном потоке или процессе, которые можно использовать снова зеленой нитей/волокон/Яш-ЭСК подход многопоточность - если вы чувствуете потребность в ней, например, из-за простой рассуждая о вопросах параллелизма.

1
ответ дан 19 марта 2018 в 03:03 Источник Поделиться