قصة عن حل مشكلة الأداء Moment.js

تعتبر Moment.js واحدة من مكتبات JavaScript الأكثر شعبية لتحليل وتنسيق التواريخ. يستخدم WhereTo Node.js ، لذلك ، كان استخدام هذه المكتبة خطوة طبيعية تمامًا. لم يكن من المتوقع حدوث مشكلات في استخدام خادم Moment.js. في النهاية ، منذ البداية ، استخدموا هذه المكتبة في الواجهة الأمامية لعرض التواريخ وسعدوا بعملها. ومع ذلك ، فإن حقيقة أداء المكتبة جيدًا على العميل لا تعني أنه لن تكون هناك مشكلات مع الخادم أيضًا.



المادة ، التي نُنشر اليوم ترجمة لها ، مكرسة لقصة حل مشكلة الأداء Moment.js.

نمو المشروع وانخفاض الإنتاجية


في الآونة الأخيرة ، زاد عدد سجلات الرحلات التي أرجعها نظام WhereTo بنحو عشر مرات. ثم واجهنا انخفاضًا قويًا في الأداء. اتضح أن دورة العرض ، التي استغرقت أقل من 100 مللي ثانية ، تستغرق الآن أكثر من 3 ثوانٍ لعرض حوالي 5000 نتيجة بحث. بدأ فريقنا البحث. بعد العديد من جلسات التوصيف ، لاحظنا أن أكثر من 99٪ من هذا الوقت يتم إنفاقه في دالة واحدة تسمى createInZone .


تستغرق الدالة createInZone حوالي 3.3 ثانية لإكمالها.

مواصلة التحقيق في الموقف ، وجدنا أن يتم استدعاء هذه الوظيفة من خلال وظيفة parseZone . لماذا هي بطيئة جدا؟ كان لدينا شعور بأن مكتبة Moment.js مصممة لحالات الاستخدام الشائعة ، ونتيجة لذلك ، ستحاول معالجة سلسلة الإدخال بطرق مختلفة. ربما يجب عليك الحد منه؟ بعد أن قرأنا الوثائق ، اكتشفنا أن وظيفة parseZone تقبل وسيطة اختيارية تحدد تنسيق التاريخ:

 moment.parseZone(input, [format]) 

أول ما فعلناه هو محاولة استخدام وظيفة parseZone مع تمرير معلومات حول تنسيق التاريخ لها ، ولكن هذا ، كما أظهرت اختبارات الأداء ، لم يؤد إلى أي شيء:

 $ node bench.js moment#parseZone x 22,999 ops/sec ±7.57% (68 runs sampled) moment#parseZone (with format) x 30,010 ops/sec ±8.09% (77 runs sampled) 

على الرغم من أن وظيفة parseZone تعمل الآن بشكل أسرع قليلاً ، إلا أن هذه السرعة لم تكن كافية لتلبية احتياجاتنا.

التحسين الخاص بالمشروع


استخدمنا Moment.js لتحليل التواريخ التي تم استردادها من واجهة برمجة تطبيقات مزودنا (Travelport). لقد أدركنا أنها تقوم دائمًا بإرجاع البيانات بنفس التنسيق:

 "2019-12-03T14:05:00.000-07:00" 

مع العلم بذلك ، بدأنا نفهم البنية الداخلية لـ Moment.js من أجل (كما كنا نأمل) كتابة وظيفة أكثر فاعلية تؤدي إلى نفس النتائج.

إنشاء بديل أسرع ل parseZone


للبدء ، كنا بحاجة لمعرفة كيف تبدو كائنات Moment.js. كان من السهل جدا أن نفهم:

 > const m = moment() > console.log(m) Moment {  _isAMomentObject: true,  _i: '2019-12-03T14:05:00.000-07:00',  _f: 'YYYY-MM-DDTHH:mm:ss.SSSSZ',  _tzm: -420,  _isUTC: true,  _pf: { ...snip },  _locale: [object Locale],  _d: 2019-12-03T14:05:00.000Z,  _isValid: true,  _offset: -420 } 

