paint-brush
Ինչու է JavaScript Benchmarking-ը խառնաշփոթ:կողմից@asyncbanana
Նոր պատմություն

Ինչու է JavaScript Benchmarking-ը խառնաշփոթ:

կողմից AsyncBanana7m2025/01/04
Read on Terminal Reader

Չափազանց երկար; Կարդալ

JavaScript-ի չափանիշը դեռևս անհրաժեշտ է, հատկապես, քանի որ JavaScript-ն օգտագործվում է ավելի զգայուն հավելվածներում: JavaScript շարժիչներն անում են ամեն ինչ՝ մեղմելու ժամանակային հարձակումները, ինչպես նաև շարժվելով դեպի [Չհետևել] [Do Not Track] JavaScript շարժիչները միտումնավոր դարձնում են ժամանակի չափումները սխալ:
featured image - Ինչու է JavaScript Benchmarking-ը խառնաշփոթ:
AsyncBanana HackerNoon profile picture
0-item
1-item

Ես ատում եմ չափագրման կոդը, ինչպես ցանկացած մարդ (որը, այս պահին, այս դիտողների մեծ մասը, հավանաբար, ¯\ (ツ) /¯ չեն): Շատ ավելի զվարճալի է ձևացնել, որ ձեր արժեքի քեշավորումը 1000%-ով ավելացրել է կատարողականությունը, քան թեստավորել՝ տեսնելու, թե ինչ է դա արել: Ավաղ, JavaScript-ում չափորոշիչները դեռևս անհրաժեշտ են, հատկապես, քանի որ JavaScript-ն օգտագործվում է ( երբ դա չպետք է լինի? ) ավելի զգայուն հավելվածներում: Ցավոք սրտի, իր բազմաթիվ հիմնական ճարտարապետական որոշումների պատճառով JavaScript-ը չի հեշտացնում չափորոշիչները:

Ինչն է սխալ JavaScript-ի հետ:

JIT Կազմողը նվազեցնում է ճշգրտությունը (?)

Նրանց համար, ովքեր ծանոթ չեն JavaScript-ի նման ժամանակակից սցենարային լեզուների կախարդությանը, նրանց ճարտարապետությունը կարող է բավականին բարդ լինել: Միայն թարգմանչի միջոցով կոդ գործարկելու փոխարեն, որը անմիջապես դուրս է հանում հրահանգները, JavaScript շարժիչների մեծ մասն օգտագործում է ճարտարապետություն, որն ավելի նման է C-ի նման կոմպիլյատոր լեզվին.


Այս կոմպիլյատորներից յուրաքանչյուրն առաջարկում է տարբեր փոխզիջում կոմպիլյացիայի ժամանակի և գործարկման ժամանակի կատարման միջև, այնպես որ օգտագործողը կարիք չունի ծախսելու հաշվողական օպտիմիզացման կոդ, որը հազվադեպ է գործարկվում՝ միաժամանակ օգտվելով առավել առաջադեմ կոմպիլյատորի կատարողականի առավելություններից առավել հաճախ գործարկվող կոդի համար ( «թեժ ուղիներ»): Կան նաև մի քանի այլ բարդություններ, որոնք առաջանում են օպտիմալացնող կոմպիլյատորներ օգտագործելիս, որոնք ներառում են այնպիսի երևակայական ծրագրավորման բառեր, ինչպիսիք են « function monomorphism »-ը, բայց ես կխնայեմ ձեզ և կխուսափեմ այստեղ այդ մասին խոսելուց:


Ուրեմն… ինչո՞ւ է սա կարևոր հենանիշավորման համար: Դե, ինչպես դուք կարող էիք կռահել, քանի որ չափագրումը չափում է կոդի արդյունավետությունը , JIT կոմպիլյատորը կարող է բավականին մեծ ազդեցություն ունենալ: Կոդերի փոքր կտորները, երբ չափորոշվում են, հաճախ կարող են տեսնել 10x+ կատարողականի բարելավումներ ամբողջական օպտիմալացումից հետո, ինչը շատ սխալներ է պարունակում արդյունքների մեջ:


