• Callback / Promises / await - что быстрее и экономичнее?

    @zuart Автор вопроса
    Да, производительность тут первична, и самый наилучший вариант "реализовать на компилируемом языке" пока недоступен... Спасибо за мнение - пока тоже к нему склоняюсь, только остается вопрос выше, продублирую тут:

    class CLS {
      asyncFn(v, fn) {
        ... некие асинхронные действия
        v.v1 = ......;
        v.v2 = ......;
        fn();
      };
    
      asyncRes(v) {
        ...
      };
    
      data(v1, v2) {
        const v = {v1: v1, v2: v2};
    
        // и вот тут и доп.вопрос
        this.asyncFn(v, () => {
          this.asyncRes(v);
        })
      };
    }


    метод data() вызывается крайне часто, в среднем 5000/сек доп вопрос заключается в том, что каждый вызов мало того, что создает объект "v" (но благодаря тому, что мы используем объект, то пробрасывать параметры/результат и переопределять отдельно значения не требуется - просто работаем прямо с объектом сразу, т.к. он передается по ссылке)...

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

    this.asyncFn(v, this.asyncRes);

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

    Вот есть ли вариант как-то это реализовать все-таки без создания этих бестолковых (анонимных "пустых") функций?
  • Callback / Promises / await - что быстрее и экономичнее?

    @zuart Автор вопроса
    Антон Швец, вот и я про то же... есть сильные опасения, что не просто асинк и промисы не улучшат ситуацию, а скорее ухудшат... и по поводу "заметить" - на штатной нагрузке в 5-10к/сек вызовов это не проявит себя, но на пиковых более 500-700к/сек - это станет очень заметно...
    Собственно текущая лапша и дала знать о своих проблемах именно на пиковых нагрузках, а как известно именно они становятся предпосылкой для рефакторинга =(((
  • Callback / Promises / await - что быстрее и экономичнее?

    @zuart Автор вопроса
    Антон Швец, если юзать async / await - то вообще легко читается, но опять же - вопрос в "накладных" доп расходах, которые и реализуют это удобство чтения, будет ли результат не "УДОБНЕЕ", а "БЫСТРЕЕ и ЛЕГЧЕ (по памяти)"...
  • Callback / Promises / await - что быстрее и экономичнее?

    @zuart Автор вопроса
    Толстый Лорри, Вот и я тоже так думаю, но тут второй вопрос встает:

    class CLS {
      asyncFn(v, fn) {
        ... некие асинхронные действия
        v.v1 = ......;
        v.v2 = ......;
        fn();
      };
    
      asyncRes(v) {
        ...
      };
    
      data(v1, v2) {
        const v = {v1: v1, v2: v2};
    
        // и вот тут и доп.вопрос
        this.asyncFn(v, () => {
          this.asyncRes(v);
        })
      };
    }


    метод data() вызывается крайне часто, в среднем 5000/сек доп вопрос заключается в том, что каждый вызов мало того, что создает объект "v" (но благодаря тому, что мы используем объект, то пробрасывать параметры/результат и переопределять отдельно значения не требуется - просто работаем прямо с объектом сразу, т.к. он передается по ссылке)...

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

    Вот есть ли вариант как-то это реализовать все-таки без создания этих бестолковых функций?
  • Callback / Promises / await - что быстрее и экономичнее?

    @zuart Автор вопроса
    Антон Швец, еще интереснее себя ведет нода, если вызвать функцию, которая в свою очередь получает переменную из параметров, потом ее передает в другую функцию + анонимную функцию-колбек, которая в свою очередь вызывает этот колбек, и тут уже память скачет, хотя по сути вышенаписанных тестов не должна бы =))) операции те же самые...
  • Callback / Promises / await - что быстрее и экономичнее?

    @zuart Автор вопроса
    profesor08, а я сказал где-то что в промисах синхронные операции выполняю?
    Попробую другими словами - любой код можно выполнить нескольими способами, какие-то будут быстрее, какие-то могут лучше с памятью работать, какие-то нечто среднее...
    Работа с колбеками - скажем так отправная точка для оценки... Там есть расходы на память, на замыкания, на создание/удаление функций и т.д.
    Промисы и await - судя по тому, что выше написано, только лишь сахар (обертка) над колбеками, соответственно это дополнительные операции/расходы ресурсов при выполнении... Когда речь идет о том, чтобы выполнять просто некие длительные операции изредка - это одно, но когда ситуация такова, что в секунду может быть до миллиона вызовов методов, которые порождают выполнение асинхронных операций, соответственно создание колбеков и замыканий и всего сопутствующего, вот тогда любые мелочи уже начинают влиять, в том числе и работа "мусорщика" тоже может сказываться - т.к. при таких активных действиях по созданию/"удалению" функций, замыканий и т.д. время очистки увеличивается, это сказывается на операциях, которые нужно выполнять в это время и они задерживаются, что вызывает свои геморои...
    Повторюсь - речь не про браузерный код, а про один из сервисов на NODE (к сожалению, какой-то очень ушлый модератор удалил тег Ноды из вопроса, тем самым внеся дезинформацию)
  • Callback / Promises / await - что быстрее и экономичнее?

    @zuart Автор вопроса
    Антон Швец, да, вы правы =)

    действительно, пока не выполняются какие-то операции с переменной, которая получена путем соединения двух других память особенно не увеличивается - только немного, но если сделать вместо const str3 поставить let str3 и потом попытаться изменить значение str3 - то память подскакивает.

    Признаю, при простых операциях действительно большого выделения памяти не происходит =)
  • Callback / Promises / await - что быстрее и экономичнее?

    @zuart Автор вопроса
    Антон Швец, к сожалению нет =(((
    в этом плане мне очень нравится старый добрый C, где в функцию можно передать конкретно или сам объект/строку/число и т.д. или ссылку на него, сделать именно так, как нужно...

    А пример с str1 + str2 - вы правы только отчасти... именно в таком формате "str1 + str2" - действительно ничего не изменится, т.к. это просто операция, результат которой никуда не сохраняется, но если добавить до "str3 = str1 + str2", то ситуация резко изменится, потребление памяти увеличится
  • Callback / Promises / await - что быстрее и экономичнее?

    @zuart Автор вопроса
    Эээээ... о браузерах никакой речи нет, это чисто NODE-вский сервис, браузеры тут никаким боком...
    И вопрос совершенно не о том, как удобнее - самый удобный вариант с async/await с точки зрения именно синтаксиса.
    Но не всегда то, что удобнее - лучше по производительности.

    Самый простой пример из моей практики был такой: на вход подавался очень длинный массив чисел в строковом виде и выровненный по длине ("1234", "0023", и т.д. - для понимания), нужно было вернуть отсортированный массив. Так вот просто по синтаксису сделать просто Array.sort(....) оказалось по времени выполнения почти в 3 раза дольше, чем выполнить ту же самую операцию в три шага: сначала привести весь массив к числовым значениям, потом отсортировать числа, и потом по результату пройти и привести снова к строкам элементы с выравниванием по длине... Если смотреть по коду - покажется, что идиотизм туда-сюда гонять типизацию, а по результату - более быстрая работа, а при массивах в 100-200 тыс элементов это оооочень сильно заметно было.

    Было наблюдение, которое дает о себе знать именно при большом количестве повторений. Есть метод, который дополняет строку до определенной длины (padEnd или padStart) - так вот использование этого метода для получения результата по выполнению более чем на 50% дольше по времени, если взять и к строке просто прицепить "с запасом" строку из символов заполнения, а потом применить обрезку...

    Так вот и тут то же самое - важен не столько синтаксис (в конце концов вынести в отдельный метод колбек не проблема, чтобы избежать глубокого вложения), да сложнее для понимания... Тут в первую очередь важны затраты - пока мои опасения подтверждаются, что по сути промисы и асинки - это только "обертка" вокруг простых колбеков, т.е. все их проблемные места (затраты на создание/удаление анонимных функций, замыкания и т.д.) присуствуют, а к ним еще и накладные расходы на этот сахар добавляются (который более или менее "сладкий" в зависимости от реализации - нативные промисы или Блюберд дают разные результаты).
  • Callback / Promises / await - что быстрее и экономичнее?

    @zuart Автор вопроса
    Отвечаю на свой же вопрос...

    Действительно при передаче в колбек переменной как объекта (т.е. по ссылке) работа с ней в колбеке происходит напрямую с исходной областью памяти. Это радует и дает надежду заменить передачу набора переменых в вызовах на один объект из этих переменных и обрабатывать их без всяких дополнительных выделений областей в памяти под "дублирующиеся" данные =)

    Т.е. лучше использовать вместо:
    function fName (var1, var2, var3) {... работаем с var1, var2, var3 ... }


    передачу объекта с полями:
    function fName (vars) { ... работаем с vars.var1, vars.var2, vars.var3 ... }


    И если нужно менять исходные var1,2,3 то в первом варианте нужно вернуть из функции новые значения как результат (а с учетом того, что переменных несколько, то возвращать придется объектом, поля которого опять же копируются из переменных - опять имеем выделение памяти), во втором варианте значения меняются прямо в функции и вообще возвращать ничего в принципе не требуется.
  • Callback / Promises / await - что быстрее и экономичнее?

    @zuart Автор вопроса
    Слишком накладно пробовать =))) суть как раз в том, что все сейчас на колбеках написано, но написано реально "адово", и складывается ощущение, что основные потери идут как раз по двум пунктам:
    - постоянное создание/удаление функций-колбеков (местами стрелочные, местами простые анонимные)
    - собственно большое количество переменных и констант в замыканиях
    и есть еще пока только предположения, что эти замыкания не всегда чистятся сборщиком мусора, и соответственно со временем выедают память (в сутки примерно полтора-два гига уходит)...

    Вот и волнует вопрос - все эти промисы/асинки реально "обертка" обычных колбеков, со всеми сопутствующими доп. расходами по памяти/процессору или все-таки некая реализация, которая лучше работает с памятью хотя бы...
  • Callback / Promises / await - что быстрее и экономичнее?

    @zuart Автор вопроса
    nrgian, я не знаю, к сожалению... Антон ниже верно написал, что требовательные по ресурсам/скорости вещи лучше писать не на JS, но что досталось, с тем и работаем =(((
    Была еще мыслишка самые узкие места переписать в какие-нибудь модули на чем-то компилируемом, но пока решили попытаться пойти по пути оптимизации кода, ну а если не получится, тогда уже думать о другом ЯП...
  • Callback / Promises / await - что быстрее и экономичнее?

    @zuart Автор вопроса
    Ок, тогда Антон, чисто "лабораторный" вариант, подтвердите или опровергните меня, плз.

    class Test {
      constructor() {
        this.s = "a";
      }
      a1(str, fn) {
        str += this.s;
        fn(str);
      };
      b1() {
        let t1 = "12";
        this.a1(t1, (r1) => {
          console.info(t1); // 12
          console.info(r1); // 12a
        });
      };
    }


    тут получается, что сначала выделяется память с замыканием под t1, потом при вызове this.a1 она "клонируется" (т.е. выделяется снова память), дальше с ней операции выполняются и выполняется вызов колбека и снова получаем еще одно выделение памяти под переданное значение r1

    Будет ли польза, если, например, заменить t1 = "12", на объект, например t1 = {"val": "12"} - по идее ведь тогда передача должна быть не по значению, а по ссылке, и тогда выполняемые действия с t1.val в методе a1 должны выполняться с той самой областью памяти, которая была выделена в методе b1.

    class Test {
      constructor() {
        this.s = "a";
      }
      a1(str, fn) {
        str.val += this.s;
        fn();
      };
      b1() {
        let t1 = {"val": "12"};
        this.a1(t1, (r1) => {
          console.info(t1.val); // 12a
          console.info(r1.val); // 12a
        });
      };
    }


    Мое предположение верно? Этот "хак" может помочь при работе с памятью? И как сборщик мусора с таким будет работать?
  • Callback / Promises / await - что быстрее и экономичнее?

    @zuart Автор вопроса
    Да, там колбеки не больше 2-х уровней вложенности, причем часть из них проще и гораздо лучше перевести из варианта вложения в вариан вызова метода, тогда получается вообще один уровень.
    Но меня в данном случае волнует еще и такой момент, как замыкания - с переменными, которые нужны в обработчиках колбеков, но в то же время и нужны на уровне выше.
    С вариантом async как видите, все гораздо проще и предположительно выгоднее "по памяти", хотя х.з. что там под капотом получается, может и там тоже черти что...
  • Callback / Promises / await - что быстрее и экономичнее?

    @zuart Автор вопроса
    Сергей Горностаев, с этим согласен, но вопрос все-равно ведь остается... Если реализация промисов "родная" и "чужая" дают разные "показатели", то получается, что все-таки это не совсем "расворимый сахар"? И тогда возвращаемся к вопросу - насколько этот сахар лучше чистых колбеков или хуже не в плане синтаксиса, а именно в плане производительности и потребении памяти (особенно то, что касается замыканий)... В примере выше с обычными колбеками переменную вообще лучше выносить в свойства класса, чтобы с ней можно было нормально работать (хотя по сути своей она там нафиг не нужна болтаться, к тому же может быть и конфликтная ситуация, если updateMovies() будет вызвана как бы "параллельно" несколько раз), тогда как на async / wait она вполне хорошо себя чувствует в методе класса...
  • Callback / Promises / await - что быстрее и экономичнее?

    @zuart Автор вопроса
    Если этот "сахар" реально не влияет и "растворяется", то почему при сравнениях нативных промисов и их реализации в bluebirds дают очень разные результаты и по скорости выполнения и по потребляемым ресурсам. Значит все-таки реализация этого "сахара" оказывает какое-то влияние?
  • Насколько забивает память "const self = this" в методах классов?

    @zuart Автор вопроса
    Robur, скорее не "из принципа", и даже больше скажу - я расслабился еще на этапе уточнения по поводу стрелочных функций. Но Ваш комментарий просто пробудил интерес, существуют ли какие-то техники, которые позволят сделать еще лучше. Чистое любопытство, но жгучий интерес =)

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

    Форкнуть что-то, интересный вариант, но думаю, что вы и сами понимаете, что это в некотором роде жесть в плане реализации, хотя и даст наибольший профит по всем параметрам - использование памяти, быстродействие и, возможно, гибкость...
  • Насколько забивает память "const self = this" в методах классов?

    @zuart Автор вопроса
    Robur, ок, хорошо, может быть пример с объектом "helper" не очень получился наглядным... Давайте примем вариант, что helper - это объект работы с REDIS например, или с MONGO - не суть с чем, это просто объект какого-то модуля из NPM. Они на вход принимают конкретные параметры, и выдают результат с конкретными данными (аргументами функций обратного вызова).
    Ну вот как можно тут решить задачу, если нужно в обработчик передать не только результат вызова метода объекта модуля, но и какие-то доп. данные?
    ЗЫ. я не ищу легких путей, он существует только один и всем известный "махнуть рукой и послать все на йух", но это не наш путь =)
  • Насколько забивает память "const self = this" в методах классов?

    @zuart Автор вопроса
    Robur, боюсь, что нет... в данном случае это так для примера упрощенный вид, а вообще там параметризация частично из входных данных, частично из расчетных в методе req(){...} - вот и выходит, что без создания анонимки одна фигня не обойтись, как бы мне этого не хотелось. Сам понимаю, что на каждый вызов создавать новую "обертку" и потом ждать, когда там мусорщик доберется до всего, что с ней связано и вычистит, это не самый лучший вариант - но как иначе?
    Даже если делать некую общую область памяти и в нее пихать такие "пробрасываемые" данные, все равно ведь надо их как-то идентифицировать, чтобы потом обработчик функции обратного вызова понимал, какой блок данных надо запросить из этого "общего банка"... К тому же потребуется химичить и своего "мусорщика", который будет чистить этот "банк" от лишних данных по некоему алгоритму (аля таймаут или еще чего)...
  • Насколько забивает память "const self = this" в методах классов?

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

    вместо:
    function req(params, ext) {
    	helper.method(params, (body) => {
    		this.result(body, ext);
    	});
    };


    использовать:
    function req(params, ext) {
    	helper.method(params, this.result);
    };


    Но встает вопрос в пааметре "ext" - как его транслировать в this.result - кроме как создавать эту "анонимную обертку". Может подсказать сможете, если такое вообще возможно?