Pull to refresh
16
0

Пользователь

Send message
Я не поленилась и решила проверить.

Делаю строго по инструкции:
1. Качаю образ ReactOS-0.4.9.iso
2. Создаю виртуальную машину в hyper-v



3. В параметрах добавляю Устаревший сетевой адаптер



4. Выбираю от куда будет поступать интернет



5. После установки ReactOS сети нет, нужны драйвера для сетевой карты



6. По ссылке качаю драйвера, создаю iso файл, монтирую и нажимаю «Обновить драйвера» (там в комментариях уже кто-то кинул готовую iso)



7. Сеть есть, всё работает


В оригинале не "в JS значение выражения 0.1 + 0.2 не равно 0.3", а "везде значение выражения 0.1 + 0.2 не равно 0.3"
Многие почему-то думают, что эта проблема специфична только для js
В качестве эксперимента для реализации ленивого доступа к данным, чтобы подход оставался универсальным. Например, сейчас есть примерно такой код:

app.use(modules.common)
app.use(modules.lazyData1)
app.use(modules.lazyData2)

В самом modules.lazyData1 забираются данные из базы, они нужны только на 2 страницах, в соответствующем шаблоне, где нужны эта данные:

mixin lazyData
     | test proxy: #{await lazyData1.proxyObj.c}

Правда pug не поддерживает await (или я не нашла как), пришлось использовать кастомный pug

В целом компактно, универсально и удобно добавлять новые модули/плагины. Скорость не замеряла до и после
В 1.8 (пока еще в альфе) добавили долгожданную поддержку sql для запросов
tarantool.org/ru/doc/1.8/tutorials/sql_tutorial.html
Не обещали, а предупреждали, что такое будет. Если поспешить и автоматически такое сделать слишком быстро, то может сломать рабочие проекты

А когда, наконец уже, необработанное исключение в промисах будет честно ронять приложение
Если вам срочно надо, то этот код ловит все необработанные исключения и выходит с не-нулевым кодом ошибки:
process.on('unhandledRejection', function (err) {
    console.error('uncaughtException:', err.message)
    console.error(err.stack)
    process.exit(1)
})
оказалось, что в ноде (последняя LTS версия) forEach на порядок медленнее.

forEach хорошо оптимизируется на реальном коде, разницы с for может и не быть, но LTS версия имеет старый оптимизатор, последний турбофан умеет намного больше

Но даже это не столь важно. Проблема микробенчмарков на js в том, что сравнивать нужно в изолированной среде. Вот например типичный микробенчмарк:
Заголовок спойлера
'use strict';

const iter = 1000000
const items = []
for (let i = 0; i < iter; i++) {
    const obj = {}
    obj.a = 'a' + i
    obj.num = '1' + i
    obj.random = Math.random() * 1000 | 0
    items.push(obj)
}

function doSomething(item) {
    if (item.random % 2 === 0) {
        result += item.num | 0
    }
    else {
        str += item.a
    }
}

console.time('forEach')
var result = 0
var str = ''
items.forEach(function (item) {
    if (item.random % 2 === 0) {
        result += item.num | 0
    }
    else {
        str += item.a
    }
})
console.log(`result: ${result}, str len: ${str.length}`)
console.timeEnd('forEach')

console.time('for')
var result = 0
var str = ''
for (let i = 0; i < items.length; i++) {
    const item = items[i]
    doSomething(item)
}
console.log(`result: ${result}, str len: ${str.length}`)
console.timeEnd('for')


Результат такой:
>node fortest.js

forEach: 154.271ms
for: 42.073ms

Вроде бы for обогнал forEach в 3 раза. Но достаточно поменять местами for и forEach, сделать чтобы forEach по коду был ниже, как результат становится ровно противоположным:

>node fortest.js
for: 153.958ms
forEach: 51.414ms


То есть уже становится понятно, что в лоб тестирование не провести. Можно ухищряться по разному, но самый простой способ это изолировать 2 куска кода друг от друга. То есть удалить код относящийся к for и запустить тест отдельно для forEach, потом перезапустить node (если код из консоли запускали, а не из файла), удалить код forEach и отдельно запустить уже for. Результат будет такой:

>node fortest.js
for: 153.152ms

>node fortest.js
forEach: 146.153ms


То есть если вы хотите протестировать 2 куска кода, лучше сразу брать benchmark.js, он хоть и не идеален, но хотя бы решает вопрос с изолированным запуском:

Пример с benchmark.js
'use strict';
const Benchmark = require('benchmark')
const suite = new Benchmark.Suite

const iter = 1000000
const items = []
for (let i = 0; i < iter; i++) {
  items.push('abc' + i)
}

let result1 = ''
let result2 = ''
let result3 = ''

function doSome(item) {
  if (item[3] % 2 === 0) {
    return item
  }
  return ''
}

suite
  .add('For', function () {
    result2 = ''
    for (let i = 0; i < items.length; i++) {
      const item = items[i]
      if (item[3] % 2 === 0) {
        result2 += item
      }
    }
  })
  .add('For with function call', function () {
    result3 = ''
    for (let i = 0; i < items.length; i++) {
      result3 += doSome(items[i])
    }
  })
  .add('forEach', function () {
    result1 = ''
    items.forEach(function (item) {
      if (item[3] % 2 === 0) {
        result1 += item
      }
    })
  })

  .on('cycle', function (event) {
    console.log(String(event.target))
  })
  .on('complete', function () {
    console.log('Fastest is ' + this.filter('fastest').map('name'))

    console.log(result1.length)
    console.log(result2.length)
    console.log(result3.length)
  })
  .run({ 'async': true })


Результат:
>node test.js
For x 15.70 ops/sec ±3.85% (40 runs sampled)
For with function call x 14.72 ops/sec ±3.52% (38 runs sampled)
forEach x 15.55 ops/sec ±9.37% (30 runs sampled)
Fastest is For, forEach

Жалко нет реального куска кода на котором вы тестировали, чтобы посмотреть что с ним такое)
О каком отсутствии сайд эффекта можно говорить, если я могу случайно сделать вот так:
const isKitten = cat => cat.months = 7

И никакой const не поможет

Поэтому в условиях невозможности гарантировать отсутвие сайд-эффекта, можно только договорится, что его не будет. Но в таком случае разница между
const getKittenNames = cats =>
    cats.filter(isKitten)
        .map(getName)

И
const getKittenNames = cats => {
    const kittens = []
    for (let cat of cats) {
        if (isKitten(cat)) {
            kittens.push(getName(cat))
        }
    }
    return kittens
}


Только в количестве строк и скорости выполнения. Или в читаемости, кому-то больше filter-map/reduce нравится, а кому-то for-of
Ситуацию «они больше нигде не используются» отслеживает не оптимизатор, а сборщик мусора.

Оптимизатор умеет больше и смотрит дальше чем сборщик мусора, уже не говоря про то, что может оптимизировать код, чтобы сборщику мусора и вовсе нечего было делать
Вы:

И где же?

Поскольку в приведенном вами коде нет никаких операций нормализации строки или явного удаления объекта — строки в пуле должны оставаться все время работы программы. Чего не наблюдается.

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

Ну хорошо, вот вам 3 условие, когда строки освобождаются: Они больше нигде не используются, их пул больше не нужен, они стали частью другого пула. Оптимизатор сам умеет понимать когда это произошло, и делает это успешно

Причем тут оптимизатор? Строки успешно собирает самый обычный сборщик мусора, как и любые другие объекты.

Оптимизатор тут при том, что он понимает, когда можно срезать углы, и превращает функцию getRandomStr(), которая прогоняется не один раз, в нечто более быстро и оптимальное
Растет же потребляемая память в данном случае, скорее всего, не из-за утечек,

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

Я спорю с утверждением что любые строки интернируются в пул строк и становятся недоступны сборщику мусора, потому что это бред.
Не было такого утверждения

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

Ну если бы мы были в мире, где оптимизатор был совсем туп, то да. Что-то он умеет оптимизировать, и это заметно

И кто сказал про навечное застревание в пуле? Те строки, что сгенерированы в getRandomStr освобождаются, так как они не используются больше нигде, только их финальная копия сохранена в пуле
Когда строки свободны, а когда нет — это чуть поработав с собственным шаблонизатором вы быстро научитесь чувствовать, но это оверхед

Вот чтобы не гадать, и предлагают использовать arr.push (а лучше Map/Set), так как он даже с одинаковым расходом памяти работает быстрее на больших строках
Какое это имеет отношение к пулу строк?
Тот факт, что итоговое потребление памяти — всего 65 мегабайт, как раз и говорит о том, что строки ни в каком пуле не удерживаются.

Вернемся к тому первому примеру:
Длина получившейся строки — 2097369. Каждый символ допустим занимает 16 бит или 2 байта, то есть размер такой строки должен быть 4мб.
А реальный размер занимаемой памяти — 79мб, которая не очищается а только продолжает расти

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