Օրինակ, ձեր ամենահիմնական չափորոշիչի կարգավորումներում (մի օգտագործեք ստորև բերված որևէ բան մի քանի պատճառներով).

 for (int i = 0; i<1000; i++) { console.time() // do some expensive work console.timeEnd() }

(Մի անհանգստացեք, մենք կխոսենք նաև console.time մասին)


Ձեր կոդի մեծ մասը կպահվի մի քանի փորձարկումներից հետո՝ զգալիորեն նվազեցնելով մեկ գործողության ժամանակը: Հենանիշային ծրագրերը հաճախ ամեն ինչ անում են այս քեշավորումը/օպտիմալացումը վերացնելու համար, քանի որ այն կարող է նաև ստիպել, որ այն ծրագրերը, որոնք փորձարկվել են ավելի ուշ, համեմատաբար ավելի արագ երևան: Այնուամենայնիվ, դուք պետք է ի վերջո հարցնեք, թե արդյոք առանց օպտիմալացման չափանիշները համապատասխանում են իրական աշխարհում կատարողականությանը:


Իհարկե, որոշ դեպքերում, ինչպես հազվադեպ մուտք գործած վեբ էջերը, օպտիմալացումը քիչ հավանական է, բայց այնպիսի միջավայրերում, ինչպիսիք են սերվերները, որտեղ ամենակարևորը կատարումն է, պետք է սպասել օպտիմալացում: Եթե դուք օգտագործում եք կոդ որպես միջին ծրագիր վայրկյանում հազարավոր հարցումների համար, ապա ավելի լավ է հուսալ, որ V8-ը օպտիմալացնում է այն:


Այսպիսով, հիմնականում, նույնիսկ մեկ շարժիչի ներսում, ձեր կոդը գործարկելու 2-4 տարբեր եղանակներ կան տարբեր կատարողականության տարբեր մակարդակներով: Օ,, նաև, որոշ դեպքերում աներևակայելի դժվար է ապահովել որոշակի օպտիմալացման մակարդակների ակտիվացում: Զվարճացիր :)։

Շարժիչները անում են ամեն ինչ, որպեսզի կանգնեցնեն ձեզ ճշգրիտ ժամանակացույցը

Գիտե՞ք մատնահետքերը: Այն տեխնիկան, որը թույլ է տվել «Չհետևել» օգտագործել՝ հետևելուն օգնելու համար: Այո, JavaScript-ի շարժիչներն ամեն ինչ անում են դա մեղմելու համար: Այս ջանքերը, ժամանակի հարձակումները կանխելու քայլի հետ մեկտեղ, հանգեցրին նրան, որ JavaScript շարժիչները միտումնավոր դարձրին ժամանակի անճշտությունը, ուստի հաքերները չեն կարող ճշգրիտ չափումներ ստանալ համակարգիչների ընթացիկ աշխատանքի կամ որոշակի գործողությունների թանկարժեքության վերաբերյալ:


Ցավոք սրտի, սա նշանակում է, որ առանց իրերը շտկելու, հենանիշերն ունեն նույն խնդիրը:


Նախորդ բաժնի օրինակը ճշգրիտ չի լինի, քանի որ այն չափում է միայն միլիվայրկյանով: Այժմ անջատեք այն performance.now() . Հիանալի:


Այժմ մենք ունենք ժամանակի դրոշմանիշներ միկրովայրկյաններով:

 // Bad console.time(); // work console.timeEnd(); // Better? const t = performance.now(); // work console.log(performance.now() - t);

Բացառությամբ… դրանք բոլորն էլ 100 մկվ-ով են: Հիմա եկեք ավելացնենք մի քանի վերնագիր՝ ժամանակային հարձակումների ռիսկը նվազեցնելու համար: Վա՜յ, մենք դեռ կարող ենք ընդամենը 5 մկվ հավելումներ: 5μs-ը, հավանաբար, բավականաչափ ճշգրտություն է շատ օգտագործման դեպքերի համար, բայց դուք ստիպված կլինեք այլ տեղ փնտրել այն ամենն, ինչ պահանջում է ավելի շատ հստակություն: Որքան գիտեմ, ոչ մի բրաուզեր թույլ չի տալիս ավելի հատիկավոր ժամանակաչափեր: Node.js-ն անում է, բայց, իհարկե, դա ունի իր խնդիրները:


Նույնիսկ եթե որոշեք գործարկել ձեր կոդը բրաուզերի միջոցով և թույլ տալ, որ կոմպիլյատորն անի իր գործը, պարզ է, որ դուք դեռ ավելի շատ գլխացավեր կունենաք, եթե ցանկանում եք ճշգրիտ ժամանակացույց: Այո, և ոչ բոլոր բրաուզերներն են հավասար:

Յուրաքանչյուր միջավայր տարբեր է

Ես սիրում եմ Bun-ը այն բանի համար, ինչ նա արել է սերվերի կողմից JavaScript-ը առաջ մղելու համար, բայց դա շատ ավելի դժվար է դարձնում սերվերների համար JavaScript-ի չափորոշիչները: Մի քանի տարի առաջ միակ սերվերի կողմից JavaScript միջավայրերը, որոնք մարդկանց հետաքրքրում էին Node.js-ն ու Deno-ն էին, որոնք երկուսն էլ օգտագործում էին V8 JavaScript շարժիչը (նույնը Chrome-ում): Փոխարենը Bun-ն օգտագործում է JavaScriptCore-ը՝ Safari-ի շարժիչը, որն ունի բոլորովին այլ կատարողական բնութագրեր:


