Как я могу улучшить скорость этого кода с помощью правильных async/await/promises?

Используя Node.js, у меня появилась задача улучшить созданный код. Этот код выполняет 60 HTTP-запросов и использует для этого библиотеки.

Выполнение всех HTTP-запросов и сохранение каждого в файл занимает 30 секунд!

Говорят, что эти запросы можно выполнить за 3 секунды с помощью:

1. Правильное управление асинхронными промисами

2. Немного умнее кеширование

3. Не использовать кластер

4. Добавляйте накладные расходы только один раз

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

Итак, приведенный ниже код получает массив из 60 элементов, каждый из которых представляет собой один HTTP-запрос:

const exchanges = ccxt.exchanges

Они входят в функцию: worker = async и в конце кода: await Promise.all(workers) дождитесь их завершения.

Я не уверен, с чего начать, чтобы на самом деле быть в состоянии сократиться до 3 секунд. Как можно улучшить скорость этого кода?

'use strict';

const ccxt  = require ('ccxt')
    , log   = require ('ololog').noLocate // npm install ololog
    , fs    = require ('fs')

    // the numWorkers constant defines the number of concurrent workers
    // those aren't really threads in terms of the async environment
    // set this to the number of cores in your CPU * 2
    // or play with this number to find a setting that works best for you
    , numWorkers = 8

;(async () => {

    // make an array of all exchanges
    const exchanges = ccxt.exchanges

        .filter (id => ![ 'cap1', 'cap2' ].includes (id))

        // instantiate each exchange and save it to the exchanges list
        .map (id => new ccxt[id] ({
            'enableRateLimit': true,
        }))

    // the worker function for each "async thread"
    const worker = async function () {

        // while the array of all exchanges is not empty
        while (exchanges.length > 0) {

            // pop one exchange from the array
            const exchange = exchanges.pop()

            // check if it has the necessary method implemented
            if (exchange.has['fetchTickers']) {

                // try to do "the work" and handle errors if any
                try {

                    // fetch the response for all tickers from the exchange
                    const tickers = await exchange.fetchTickers()

                    // make a filename from exchange id
                    const filename = '/myproject/tickers/' + exchange.id + 'Tickers.json'

                    // save the response to a file
                    fs.writeFileSync(filename, JSON.stringify({ tickers }));

                } catch (e) { } //Error
            }
        }
    }

    // create numWorkers "threads" (they aren't really threads)
    const workers = [ ... Array (numWorkers) ].map (_ => worker ())

    // wait for all of them to execute or fail
    await Promise.all (workers)

}) ()

Кто сказал, что это возможно? Я имею в виду, если вы собираетесь делать это синхронно, чем ждать завершения каждого вызова, прежде чем делать следующий. Похоже, вы не хотели бы делать это синхронно.

epascarello 21.02.2019 22:46

Сократите количество запросов, которые вы делаете. Например, найти способ объединить все «fetchTickers» с «бирж» в один запрос.

James 21.02.2019 22:50

Кстати, почему вы используете writeFileSync, если производительность имеет значение? Во время выполнения синхронной операции цикл событий узла блокируется.

Alberti Buonarroti 21.02.2019 22:50

@James Я полагаю, что в библиотеке невозможно объединить все «fetchTickers» в один запрос, поскольку это совершенно разные URL-адреса.

Andreas 21.02.2019 23:05

@ Alberti Buonarroti Да, пожалуй, это единственное, что я могу улучшить. Интересно, можно ли запускать эти запросы в каком-то еще более «параллельном» подходе, чем это закодировано сейчас? Подсказки, которые я получил, заключаются в более разумном использовании async/await/promises.

Andreas 21.02.2019 23:06

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

epascarello 21.02.2019 23:11

Возможно, вы захотите проверить Как заставить запрос Axios GET ждать?. Использование await внутри цикла означает, что выполнение не будет продолжаться до тех пор, пока обещание не будет разрешено. Это замедляет ваш код. Вы должны отправить свои запросы в массив и использовать Promsie.all, чтобы дождаться, пока все промисы не будут выполнены.

Alberti Buonarroti 21.02.2019 23:16

Подумайте о том, чтобы провести тест, неизвестно, что занимает больше всего времени. sync пахнет, вы могли бы обратиться к нему, прежде чем спрашивать, уже есть fs.promises. Скорее всего, вам не нужно ограничиваться 8 работниками, потому что вы не зависите от ядер ЦП. В случае, если запросы могут быть запущены одновременно, вам могут вообще не понадобиться работники. Неизвестно, что нужно кэшировать, вероятно, конкретно для вашего случая (ccxt).

Estus Flask 21.02.2019 23:20

@ Альберти Буонарроти, это было интересно. Спасибо за ссылку и информацию. Я посмотрю ссылку, которую вы прислали. Это может быть что-то, что является узким местом.

Andreas 21.02.2019 23:24

@estus Это интересный тест. Я проверю и это. Да, это (ccxt), кажется, требует довольно много времени для инициализации при запуске кода. Возможно, несколько секунд. Возможно ли это преодолеть?

