Как движок JS работает с явно типизированными вычислениями? - PullRequest
0 голосов
/ 22 февраля 2019

В последнее время меня интересует метапрограммирование.Мы можем взять общее вычисление, в данном случае среднее, и создать для него эффективную, четко прописанную функцию.Здесь я создаю в mean2 функцию, которая будет вычислять среднее явно (без циклов).Как правило, эти явные функции выполняются быстрее.Но я испытываю интересное поведение.В мои сроки для массива размером 50, запускаемого в циклах 4e7, явная функция выигрывает легко, как и ожидалось:

символьный: 2479.273ms |литерал: 60,572 мс

Но при увеличении начального размера массива от 50 до 55, производительность резко падает.

символьный: 2445,357мс |литерал: 3221,829мс

Что может быть причиной этого?

const A = new Float64Array(50).map(Math.random)

const mean1 = function (A) {
    let sum = 0

    for (let i = 0; i < A.length; i++)
        sum += A[i]

    return sum / A.length
}

const mean2 = (function (A) {
    return new Function('A', `
        return (${new Array(A.length).fill(null).map(function (_, i) {
            return `A[${i}]`
        }).join('+')}) / ${A.length}
    `)
})(A)

console.time('symbolic')
for (let i = 0; i < 4e7; i++)
    mean1(A)
console.timeEnd('symbolic')


console.time('literal')
for (let i = 0; i < 4e7; i++)
    mean2(A)
console.timeEnd('literal')

1 Ответ

0 голосов
/ 15 мая 2019

Результат кэшируется во 2-м примере, он имеет мало общего с методом «метапрограммирования» (или, точнее, с отсутствием цикла).

Это не так простопрямо доказать с помощью JavaScript;в скомпилированном языке вы обычно просто смотрите на сборку и видите, что компилятор пытался быть каким-то хитрым и оптимизировать ваш цикл внутри таймера.Среда выполнения JS, очевидно, более непрозрачна и сложна, выходные данные не так легко доступны.Вместо этого вы можете проверить его по доверенности, наблюдая за поведением различных, но эквивалентных форм, чтобы увидеть, что вызывает предварительную оптимизацию.

Следующие небольшие изменения вашей функции "mean2" избегают оптимизации кэширования в nodejs (v8) во время тестирования:

Назначить переменной области действия перед возвратом:

const mean2 = (function (A) {
    return new Function('A', `
        let sum = (${new Array(A.length).fill(null).map(function (_, i) {
            return `A[${i}]`
        }).join('+')}) / ${A.length};
        return sum;
    `)
})(A)

Использовать A.length ссылку вместо литерала:

const mean2 = (function (A) {
    return new Function('A', `
        return (${new Array(A.length).fill(null).map(function (_, i) {
            return `A[${i}]`
        }).join('+')}) / A.length
    `)
})(A)

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...