LINUX.ORG.RU

Неожиданно разная скорость вычисления дизъюнкции в зависимости от перестановок операндов

 , ,


0

2

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

Первый случай:

var a = 'Android'
a === 'Android' || a === 'android'
// 186 367 955.14 ops/s ± 0.37%
var a = 'Android'
a.toLowerCase() === 'android'
// 948 396 966.94 ops/s ± 0.68%

Второй случай:

var a = 'Android'
a === 'android' || a === 'Android'
// 964 766 563.52 ops/s ± 0.23%
var a = 'Android'
a.toLowerCase() === 'android'
// 959 593 276.92 ops/s ± 0.18%

Казалось бы в первом случае, в примере с ||, проверяется первое равенство и сразу возвращается результат, согласно кратким вычислениям. А во втором случае ещё должно второе равенство провериться, потом || и только потом результат вернуться, так что по идее второй случай, с ||, должен быть медленнее первого, но получается наоборот! Второй случай быстрее почти в пять раз! Где тут собака зарыта? На toLowerCase не обращайте внимания, хотел сравнить дизъюнкцию с ним, а получил неожиданные результаты и пришлось сравнивать уже дизъюнкции между собой!

★★★★

Последнее исправление: mydibyje (всего исправлений: 1)
Ответ на: комментарий от thesis

Справедливое замечание, проверил в консоли браузера:

(() => {
  const a = 'Android';
  const getA = (ab) => {return ab === 'Android' || ab === 'android';};
  console.time("engine");
  for(var i = 0; i < 1000 * 1000 * 1000; i++) {getA(a);}
  console.timeEnd("engine");
})();

// 2016.114013671875 ms
(() => {
  const a = 'Android';
  const getA = (ab) => {return ab === 'android' || ab === 'Android';};
  console.time("engine");
  for(var i = 0; i < 1000 * 1000 * 1000; i++) {getA(a);}
  console.timeEnd("engine");
})();
// 506.461181640625 ms
mydibyje ★★★★
() автор топика
Ответ на: комментарий от mydibyje

Если запускать несколько раз то скорость становится одинаковой. Видимо компилирует в машинный код. В режиме интерпретации второй вариант действительно работает быстрей первого в 5 раз. Не знаю, почему. Какие-то приколы интерпретатора, видимо, эвристики и прочая муть. Сомневаюсь, что тебе кто-то ответит по существу.

vbr ★★★★★
()
Последнее исправление: vbr (всего исправлений: 2)
Ответ на: комментарий от mydibyje

Глянул у себя - в лисе одинаково, в хромом пятикратная разница, причем «медленный» вариант отрабатывает в полтора раза медленнее, чем в лисе.
Фу.

thesis ★★★★★
()
Ответ на: комментарий от vbr

Какие-то приколы интерпретатора

создаёт литерал (или как там оно в JS зовётся)

проверить : добавить в код const b = ‘android’ ; скорость сравняется

MKuznetsov ★★★★★
()
Ответ на: комментарий от MKuznetsov

Это очень странно, но нет:

// setup
const a = 111
const b = 222

// case 1
a === a || a === b

// case 2
a === b || a === a

Первый вариант всё так же медленнее второго.

static_lab ★★★★★
()
Ответ на: комментарий от static_lab

Поделал варианты Aa, aA, AA, aa - тоже забавные результаты.

Под node вообще вариант a === 'android' || a === 'android' стабильно самый медленный у меня выходит.

Под js91 Aa самый медленный.

Только под quickjs предсказуемо Aa и AA самые быстрые. Если не считать, что его «самая быстрость» относительно Гугла и Мозиллы сама по себе никакая не быстрость.

Toxo2 ★★★★
()
Ответ на: комментарий от Toxo2

В смысле - такое:

$cat 2.js

let c = [];
let t = ['Aa', 'aA', 'AA', 'aa'];
const a = 'Android';
const interval = 1000;
for(c[0] = 0, now = Date.now(); (Date.now() - now) < interval; ++c[0])
    {a === 'Android' || a === 'android';}
for(c[1] = 0, now = Date.now(); (Date.now() - now) < interval; ++c[1])
    {a === 'android' || a === 'Android';}
for(c[2] = 0, now = Date.now(); (Date.now() - now) < interval; ++c[2])
    {a === 'Android' || a === 'Android';}
for(c[3] = 0, now = Date.now(); (Date.now() - now) < interval; ++c[3])
    {a === 'android' || a === 'android';}
var avg = ((c.reduce((a, b) => a + b)) / c.length);
for(var i = 0; i < 4; i++)
    console.log(c[i], t[i], ((c[i] / avg) * 100).toFixed(2), c[i] > avg ? 'better' : 'worse');
и вывод:
$ node 2.js 
21770756 Aa 97.31 worse
22986390 aA 102.74 better
21726825 AA 97.11 worse
23008372 aa 102.84 better
$ js91 2.js 
20301281 Aa 101.26 better
19836506 aA 98.94 worse
20220929 AA 100.86 better
19834743 aa 98.93 worse
$ qjs 2.js 
8465483 Aa 106.37 better
7430329 aA 93.36 worse
8514123 AA 106.98 better
7425265 aa 93.30 worse
У Гугла какая-то максимально хитрая оптимизация видимо.

Toxo2 ★★★★
()
Ответ на: комментарий от futurama

Это на первом прогоне, прогнал несколько раз, результаты ниже в миллисек. Получается ФФ правильнее обрабатывает short-circuit evaluation, чем Хром.
Конечно, как заметили в начале темы, хотелось бы увидеть выхлоп байткода от Ignition и последующий маш.код от Turbofan по этому куску, но не знаю как. 🤷‍♂️

function aA() {
  const a = 'Android';
  const getA = (ab) => {return ab === 'android' || ab === 'Android';};
  console.time("ms");
  for(let i = 0; i < 1000 * 1000 * 1000; i++) {getA(a);}
  console.timeEnd("ms");
}
for(let i = 0; i < 10; i++) aA();

function Aa() {
  const a = 'Android';
  const getA = (ab) => {return ab === 'Android' || ab === 'android';};
  console.time("ms");
  for(var i = 0; i < 1000 * 1000 * 1000; i++) {getA(a);}
  console.timeEnd("ms");
}
for(let i = 0; i < 10; i++) Aa();
Chrome AaChrome aAFirefox AaFirefox aA
1.201450710191022
2.427851833154359
3.25325333194324
4.25125233024323
5.25325433114340
6.25125433024429
7.25425733134377
8.25226933014335
9.25325933004321
10.25125733134314
mydibyje ★★★★
() автор топика
Последнее исправление: mydibyje (всего исправлений: 2)
Ответ на: комментарий от mydibyje

На ноде кстати, понятно как смотреть байткод, кстати он у обеих функций одинаков, значит дело в маш.коде и скорее всего оптимизированном, а вот в браузере непонятно что делать с флагом --js-flags="--print-bytecode", ведь интересен браузер в первую очередь.

//script.js
const a = 'Android';
function aA(a){
  return a === 'android' || a === 'Android';
}
function Aa(a){
  return a === 'Android' || a === 'android';
}
aA(a);
Aa(a);
$ node --print-bytecode --print-bytecode-filter=aA script.js
41 S> 00000296735446F6 @    0 : 13 00             LdaConstant [0]
50 E> 00000296735446F8 @    2 : 6c 03 00          TestEqualStrict a0, [0]
      00000296735446FB @    5 : 98 07             JumpIfTrue [7] (0000029673544702 @ 12)
      00000296735446FD @    7 : 13 01             LdaConstant [1]
69 E> 00000296735446FF @    9 : 6c 03 01          TestEqualStrict a0, [1]
83 S> 0000029673544702 @   12 : a9                Return

А вот как смотреть оптимизированный маш.код не разобрался, флаг --print-opt-code (все флаги смотреть через node --v8-options) есть, но не выводит ничего. Приходится пользоваться --print-code, который выводит портянку на 3500 строк.

mydibyje ★★★★
() автор топика
Последнее исправление: mydibyje (всего исправлений: 2)
Ответ на: комментарий от mydibyje

Это на первом прогоне

Я много раз прогнал каждый вариант

futurama ★★★★★
()
Ответ на: комментарий от mydibyje

Оказалось, что флаг --print-opt-code таки работает 🎉, но только если выполнение функции требует времени.

//script.js
const a = 'Android';
const getA = (b) => {return b === 'android' || b === 'Android';};
console.time("ms");
for(var i = 0; i < 1000 * 1000 * 1000; i++) {getA(a);}
console.timeEnd("ms");
$ node --print-opt-code --print-opt-code-filter=getA  script.js > aA.txt

Теперь можно сравнивать.

mydibyje ★★★★
() автор топика
Последнее исправление: mydibyje (всего исправлений: 2)
Ответ на: комментарий от mydibyje

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

dsxl
()
15 февраля 2023 г.
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.