كانت الخطوة التالية هي إنشاء مثيل لحظية دون استخدام مُنشئ:

 export function parseTravelportTimestamp(input: string) {  const m = {}  // $FlowIgnore  m.__proto__ = moment.prototype  return m } 

الآن يبدو أن لدينا الكثير من خصائص مثيل اللحظات التي يمكن أن نضعها فقط (لا أخوض في تفاصيل كيف عرفنا بهذا ، ولكن إذا نظرت إلى شفرة مصدر Moment.js ستفهمها):

 const FAKE = moment() const TRAVELPORT_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSSZ' export function parseTravelportTimestamp(input: string) {  const m = {}  // $FlowIgnore  m.__proto__ = moment.prototype  const offset = 0 // TODO  const date = new Date(input.slice(0, 23))  m._isAMomentObject = true  m._i = input  m._f = TRAVELPORT_FORMAT  m._tzm = offset  m._isUTC = true  m._locale = FAKE._locale  m._d = date  m._isValid = true  m._offset = offset  return m } 

كانت الخطوة الأخيرة من عملنا هي معرفة كيفية تحليل قيمة offset للطابع الزمني. اتضح أن هذا هو دائما نفس الموقف في السطر. نتيجةً لذلك ، تمكنا من تحسين هذا:

 function parseTravelportDateOffset(input: string) {  const hrs = +input.slice(23, 26)  const mins = +input.slice(27, 29)  return hrs * 60 + (hrs < 0 ? -mins : mins) } 

إليك ما حدث بعد وضعه معًا:

 const FAKE = moment() const TRAVELPORT_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSSZ' function parseTravelportDateOffset(input: string) {  const hrs = +input.slice(23, 26)  const mins = +input.slice(27, 29)  return hrs * 60 + (hrs < 0 ? -mins : mins) } /** *     ISO-8601,    : * - "2019-12-03T12:30:00.000-07:00" */ export function parseTravelportTimestamp(input: string): moment {  const m = {}  // $FlowIgnore  m.__proto__ = moment.prototype  const offset = parseTravelportDateOffset(input)  const date = new Date(input.slice(0, 23))  m._isAMomentObject = true  m._i = input  m._f = TRAVELPORT_FORMAT  m._tzm = offset  m._isUTC = true  m._locale = FAKE._locale  m._d = date  m._isValid = true  m._offset = offset  return m } 

اختبارات الأداء


لقد اختبرنا أداء الحل الناتج باستخدام وحدة npm القياسية. هنا هو رمز الاختبار:

 const FAKE = moment() const TRAVELPORT_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSSZ' function parseTravelportDateOffset(input: string) {  const hrs = +input.slice(23, 26)  const mins = +input.slice(27, 29)  return hrs * 60 + (hrs < 0 ? -mins : mins) } /** *     ISO-8601,    : * - "2019-12-03T12:30:00.000-07:00" */ export function parseTravelportTimestamp(input: string): moment {  const m = {}  // $FlowIgnore  m.__proto__ = moment.prototype  const offset = parseTravelportDateOffset(input)  const date = new Date(input.slice(0, 23))  m._isAMomentObject = true  m._i = input  m._f = TRAVELPORT_FORMAT  m._tzm = offset  m._isUTC = true  m._locale = FAKE._locale  m._d = date  m._isValid = true  m._offset = offset  return m } 

فيما يلي نتائج بحث أدائنا:

 $ node fastMoment.bench.js moment#parseZone x 21,063 ops/sec ±7.62% (73 runs sampled) moment#parseZone (with format) x 24,620 ops/sec ±6.11% (71 runs sampled) fast#parseTravelportTimestamp x 1,357,870 ops/sec ±5.24% (79 runs sampled) Fastest is fast#parseTravelportTimestamp 

كما اتضح ، تمكنا من تسريع تحليل الطوابع الزمنية بنحو 64 مرة. لكن كيف أثر هذا على التشغيل الفعلي للنظام؟ إليك ما حدث كنتيجة للتوصيف.


إجمالي وقت تنفيذ parseTravelportTimestamp أقل من 40 مللي ثانية.

كانت النتائج مدهشة: لقد بدأنا بـ 3.3 ثانية ، ثم انتقلنا إلى تحليل التواريخ ، ووصلنا إلى أقل من 40 مللي ثانية.

النتائج


عندما بدأنا العمل على منصتنا ، كان علينا حل عدد رهيب من المشاكل. كنا نعرف فقط أننا كنا نكرر لأنفسنا: "دعها تعمل أولاً ، ولكن يمكنك القيام بالتحسين لاحقًا".

على مدى السنوات القليلة الماضية ، ازداد تعقيد مشروعنا بشكل هائل. لحسن الحظ ، وصلنا الآن إلى المكان الذي يمكننا فيه الانتقال إلى الجزء الثاني من "تعويذة" - التحسين.

ساعدت حلول المكتبات المشروع على الوصول إلى ما هو عليه اليوم. لكننا نواجه مشكلة "مكتبة". لحلها ، علمنا أنه من خلال إنشاء آلية خاصة بنا تركز على احتياجاتنا ، يمكننا أن نجعل الكود "أسهل" من حيث استهلاك موارد النظام وتوفير وقت ثمين لمستخدمينا.

أعزائي القراء! هل واجهت مشاكل مماثلة لتلك التي تمت مناقشتها في هذه المقالة؟


Source: https://habr.com/ru/post/ar468411/


All Articles