Проверка дали начинът, по който пишем JS, може да промени производителността на неговото изпълнение, дори ако кодът е същият?
Тази публикация е създадена въз основа на работата на Мартин Клепе (@aemkei) за преобразуването на JS само в шест основни знака и запазването му в изпълним вид. Това е отговор на коментар от Jonathan Means.
За тези, които четат това извън контекста, ще прекарам известно време в обяснение на проблема, преди да премина към тестове.
Какво е JSF*ck?
Освен че е NSFW, това е стил на програмиране, който използва само шест знака за писане и изпълнение на код в JS. Можем да направим това поради sth, наречено Принуда на типа. JS принудата се появява от време на време на JS конференции (и най-вече за да се направи някакъв забавен видеоклип от него), целта му е да позволи на потребителите да работят с различен тип типове, без изрично да ги поставят в един.
const a = "1" + 5 // "15"
Идеята е страхотна, но понякога (или по-вероятно доста често) хората не разбират как всъщност работи под капака. За тези, които се интересуват от идеята, ето линк към спецификацията http://www.ecma-international.org/ecma-262/#sec-type-conversion.
Повечето от тези разговори избират някои от неинтуитивните примери като
const a = [] + [] // ""
или един от най-известните (+ +"a"
връща 'NaN')
// "banana"
const yellowFruit = ("b" + "a" + +"a" + "a").toLocaleLowerCase()
Какво можем да направим с това?
Поради принудата трябва да можем да създадем всяко валидно изречение в JavaScript (включително оценка). Ако можем да направим това, тогава трябва да можем да конвертираме нашия код в sth като:
// 5+4
[!+[]+!+[]+!+[]+!+[]+!+[]]+(+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]])+[])[!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]]
Постигането на това не е толкова лесно, колкото си мислите.
За да получите число като низ, можете да използвате
+[] // "0"
+!+[] // "1"
[+!+[]]+[+[]] // "10
Получаването на низове е още по-трудно
[][[]][!+[]+!+[]] // "d" or "undefined"[2]
Той използва факта, че низовете могат да бъдат достъпни като масив от знаци и можете да генерирате „недефиниран“ низ доста лесно, като извикате [][[]]
.
Няма да обяснявам всеки един от тях подробно. Има страхотно видео, пуснато от LowLevelJS https://www.youtube.com/watch?v=sRWE5tnaxlI, което обхваща темата. Ако искате да играете с него, моля, отидете на http://www.jsfuck.com/ и опитайте своя код там.
Последици върху представянето
Както може би забелязвате, записването на целия куп код по този начин е доста скъпо. Успях да анализирам част от тестовия код в JSF конвенция и код, който изглежда така
const startT1 = Date.now();
const N = 10000;
let f = { tmp: 3, tmp2: 3, tmp3: 3, tmp4: 3, tmp5: 3, a: 'Gandalf', b: 'The Grey' };
let f2 = { tmp: 3, tmp2: 3, tmp3: 3, tmp4: 3, tmp5: 3, a: 'Jack', b: 'Sparrow' };
let f3 = { tmp: 3, tmp2: 3, tmp3: 3, tmp4: 3, tmp5: 3, a: 'Charles', b: 'Xavier' };
let f4 = { tmp: 3, tmp2: 3, tmp3: 3, tmp4: 3, tmp5: 3, a: 'Frodo', b: 'Baggins' };
let f5 = { tmp: 3, tmp2: 3, tmp3: 3, tmp4: 3, tmp5: 3, a: 'Legolas', b: 'Thranduilion' };
let f6 = { tmp: 3, tmp2: 3, tmp3: 3, tmp4: 3, tmp5: 3, a: 'Indiana', b: 'Jones' };
function test(obj) {
let result = '';
for (let i = 0; i < N; i += 1) {
result += obj.a + obj.b;
}
return result;
}
for(let i = 0; i < N; i += 1) {
test(f);
test(f2);
test(f3);
test(f4);
test(f5);
test(f6);
}
console.log("test with one shape:", Date.now() - startT1, "ms.");
Анализира на низ от 847192 реда. Така че вместо ›1KB файл, сега имаме 828KB. Можете да получите този код тук и да го изпълните само като извикате node index.js
.
Време е да започнем да тестваме нашия код!!!
Тестова среда:
Ubuntu 18.04
Node 12.13.0
Intel i7-7820X
Примери за тестове:
- стандартно повикване — https://gist.github.com/burnpiro/56be270ac032924faf48824e08995687
- стойност на низ — https://gist.github.com/burnpiro/ffaa8edac33370e1aa5126c83fb728bb
- JSF eval — https://gist.github.com/burnpiro/5a177d8bb307005c4d8f5672fe9ff0a3
Тествам стандартна функция два пъти, за да мога да забележа разликата в изпълнението на eval
.
Резултати от теста:
стандартно обаждане (50 проби)
AVG: 3274ms
Std. Dev: 7ms
Heap: 8.06MB
оценка на низ (50 проби)
AVG: 3272ms
Std. Dev: 6ms
Heap: 8.08MB
JSF оценка (50 проби):
AVG: 3241ms
Std. Dev: 8ms
Heap: 18.88MB
ЗАБЕЛЕЖКА! Стойностите на вашата машина може да са различни, защото използвате различна версия на nodeJS или различен процесор.
Изводи
Няма разлика в производителността на изпълнение между различните версии на един и същ код във V8 (възел). Това беше очаквано, но важно нещо да се види тук е количеството памет, използвано от V8. Няма почти никаква разлика между оценка и стандартно извикване на функция, но анализирането на вашия код в JSF отнема много повече памет от оригиналния.
Не е толкова изненадващо, ако го погледнете. Съхраняването и анализирането на почти 1MB файл в JS Engine трябва да отнеме много повече памет от 1KB файл.
Дори ако JSF кодът не е практичен, хората могат да разберат по-добре как работи JS, просто като прочетат част от него. Типовата принуда не е някаква магия, както казват много хора в Twitter. Знам, че „Всяка достатъчно напреднала технология е неразличима от магията“ (Първият закон на Кларк), но обичам да перифразирам това: „Всяка неразбираема технология е неразличими от магия”. Ако можете да го разберете, това вече не е магия :)
Първоначално публикувано на адрес https://erdem.pl.