Andreas 21.02.2019 23:25
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
10
571
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я пытаюсь понять, можно ли сделать это еще быстрее. Я пытаюсь кэшировать все возможные необходимые воспоминания. Прежде чем выполнять запрос .fetchTickers().

Мне удалось снизиться даже до 9 секунд с 15 секунд, как казалось. Но этот код ниже еще на один шаг вперед, но я получаю ошибки компиляции и не уверен, что делаю неправильно.

Ошибка:

ReferenceError: идентификатор не определен

Разве id не передается в объекте «exchange», который вставляется в «exchangesArray»?

Сначала я пытаюсь поместить объект обмена в массив со всем временем, которое требуется:

var exchangesArray = [];

Затем с этим «exchangesArray» я пытаюсь выполнить функцию, которая выполняет fetchTickers:

'use strict';
const ccxt = require('ccxt');
const fs = require('fs');
const path = require('path');

//Cache some memories first
var exchangesArray = [];
(async () => {
  const allexchanges = ccxt.exchanges.filter((id) => !['coinmarketcap', 'theocean'].includes(id))
        .map(async (id) => {
            const Exchange = ccxt[id];
            const exchange = new Exchange({ enableRateLimit: true });
            if (exchange.has['fetchTickers']) {

                exchangesArray.push(exchange);
            }
        });

    await Promise.all(allexchanges);
})();

//Use cached memories to do the "fetchTickers()" as fast as possible
(async () => {
    const start = Date.now();

    const exchanges = exchangesArray;
    while (exchanges.length > 0) {

        // pop one exchange from the array
        const exchange = exchanges.pop()

        try {
            const tickers = await exchange.fetchTickers();
            const dumpFile = path.join(__dirname, 'exchanges', `${id}-Tickers.json`);
            await fs.promises.writeFile(dumpFile, JSON.stringify(tickers));
        } catch (e) {
            console.error(e);
        }

    }

    await Promise.all(exchanges);

    const end = Date.now();
    console.info(`Done in ${(end - start) / 1000} seconds`);
})();

Я борюсь с: exchangesArray.push(exchange); Кажется, что объект «обмена» не имеет всей информации, когда он вставлен в «обмены».

Andreas 22.02.2019 21:00
Ответ принят как подходящий

Я думаю, вы делаете вещи более сложными, чем они должны быть. Вы можете выполнить всю работу в обратном вызове map, а затем использовать Promise.all(promises), чтобы дождаться завершения всех операций. Этот процесс занимает больше времени, чем ожидаемые «3 секунды» (15 секунд в моем случае), и возникает много ошибок (например, отсутствует apiToken или fetchTickers не реализованы), но это может быть проблемой с моей средой (я никогда раньше не использовал ccxt и у меня нет apiTokens).

Это реализация, которую я придумал, надеюсь, она поможет вам удовлетворить ваши потребности:

const ccxt = require('ccxt');
const fs = require('fs');
const path = require('path');

(async () => {
    const start = Date.now();

    const dumps = ccxt.exchanges
        .filter((id) => !['coinmarketcap', 'theocean'].includes(id))
        .map(async (id) => {
            const Exchange = ccxt[id];
            const exchange = new Exchange({enableRateLimit: true});
            if (exchange.has['fetchTickers']) {
                try {
                    const tickers = await exchange.fetchTickers();
                    const dumpFile = path.join(__dirname, 'exchanges', `${id}-Tickers.json`);
                    await fs.promises.writeFile(dumpFile, JSON.stringify(tickers));
                } catch (e) {
                    console.error(e);
                }
            }
        });

    await Promise.all(dumps);

    const end = Date.now();
    console.info(`Done in ${(end - start) / 1000} seconds`);
})();

Спасибо! Код интересный, но файлы .json пустые? Они содержат только эту строку: "[object Object]" Может быть, это близко, и чего-то не хватает, чего я еще не понял?

Andreas 22.02.2019 02:46

Правильно, вам нужно преобразовать объект в строку перед записью в файл. Я внес правку в ответ.

Jake Holzinger 22.02.2019 02:50

Да, я как раз тоже хотел это опубликовать. Я тоже это узнал :) Спасибо за этот код. Здесь также потребовалось 15 секунд для загрузки, что является большим улучшением. Я попробую и посмотрю, можно ли разделить обмены на разные дампы/асинхронные вызовы, чтобы как-то сделать их параллельными и сделать это еще быстрее :) Спасибо!

Andreas 22.02.2019 02:54

Запросы и запись файлов уже выполняются параллельно, я не уверен, насколько еще это можно улучшить с точки зрения производительности.

Jake Holzinger 22.02.2019 03:00

Да, вы правы, я тоже не уверен. У «гуру» библиотеки, к сожалению, есть способ сделать это за 3 секунды, но он ничего не скажет, а вместо этого позвольте мне попытаться выяснить это.

Andreas 22.02.2019 03:05

Другие вопросы по теме