Не знаю как вы умудрились прийти к выводу, что раз памяти в 10 раз (а чем дальше, тем больше) больше расходуется чем надо, то значит всё в порядке со строками

Вот еще пример:
'use strict';

function getRandomStr() {
    let testString = ''
    for (let i = 0; i < 512; i++) {
        testString += '' + Math.random() * 100 | 0
    }
    return testString
}

const iter = 1000

let string = ''
let i = 0
const intr = setInterval(() => {
    i++
    string += getRandomStr()
    if (i > iter) {
        stopIntr()
    }
}, 10)

function stopIntr() {
    clearInterval(intr)
    console.log('Занимает памяти: ' + (process.memoryUsage().rss / 1024 | 0) + 'KB')
    console.log('Хотя должно занимать всего лишь: ' + (Buffer.from(string).length / 1024 | 0) + 'KB')
}

>node sdfs.js
Занимает памяти: 77268KB
Хотя должно занимать всего лишь: 951KB

Тоесть в 77 раз больше памяти расходует, чем требуется всего лишь на 1000 итераций. В примере с arr.push было 100.000 итераций, а памяти расходовалось всего «104MB»

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

И чтобы этого избежать можно использовать arr.push(newData) и arr.join('')
Это и был мой изначальный совет (слово в слово)
Если вы до сих пор хотите гнуть линию, что никаких удержаний нет, в пул не попадают — то я пожалуй пас.
Второй пример — https://habrahabr.ru/post/327440/#comment_10194348

Результат для arr.push:
>node sdfs.js
104MB

Работает очень быстро

Результат для конкатенации строк работает вечность и всё равно в конце выдает:
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
Вот еще пример, тут символы, а не цифры, это ближе к тому, что передается в качестве боди:
'use strict';
const iter = 100000

function getRandomStr() {
    let testString = []
    for (let i = 0; i < 512; i++) {
        testString.push(String.fromCharCode(Math.random() * 255 | 0))
    }
    return testString.join('')
}

let string = []
for (let i = 0; i < iter; i++) {
    string.push(getRandomStr())
}
console.log((process.memoryUsage().rss / 1024 / 1024 | 0) + 'MB')

Результат:
>node sdfs.js
104MB

И результат довольно быстрый. А вариант с конкатенацией строк
'use strict';
const iter = 100000

function getRandomStr() {
    let testString = ''
    for (let i = 0; i < 512; i++) {
        testString += String.fromCharCode(Math.random() * 255 | 0)
    }
    return testString
}

let string = ''
for (let i = 0; i < iter; i++) {
    string += getRandomStr()
}
console.log((process.memoryUsage().rss / 1024 / 1024 | 0) + 'MB')

Работает вечность и всё равно в конце выдает:
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory


Спасибо, о конкатенации строк и памяти вообще нигде ничего нет.

Еще как есть. Вот например от одного из разработчиков ноды:
https://habrahabr.ru/post/283090/#comment_9641904
Вот выше код, его изучите
Я вот прямо сейчас написал ради интереса такую штуку:
Скрипт сделал всё верно
И на старую строку ссылку никто не удерживает, с этой стороны проблемы нет.

Строки в js при конкатенации делают 2 вещи:
1. Создается новая строка
2. Новая строка складывается в пул

Если в коде встречается, что новая строка, которую прибавляют к старой уже есть в пуле, то новая строка не создается, просто используется та что уже в пуле. Поэтому если складывать одни и те же строки, то размер, можно сказать, увеличиваться не будет

Но если новая строка всегда новая и в пуле ее нет, начинается ад
Вот правильный пример:
'use strict';

function getRandomStr() {
    let testString = ''
    for (let i = 0; i < 512; i++) {
        testString += '' + Math.random() * 100 | 0
    }
    return testString
}

let string = ""
setInterval(() => {
    if (string.length < 1024 * 1024 * 2) {
        string += getRandomStr()
        console.log(string.length, process.memoryUsage())
    }
}, 10)


В начале:
974 { rss: 20697088,
  heapTotal: 5685248,
  heapUsed: 3913832,
  external: 9284 }

Спустя 25 секунд:
2097369 { rss: 104075264,
  heapTotal: 87474176,
  heapUsed: 65276264,
  external: 9284 }

Память и не думает очищаться. Но помимо памяти есть еще и накладные расходы на всё это, которые в случае с 2гб файлом переходят разумный предел и выбрасывается исключение до расхода всей доступной памяти
Если бы закончилась память — было бы написано что закончилась память. А тут совсем другая ошибка.

Я и не говорила, что память закончилась, я сказала, что может до 2гб дело и не дошло.
А то, что память таким образом кончается намного быстрее — это особенность строк в js.

Всё верно, а старые строки будут по мере надобности подхватываться сборщиком мусора, так как на них больше нет ссылок.

Нет, не верно. Ссылка на старые строки остается, в этом и проблема

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

Дело в том, что строки не только иммутабельны, но еще и pooled. Поэтому при создании новой строки, старая остается в пуле, и остается там до тех пор пока вручную не нормализовать строку или не удалить весь объект строки

То есть сборщиком мусора сама она не очиститься

И на сколько я помню конкатенация работает быстрее чем Array#join.
UPD: пруф

В этом примере размер массива всего лишь 13 символов. Естественно конкатенация будет быстрее. Речь про огромные массивы строк. Там arr.join будет быстрее, меньше израсходует памяти и вообще будет работать, в отличии от строк, которые могут свалится с такой ошибкой как выше.
При чем я бы вообще рекомендовала вместо arr.push использовать Map.set — будет в 2 раза быстрее чем []
В js строки иммутабельны, и когда что-то плюсуется к строке, то создается новая строка. В итоге память занимает и старая строка, и новая и так далее. Таким образом память кончается намного быстрее, чем кажется, может до 2гб дело даже не дошло. Чтобы этого избежать можно использовать arr.push(newData) и arr.join('')

Но то что ни в одном из этих случаев сам сервер не «загнется», с этим я согласна
server.js
'use strict';

const http = require('http')
const routing = require('./routing')

http.Server(function (req, res) {
    let jsonString = ''

    res.setHeader('Content-Type', 'application/json; charset=utf-8')
    req.on('data', (data) => {
        jsonString += data
    })
    req.on('end', () => {
        routing(req, res, jsonString)
    })
}).listen(8000)


routing/index.js
'use strict';

const url = require('url')
const fs = require('fs')
const path = require('path')

function show404(req, res) {
    const nopath = path.join(__dirname, 'nopage', 'index.html')

    fs.readFile(nopath, (err, html) => {
        if (!err) {
            res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' })
            res.end('' + html)
        }
        else {
            const text = "Something went wrong. Please contact webmaster";
            res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' })
            res.end(text)
        }
    })
}

function loadStaticFile(pathname, req, res) {
    const staticPath = path.join(__dirname, pathname)
    if (pathname === '/favicon.ico') {
    }
    else if (/[.]mp3$/gi.test(pathname)) {
        res.writeHead(200, { 'Content-Type': 'audio/mpeg' })
    }
    else if (/[.]css$/gi.test(pathname)) {
        res.writeHead(200, { 'Content-Type': 'text/css' })
    }
    else if (/[.]js$/gi.test(pathname)) {
        res.writeHead(200, { 'Content-Type': 'application/javascript; charset=utf-8' })
    }
    fs.createReadStream(staticPath).pipe(res)
}

function router(req, res, postData) {
    const urlParsed = url.parse(req.url, true)
    const pathname = urlParsed.pathname

    if (/[.]/.test(pathname)) {
        loadStaticFile(pathname, req, res)
        return
    }
    
    let filepath = path.join(__dirname, 'dynamic', pathname, 'index.js')
    fs.access(filepath, err => {
        if (!err) {
            const routeDestination = require(filepath)
            routeDestination.promise(res, req, postData)
                .then(result => {
                    res.writeHead(200)
                    res.end('' + result)
                })
                .catch(err => {
                    res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' })
                    res.end(`${err.name}: ${err.message}`)
                })
        }
        else {
            filepath = path.join(__dirname, 'static', pathname, 'index.html')
            fs.readFile(filepath, 'utf-8', (err, html) => {
                if (err) {
                    show404(req, res)
                }
                else {
                    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
                    res.end('' + html)
                }
            })
        }
    })
}

module.exports = router


Думаю, от require избавится не получится, всё таки у запрашиваемых модулей могут быть зависимости. Только если все модули переписать соответствующим образом.

А вместо postData можно сделать:
    req.on('end', () => {
        req.body = jsonString
        routing(req, res)
    })

Чтобы не пробрасывать постоянно лишнюю переменную
Это да, я имела введу, что Set подходит под специфические задачи, а Map под любые
Просто Set специфичен (не прочитать рандомное значение, без перебора всего), а Map это почти прозрачная замена ассоциативного массива с удобным доступом

Information

Rating
Does not participate
Location
Дания
Registered
Activity