Բազմաթիվ JavaScript միջավայրերի՝ իրենց կատարողական բնութագրերով այս խնդիրը համեմատաբար նոր է սերվերային JavaScript-ում, սակայն երկար ժամանակ տանջում է հաճախորդներին: 3 տարբեր սովորաբար օգտագործվող JavaScript շարժիչները՝ V8, JSC և SpiderMonkey Chrome-ի, Safari-ի և Firefox-ի համար, համապատասխանաբար, բոլորը կարող են զգալիորեն ավելի արագ կամ դանդաղ աշխատել համարժեք կոդի վրա:


Այս տարբերությունների օրինակներից մեկն է Tail Call Optimization (TCO): TCO-ն օպտիմիզացնում է գործառույթները, որոնք կրկնվում են իրենց մարմնի վերջում, այսպես.

 function factorial(i, num = 1) { if (i == 1) return num; num *= i; i--; return factorial(i, num); }


Փորձեք համեմատել factorial(100000) Bun-ում: Այժմ փորձեք նույնը Node.js-ում կամ Deno-ում: Դուք պետք է ստանաք այս սխալի նման.

 function factorial(i, num = 1) { ^ RangeError: Maximum call stack size exceeded


V8-ում (և Node.js-ի և Deno-ի ընդլայնմամբ), ամեն անգամ, երբ factorial() իրեն կանչում է վերջում, շարժիչը ստեղծում է միանգամայն նոր ֆունկցիայի համատեքստ, որպեսզի գործարկվի nested ֆունկցիան, որն ի վերջո սահմանափակվում է զանգերի կույտով: Բայց ինչու դա տեղի չի ունենում Բունում: JavaScriptCore-ը, որն օգտագործում է Bun-ը, իրականացնում է TCO-ն, որն օպտիմիզացնում է այս տիպի ֆունկցիաները՝ դրանք վերածելով ավելի նման մի for loop-ի.

 function factorial(i, num = 1) { while (i != 1) { num *= i; i--; } return i; }

Վերոհիշյալ դիզայնը ոչ միայն խուսափում է զանգերի կույտի սահմաններից, այլ նաև շատ ավելի արագ է, քանի որ այն չի պահանջում որևէ նոր գործառույթի համատեքստ, ինչը նշանակում է, որ վերը նշվածի նման գործառույթները տարբեր շարժիչների դեպքում տարբեր կերպ են նշվելու:


Ըստ էության, այս տարբերությունները պարզապես նշանակում են, որ դուք պետք է նշեք բոլոր շարժիչները, որոնք ակնկալում եք գործարկել ձեր կոդը, որպեսզի համոզվեք, որ մեկում արագ ծածկագիրը մյուսում դանդաղ չէ: Բացի այդ, եթե դուք մշակում եք գրադարան, որը ակնկալում եք օգտագործել բազմաթիվ հարթակներում, համոզվեք, որ ներառեք ավելի շատ էզոտերիկ շարժիչներ, ինչպիսին է Hermes-ը . նրանք ունեն կտրուկ տարբեր կատարողական բնութագրեր:

Պատվավոր հիշատակումներ

  • Աղբահանը և ամեն ինչ պատահական դադար տալու նրա միտումը.


  • JIT կոմպիլյատորի կարողությունը ջնջել ձեր ամբողջ կոդը, քանի որ դա «անհրաժեշտ չէ»:


  • Սարսափելի լայն կրակի գրաֆիկներ JavaScript մշակող գործիքների մեծ մասում:


  • Կարծում եմ՝ հասկացաք իմաստը:

Այսպիսով… Ո՞րն է լուծումը:

Ես կցանկանայի մատնանշել npm փաթեթը, որը կլուծի այս բոլոր խնդիրները, բայց իրականում այդպիսին չկա:


Սերվերի վրա դուք մի փոքր ավելի հեշտ ժամանակ ունեք: Դուք կարող եք օգտագործել d8 օպտիմիզացման մակարդակները ձեռքով կառավարելու, աղբահավաքիչը կառավարելու և ճշգրիտ ժամանակացույց ստանալու համար: Իհարկե, դրա համար լավ մշակված ուղենշային խողովակաշար ստեղծելու համար ձեզ անհրաժեշտ կլինի Bash-fu, քանի որ, ցավոք, d8-ը լավ ինտեգրված չէ (կամ ընդհանրապես ինտեգրված) Node.js-ի հետ:


Դուք կարող եք նաև միացնել որոշակի դրոշներ Node.js-ում՝ նմանատիպ արդյունքներ ստանալու համար, բայց դուք բաց կթողնեք այնպիսի գործառույթներ, ինչպիսիք են օպտիմիզացման որոշակի մակարդակների միացումը:

 v8 --sparkplug --always-sparkplug --no-opt [file]

D8-ի օրինակ՝ միացված հատուկ կոմպիլյացիոն մակարդակով (կայծ մոմ): D8-ը, ըստ լռելյայն, ներառում է GC-ի ավելի մեծ վերահսկողություն և ընդհանուր առմամբ ավելի շատ վրիպազերծման տեղեկատվություն:


Կարող եք մի քանի նմանատիպ հնարավորություններ ստանալ JavaScriptCore-ում??? Անկեղծ ասած, ես շատ չեմ օգտագործել JavaScriptCore-ի CLI-ն, և այն խիստ թերփաստաթղթավորված է: Դուք կարող եք միացնել որոշակի մակարդակներ՝ օգտագործելով դրանց հրամանի տողերի դրոշները , բայց ես վստահ չեմ, թե որքան վրիպազերծման տեղեկատվություն կարող եք առբերել: Bun-ը ներառում է նաև որոշ օգտակար չափորոշիչներ , սակայն դրանք սահմանափակված են Node.js-ով:


Ցավոք, այս ամենը պահանջում է շարժիչի հիմնական շարժիչ/փորձնական տարբերակը, որը կարող է բավականին դժվար լինել: Ես պարզեցի, որ շարժիչները կառավարելու ամենապարզ միջոցը esvu-ն է, որը զուգակցվում է eshost-cli-ի հետ, քանի որ դրանք միասին զգալիորեն հեշտացնում են շարժիչների կառավարումը և դրանց միջոցով ծածկագրի գործարկումը: Իհարկե, դեռ շատ ձեռքով աշխատանք է պահանջվում, քանի որ այս գործիքները պարզապես կառավարում են գործարկվող կոդը տարբեր շարժիչներում. դուք դեռ պետք է ինքներդ գրեք չափման կոդը:


Եթե դուք պարզապես փորձում եք սերվերի վրա հնարավորինս ճշգրիտ չափորոշել լռելյայն ընտրանքներով շարժիչը, կան Node.js գործիքներ, ինչպիսիք են mitata-ն , որոնք օգնում են բարելավել ժամանակի ճշգրտությունը և GC-ի հետ կապված սխալները: Այս գործիքներից շատերը, ինչպես Mitata-ն, կարող են օգտագործվել նաև բազմաթիվ շարժիչների համար. իհարկե, դուք դեռ պետք է ստեղծեք խողովակաշար, ինչպես վերը նշվածը:


Բրաուզերի վրա ամեն ինչ շատ ավելի բարդ է: Ես չգիտեմ որևէ լուծում ավելի ճշգրիտ ժամանակացույցի համար, և շարժիչի կառավարումը շատ ավելի սահմանափակ է: Առավելագույն տեղեկատվությունը, որը դուք կարող եք ստանալ՝ կապված բրաուզերում JavaScript-ի գործարկման ժամանակի կատարման հետ, կլինի Chrome-ի մշակող գործիքներից , որոնք առաջարկում են հիմնական կրակի գրաֆիկը և պրոցեսորի դանդաղեցման սիմուլյացիոն ծրագրերը:

Եզրակացություն

Դիզայնի միևնույն որոշումներից շատերը, որոնք JavaScript-ին (համեմատաբար) դարձրեցին կատարողական և շարժական, համեմատաբար ավելի բարդ են դարձնում համեմատականները, քան այլ լեզուներով: Կան շատ ավելի շատ թիրախներ, որոնք պետք է նշվեն, և դուք շատ ավելի քիչ վերահսկողություն ունեք յուրաքանչյուր թիրախի վրա:


Հուսանք, որ լուծումը մի օր կպարզեցնի այս խնդիրներից շատերը: Ես, ի վերջո, կարող եմ ստեղծել մի գործիք, որը կպարզեցնի խաչաձև շարժիչային և կոմպիլացիոն մակարդակների չափորոշիչները, բայց առայժմ այս բոլոր խնդիրները լուծելու համար խողովակաշար ստեղծելը բավականին մեծ աշխատանք է պահանջում: Իհարկե, կարևոր է հիշել, որ այս խնդիրները չեն վերաբերում բոլորին. եթե ձեր կոդը աշխատում է միայն մեկ միջավայրում, մի վատնեք ձեր ժամանակը այլ միջավայրերի համեմատական գնահատման վրա:


Ինչ էլ որ ընտրեք չափանիշը, հուսով եմ, որ այս հոդվածը ձեզ ցույց տվեց JavaScript-ի չափորոշիչում առկա որոշ խնդիրներ: Տեղեկացրե՛ք ինձ, եթե վերևում նկարագրված որոշ բաների իրականացման վերաբերյալ ձեռնարկը օգտակար կլինի: