Xterm.js: تحسينات أداء المخزن المؤقت

تم إنشاؤها على ١٣ يوليو ٢٠١٧  ·  73تعليقات  ·  مصدر: xtermjs/xterm.js

مشكلة

ذاكرة

في الوقت الحالي ، يستهلك المخزن المؤقت الخاص بنا قدرًا كبيرًا من الذاكرة ، خاصة بالنسبة للتطبيق الذي يقوم بتشغيل محطات طرفية متعددة مع مجموعة التمرير العكسي الكبيرة. على سبيل المثال ، يستهلك العرض التوضيحي الذي يستخدم محطة 160 × 24 مع ملء 5000 تمرير خلفي حوالي 34 ميجا بايت من الذاكرة (انظر https://github.com/Microsoft/vscode/issues/29840#issuecomment-314539964) ، تذكر أن هذه مجرد محطة طرفية واحدة وستحتاج شاشات 1080 بكسل من المحتمل أن تستخدم محطات طرفية أوسع. أيضًا ، من أجل دعم truecolor (https://github.com/sourcelair/xterm.js/issues/484) ، ستحتاج كل شخصية لتخزين نوعين إضافيين number مما سيضاعف تقريبًا من استهلاك الذاكرة الحالي من المخزن المؤقت.

الجلب البطيء لنص الصف

هناك مشكلة أخرى تتمثل في الحاجة إلى جلب النص الفعلي للسطر بسرعة. يرجع سبب بطء هذا إلى الطريقة التي يتم بها عرض البيانات ؛ يحتوي السطر على مجموعة من الأحرف ، كل منها يحتوي على سلسلة أحرف واحدة. لذلك سنقوم ببناء الخيط وبعد ذلك سيكون جاهزًا لجمع القمامة مباشرة بعد ذلك. في السابق لم نكن بحاجة إلى القيام بذلك على الإطلاق لأن النص تم سحبه من المخزن المؤقت للسطر (بالترتيب) وعرضه على DOM. ومع ذلك ، أصبح هذا أمرًا مفيدًا بشكل متزايد على الرغم من أننا نقوم بتحسين xterm.js بشكل أكبر ، فإن ميزات مثل التحديد والروابط تسحب هذه البيانات. مرة أخرى باستخدام مثال التمرير 160x24 / 5000 ، يستغرق الأمر 30-60 مللي ثانية لنسخ المخزن المؤقت بأكمله على منتصف 2014 Macbook Pro.

دعم المستقبل

هناك مشكلة أخرى محتملة في المستقبل وهي عندما ننظر إلى تقديم بعض نماذج العرض التي قد تحتاج إلى تكرار بعض أو كل البيانات الموجودة في المخزن المؤقت ، ستكون هناك حاجة إلى هذا النوع من الأشياء لتنفيذ إعادة التدفق (https://github.com/sourcelair /xterm.js/issues/622) بشكل صحيح (https://github.com/sourcelair/xterm.js/pull/644#issuecomment-298058556) وربما تكون هناك حاجة أيضًا لدعم برامج قراءة الشاشة بشكل صحيح (https://github.com /sourcelair/xterm.js/issues/731). سيكون من الجيد بالتأكيد وجود مساحة كبيرة للمناورة عندما يتعلق الأمر بالذاكرة.

بدأت هذه المناقشة في https://github.com/sourcelair/xterm.js/issues/484 ، وهذا يخوض في مزيد من التفاصيل ويقترح بعض الحلول الإضافية.

أميل إلى الحل 3 وأتجه نحو الحل 5 إذا كان هناك وقت وأظهر تحسنًا ملحوظًا. سوف أحب أي ردود فعل! / ccjerch ، mofux ، rauchg ، parisk

1. حل بسيط

هذا هو ما نفعله الآن بشكل أساسي ، فقط مع إضافة truecolor fg و bg.

// [0]: charIndex
// [1]: width
// [2]: attributes
// [3]: truecolor bg
// [4]: truecolor fg
type CharData = [string, number, number, number, number];

type LineData = CharData[];

الايجابيات

  • بسيط جدا

سلبيات

  • يتم استهلاك الكثير من الذاكرة ، مما يؤدي إلى مضاعفة استخدامنا الحالي للذاكرة تقريبًا وهو مرتفع جدًا بالفعل.

2. سحب النص من CharData

سيؤدي هذا إلى تخزين السلسلة مقابل السطر بدلاً من الخط ، ومن المحتمل أن يشهد هذا مكاسب كبيرة جدًا في التحديد والربط وسيكون أكثر فائدة مع مرور الوقت للوصول السريع إلى سلسلة السطر بأكملها.

interface ILineData {
  // This would provide fast access to the entire line which is becoming more
  // and more important as time goes on (selection and links need to construct
  // this currently). This would need to reconstruct text whenever charData
  // changes though. We cannot lazily evaluate text due to the chars not being
  // stored in CharData
  text: string;
  charData: CharData[];
}

// [0]: charIndex
// [1]: attributes
// [2]: truecolor bg
// [3]: truecolor fg
type CharData = Int32Array;

الايجابيات

  • لا حاجة لإعادة بناء الخط متى احتجنا إليه.
  • ذاكرة أقل من اليوم بسبب استخدام Int32Array

سلبيات

  • بطيئة في تحديث الأحرف الفردية ، يجب إعادة إنشاء السلسلة بأكملها لتغييرات الأحرف الفردية.

3. تخزين السمات في نطاقات

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

type LineData = CharData[]

// [0]: The character
// [1]: The width
type CharData = [string, number];

class CharAttributes {
  public readonly _start: [number, number];
  public readonly _end: [number, number];
  private _data: Int32Array;

  // Getters pull data from _data (woo encapsulation!)
  public get flags(): number;
  public get truecolorBg(): number;
  public get truecolorFg(): number;
}

class Buffer extends CircularList<LineData> {
  // Sorted list since items are almost always pushed to end
  private _attributes: CharAttributes[];

  public getAttributesForRows(start: number, end: number): CharAttributes[] {
    // Binary search _attributes and return all visible CharAttributes to be
    // applied by the renderer
  }
}

الايجابيات

  • ذاكرة أقل من اليوم على الرغم من أننا نقوم أيضًا بتخزين بيانات حقيقية
  • يمكن تحسين تطبيق السمات ، بدلاً من التحقق من سمة كل حرف فردي وتقسيمها إلى السمة السابقة
  • يتضمن تعقيد تخزين البيانات داخل مصفوفة ( .flags بدلاً من [0] )

سلبيات

  • يعد تغيير سمات نطاق من الأحرف داخل نطاق آخر أكثر تعقيدًا

4. ضع السمات في ذاكرة التخزين المؤقت

الفكرة هنا هي الاستفادة من حقيقة أنه لا يوجد عمومًا العديد من الأنماط في أي جلسة طرفية واحدة ، لذلك لا ينبغي إنشاء أقل عدد ممكن من الأساليب وإعادة استخدامها.

// [0]: charIndex
// [1]: width
type CharData = [string, number, CharAttributes];

type LineData = CharData[];

class CharAttributes {
  private _data: Int32Array;

  // Getters pull data from _data (woo encapsulation!)
  public get flags(): number;
  public get truecolorBg(): number;
  public get truecolorFg(): number;
}

interface ICharAttributeCache {
  // Never construct duplicate CharAttributes, figuring how the best way to
  // access both in the best and worst case is the tricky part here
  getAttributes(flags: number, fg: number, bg: number): CharAttributes;
}

الايجابيات

  • استخدام الذاكرة مشابه لليوم على الرغم من أننا نقوم أيضًا بتخزين بيانات truecolor
  • يتضمن تعقيد تخزين البيانات داخل مصفوفة ( .flags بدلاً من [0] )

سلبيات

  • توفير ذاكرة أقل من نهج النطاقات

5. الهجين 3 و 4

type LineData = CharData[]

// [0]: The character
// [1]: The width
type CharData = [string, number];

class CharAttributes {
  private _data: Int32Array;

  // Getters pull data from _data (woo encapsulation!)
  public get flags(): number;
  public get truecolorBg(): number;
  public get truecolorFg(): number;
}

interface CharAttributeEntry {
  attributes: CharAttributes,
  start: [number, number],
  end: [number, number]
}

class Buffer extends CircularList<LineData> {
  // Sorted list since items are almost always pushed to end
  private _attributes: CharAttributeEntry[];
  private _attributeCache: ICharAttributeCache;

  public getAttributesForRows(start: number, end: number): CharAttributeEntry[] {
    // Binary search _attributes and return all visible CharAttributeEntry's to
    // be applied by the renderer
  }
}

interface ICharAttributeCache {
  // Never construct duplicate CharAttributes, figuring how the best way to
  // access both in the best and worst case is the tricky part here
  getAttributes(flags: number, fg: number, bg: number): CharAttributes;
}

الايجابيات

  • بروتنالي الأسرع والأكثر كفاءة في الذاكرة
  • ذاكرة فعالة للغاية عندما يحتوي المخزن المؤقت على العديد من الكتل ذات الأنماط ولكن فقط من أنماط قليلة (الحالة الشائعة)
  • يتضمن تعقيد تخزين البيانات داخل مصفوفة ( .flags بدلاً من [0] )

سلبيات

  • أكثر تعقيدًا من الحلول الأخرى ، قد لا يكون من المفيد تضمين ذاكرة التخزين المؤقت إذا احتفظنا بالفعل بـ CharAttributes لكل كتلة؟
  • النفقات الزائدة في الكائن CharAttributeEntry
  • يعد تغيير سمات نطاق من الأحرف داخل نطاق آخر أكثر تعقيدًا

6. هجين 2 و 3

يأخذ هذا الحل 3 ولكنه يضيف أيضًا سلسلة نصية ذات تقييم كسول للوصول السريع إلى نص السطر. نظرًا لأننا نقوم أيضًا بتخزين الأحرف في CharData يمكننا تقييمها بتكاسل.

type LineData = {
  text: string,
  CharData[]
}

// [0]: The character
// [1]: The width
type CharData = [string, number];

class CharAttributes {
  public readonly _start: [number, number];
  public readonly _end: [number, number];
  private _data: Int32Array;

  // Getters pull data from _data (woo encapsulation!)
  public get flags(): number;
  public get truecolorBg(): number;
  public get truecolorFg(): number;
}

class Buffer extends CircularList<LineData> {
  // Sorted list since items are almost always pushed to end
  private _attributes: CharAttributes[];

  public getAttributesForRows(start: number, end: number): CharAttributes[] {
    // Binary search _attributes and return all visible CharAttributes to be
    // applied by the renderer
  }

  // If we construct the line, hang onto it
  public getLineText(line: number): string;
}

الايجابيات

  • ذاكرة أقل من اليوم على الرغم من أننا نقوم أيضًا بتخزين بيانات حقيقية
  • يمكن تحسين تطبيق السمات ، بدلاً من التحقق من سمة كل حرف فردي وتقسيمها إلى السمة السابقة
  • يتضمن تعقيد تخزين البيانات داخل مصفوفة ( .flags بدلاً من [0] )
  • وصول أسرع إلى سلسلة الخط الفعلية

سلبيات

  • ذاكرة إضافية بسبب التعلق بسلاسل الخط
  • يعد تغيير سمات نطاق من الأحرف داخل نطاق آخر أكثر تعقيدًا

حلول لن تنجح

  • لن يعمل تخزين السلسلة كـ int داخل Int32Array حيث يستغرق الأمر وقتًا طويلاً لتحويل int مرة أخرى إلى حرف.
areperformance typplan typproposal

التعليق الأكثر فائدة

الوضع الحالي:

بعد، بعدما:

ال 73 كومينتر

طريقة أخرى يمكن مزجها: استخدام indexeddb أو websql أو واجهة برمجة تطبيقات نظام الملفات لإخراج مدخلات التمرير غير النشطة إلى القرص 🤔

اقتراح رائع. أوافق على أن 3. هي أفضل طريقة للذهاب الآن لأنها تتيح لنا حفظ الذاكرة مع دعم اللون الحقيقي أيضًا.

إذا وصلنا إلى هناك واستمرت الأمور على ما يرام ، فيمكننا بعد ذلك التحسين كما هو مقترح في 5. أو بأي طريقة أخرى تتبادر إلى أذهاننا في ذلك الوقت وتكون منطقية.

3. رائع 👍.

mofux ، في حين أن هناك بالتأكيد حالة استخدام لاستخدام التقنيات المدعومة من تخزين القرص لتقليل أثر الذاكرة ، يمكن أن يؤدي ذلك إلى تدهور تجربة المستخدم للمكتبة في بيئات المتصفح التي تطلب من المستخدم الإذن باستخدام تخزين القرص.

فيما يتعلق بدعم المستقبل :
كلما فكرت في الأمر ، زادت فكرة امتلاك WebWorker الذي يقوم بكل العمل الشاق المتمثل في تحليل بيانات tty ، والحفاظ على المخازن المؤقتة للخطوط ، ومطابقة الروابط ، ومطابقة رموز البحث المميزة ومثل هذه النداءات بالنسبة لي. القيام بالعمل الشاق بشكل أساسي في سلسلة خلفية منفصلة دون حظر واجهة المستخدم. لكن أعتقد أن هذا يجب أن يكون جزءًا من مناقشة منفصلة ربما تجاه إصدار 4.0 😉

+100 حول WebWorker في المستقبل ، لكنني أعتقد أننا بحاجة إلى تغيير إصدارات قائمة المتصفحات التي ندعمها لأنه لا يمكن لجميعهم استخدامها ...

عندما أقول Int32Array ، سيكون هذا مصفوفة عادية إذا لم تكن مدعومة من قبل البيئة.

mofux التفكير الجيد مع WebWorker في المستقبل 👍

AndrienkoAleksandr نعم إذا أردنا استخدام WebWorker فسنظل بحاجة إلى دعم البديل أيضًا من خلال اكتشاف الميزة.

قائمة رائعة :)

أميل أيضًا إلى الانحناء نحو 3. لأنه يعد بخفض كبير في استهلاك الذاكرة لأكثر من 90٪ من استخدام الجهاز الطرفي النموذجي. يجب أن يكون تحسين ذاكرة Imho هو الهدف الرئيسي في هذه المرحلة. قد يكون المزيد من التحسين لحالات استخدام محددة قابلاً للتطبيق علاوة على ذلك (ما يتبادر إلى ذهني: "اللوحة مثل التطبيقات" مثل ncurses وستستخدم هذه الكثير من تحديثات الخلية المفردة وستؤدي نوعًا ما إلى تدهور قائمة [start, end] بمرور الوقت) .

AndrienkoAleksandr نعم أحب فكرة عامل الويب أيضًا لأنها يمكن أن ترفع بعض العبء عن الموضوع الرئيسي. المشكلة هنا هي (إلى جانب حقيقة أنه قد لا تكون مدعومة من قبل جميع أنظمة الهدف المطلوبة) هي الجزء _some_ - لم يعد جزء JS مشكلة كبيرة بعد الآن مع جميع التحسينات التي شهدها xterm.js على مدار الوقت. المشكلة الحقيقية في الأداء هي تخطيط / عرض المتصفح ...

mofux يعد الترحيل إلى بعض "الذاكرة الأجنبية" فكرة جيدة ، على الرغم من أنه يجب أن يكون جزءًا من بعض التجريد العالي وليس من "عنصر واجهة مستخدم طرفي تفاعلي" مثل xterm.js. يمكن تحقيق ذلك عن طريق ملحق imho.

Offtopic: هل أجريت بعض الاختبارات باستخدام المصفوفات مقابل المصفوفات المصنّعة مقابل المصفوفات المصنّعة مقابل asm.js. كل ما يمكنني قوله - OMG ، إنه مثل 1 : 1,5 : 10 للأحمال والمجموعات المتغيرة البسيطة (على FF أكثر). إذا بدأت سرعة JS النقية في الإضرار حقًا ، فقد يكون "استخدام ASM" موجودًا للإنقاذ. لكنني أرى أن هذا هو الملاذ الأخير لأنه سينطوي على تغييرات جوهرية. و webassembly ليس جاهزًا للشحن بعد.

Offtopic: هل أجريت بعض الاختبارات باستخدام المصفوفات مقابل المصفوفات المصنّعة مقابل المصفوفات المصنّعة مقابل asm.js. كل ما يمكنني قوله - OMG ، إنه مثل 1: 1،5: 10 للأحمال والمجموعات المتغيرة البسيطة (على FF أكثر)

jerch للتوضيح ، هل أن المصفوفات مقابل المصفوفات المصنفة هي 1: 1 إلى 1: 5؟

Woops رائعة مع الفاصلة - قصدت 10:15:100 السرعة. ولكن فقط في المصفوفات من نوع FF كانت أسرع قليلاً من المصفوفات العادية. asm أسرع بعشر مرات على الأقل من مصفوفات js على جميع المتصفحات - تم اختبارها باستخدام FF و webkit (Safari) و blink / V8 (Chrome و Opera).

jerch cool ، فإن

فكرة لحفظ الذاكرة - ربما يمكننا التخلص من width لكل شخصية. سأحاول تنفيذ إصدار wcwidth أقل تكلفة.

jerch ، نحتاج إلى الوصول إليه قليلاً ، ولا يمكننا تحميله الكسول أو أي شيء لأنه عندما يأتي إعادة التدفق ، سنحتاج إلى عرض كل حرف في المخزن المؤقت. حتى لو كان سريعًا ، فقد لا نزال نرغب في الاحتفاظ به.

قد يكون من الأفضل جعله اختياريًا ، بافتراض 1 إذا لم يكن محددًا:

type CharData = [string, number?]; // not sure if this is valid syntax

[
  // 'a'
  ['a'],
  // '文'
  ['文', 2],
  // after wide
  ['', 0],
  ...
]

Tyriar Yeah - حسنًا بما
Speedup هو 10 إلى 15 مرة على جهاز الكمبيوتر الخاص بي مقابل تكلفة 16 كيلو بايت لجدول البحث. ربما يكون الجمع بين الاثنين ممكنًا إذا لزم الأمر.

بعض العلامات الأخرى التي سندعمها في المستقبل: https://github.com/sourcelair/xterm.js/issues/580

فكرة أخرى: الجزء السفلي فقط من المحطة ( Terminal.ybase إلى Terminal.ybase + Terminal.rows ) هو الجزء الديناميكي. التمرير للخلف الذي يشكل الجزء الأكبر من البيانات ثابت تمامًا ، ربما يمكننا الاستفادة من ذلك. لم أكن أعرف هذا حتى وقت قريب ، ولكن حتى أشياء مثل حذف الأسطر (DL ، CSI Ps M) لا تعيد التمرير لأسفل بل أدخل سطرًا آخر. وبالمثل ، يؤدي التمرير لأعلى (SU، CSI Ps S) إلى حذف العنصر عند Terminal.scrollTop وإدراج عنصر عند Terminal.scrollBottom .

قد تؤدي إدارة الجزء السفلي الديناميكي من الجهاز بشكل مستقل والضغط على التمرير للخلف عند دفع الخط إلى بعض المكاسب الكبيرة. على سبيل المثال ، يمكن أن يكون الجزء السفلي أكثر تفصيلاً لتفضيل تعديل السمات ، والوصول الأسرع ، وما إلى ذلك ، بينما يمكن أن يكون التمرير الخلفي أكثر تنسيقًا للأرشيف كما هو مقترح أعلاه.

فكرة أخرى: من الأفضل تقييد CharAttributeEntry بالصفوف لأن هذه هي الطريقة التي تعمل بها معظم التطبيقات. أيضًا إذا تم تغيير حجم الجهاز الطرفي ، فسيتم إضافة حشوة "فارغة" إلى اليمين والتي لا تشترك في نفس الأنماط.

على سبيل المثال:

screen shot 2017-08-07 at 8 51 52 pm

على يمين الفروق الحمراء / الخضراء توجد خلايا "فارغة" غير مرتبة.

تضمين التغريدة
هل هناك فرصة لإعادة هذه القضية إلى جدول الأعمال؟ على الأقل بالنسبة للبرامج ذات الإخراج المكثف ، قد توفر طريقة مختلفة للاحتفاظ ببيانات المحطة الطرفية الكثير من الذاكرة والوقت. سيعطي بعض الهجين 2/3/4 دفعة إنتاجية هائلة ، إذا تمكنا من تجنب تقسيم وحفظ الأحرف الفردية لسلسلة الإدخال. كما أن حفظ السمات بمجرد تغييرها سيساعد في حفظ الذاكرة.

مثال:
باستخدام المحلل اللغوي الجديد ، يمكننا حفظ مجموعة من أحرف الإدخال دون العبث بالسمات ، نظرًا لأننا نعلم أنها لن تتغير في منتصف تلك السلسلة. يمكن حفظ سمات هذه السلسلة في بعض بنية البيانات أو السمات الأخرى جنبًا إلى جنب مع wcwidths (نعم ، ما زلنا بحاجة إلى تلك الصفات للعثور على فواصل الأسطر) وفواصل الأسطر والتوقفات. سيؤدي هذا بشكل أساسي إلى التخلي عن نموذج الخلية عندما تكون البيانات واردة.
تنشأ المشكلة إذا تدخل شيء ما وأراد الحصول على تمثيل تفصيلي لبيانات المحطة الطرفية (على سبيل المثال ، العارض أو بعض تسلسل الهروب / يريد المستخدم تحريك المؤشر). لا يزال يتعين علينا إجراء حسابات الخلية إذا حدث ذلك ، ولكن يجب أن يكون ذلك كافيًا فقط للمحتوى داخل الأعمدة والصفوف الطرفية. (لست متأكدًا بعد من المحتوى الذي تم تمريره للخارج ، والذي قد يكون أكثر قابلية للفصل ورخيصة لإعادة الرسم.)

jerch سألتقي مع mofux يومًا ما في براغ في غضون أسبوعين وسنقوم / نبدأ بعض التحسينات الداخلية لكيفية التعامل مع سمات النص التي تغطي هذا 😃

من https://github.com/xtermjs/xterm.js/pull/1460#issuecomment -390500944

تعد الخوارزمية مكلفة نوعًا ما نظرًا لأن كل حرف يحتاج إلى تقييم مرتين

jerch إذا كان لديك أي أفكار حول الوصول السريع للنص من المخزن المؤقت ، فأخبرنا بذلك. حاليًا معظمها عبارة عن حرف واحد فقط كما تعلم ، ولكن قد يكون ArrayBuffer ، سلسلة ، وما إلى ذلك. كنت أفكر في أننا يجب أن نفكر في الاستفادة بشكل أكبر من التمرير غير القابل للتغيير بطريقة أو بأخرى.

حسنًا ، لقد جربت كثيرًا مع ArrayBuffers في الماضي:

  • إنها أسوأ قليلاً من Array فيما يتعلق بوقت التشغيل للطرق النموذجية (ربما لا يزال أقل تحسينًا بواسطة بائعي المحركات)
  • new UintXXArray أسوأ بكثير من إنشاء المصفوفة الحرفية باستخدام []
  • أنها تدفع عدة مرات إذا كان بإمكانك تخصيص وإعادة استخدام بنية البيانات (حتى 10 مرات) ، هذا هو المكان الذي تأكل فيه طبيعة القائمة المرتبطة للمصفوفات المختلطة الأداء بسبب التخصيص الثقيل و gc خلف الكواليس
  • بالنسبة لبيانات السلسلة ، يستهلك التحويل الرابع والخلفي جميع الفوائد - لا توفر JS المؤسفة سلسلة أصلية لمحولات Uint16Array (يمكن تنفيذها جزئيًا باستخدام TextEncoder رغم ذلك)

تقترح النتائج التي توصلت إليها حول ArrayBuffer عدم استخدامها لبيانات السلسلة بسبب عقوبة التحويل. نظريًا ، يمكن أن تستخدم المحطة الطرفية ArrayBuffer من node-pty إلى بيانات المحطة الطرفية (سيوفر هذا العديد من التحويلات في الطريق إلى الواجهة الأمامية) ، ولست متأكدًا مما إذا كان يمكن إجراء العرض بهذه الطريقة ، أعتقد أن العرض أشياء تحتاج دائمًا إلى تحويل نهائي من uint16_t إلى string . ولكن حتى إنشاء السلسلة الأخيرة سيأكل معظم وقت التشغيل الذي تم توفيره - وعلاوة على ذلك ، سيحول الجهاز الطرفي داخليًا إلى وحش C-ish قبيح. لذلك تخليت عن هذا النهج.

TL ؛ DR ArrayBuffer هو الأفضل إذا كان بإمكانك

فكرة جديدة توصلت إليها تحاول تقليل إنشاء الأوتار قدر الإمكان ، esp. يحاول تجنب الانقسامات والانقسامات السيئة. يعتمد نوعًا ما على فكرتك الثانية أعلاه مع طريقة InputHandler.print ، wcwidth وخط توقفات في الاعتبار:

  • يحصل الآن print على سلاسل كاملة تصل إلى عدة خطوط طرفية
  • حفظ هذه السلاسل في قائمة مؤشر بسيطة دون أي تغيير (لا يوجد تخصيص سلسلة أو gc ، يمكن تجنب تخصيص القائمة إذا تم استخدامه مع بنية prealloc'd) جنبًا إلى جنب مع السمات الحالية
  • تقدم المؤشر بمقدار wcwidth(string) % cols
  • حالة خاصة \n (فاصل سطر ثابت): تقدم المؤشر بسطر واحد ، ضع علامة في قائمة المؤشرات على أنها فاصل صلب
  • تجاوز خط الحالة الخاصة مع الالتفاف: ضع علامة على الموضع في السلسلة على أنه فاصل أسطر ناعم
  • حالة خاصة \r : تحميل محتوى السطر الأخير (من موضع المؤشر الحالي إلى آخر فاصل سطر) في مخزن مؤقت للسطر ليتم الكتابة فوقه
  • تتدفق البيانات كما هو مذكور أعلاه ، على الرغم من حالة \r فلا حاجة إلى تجريد الخلية أو تقسيم السلسلة
  • لا تمثل تغييرات السمات مشكلة ، طالما لم يطلب أي شخص التمثيل الحقيقي cols x rows (يقومون فقط بتغيير علامة Attr التي يتم حفظها مع السلسلة بأكملها)

راجع للشغل ، فإن wcwidths عبارة عن مجموعة فرعية من خوارزمية grapheme ، لذلك قد يكون هذا قابلاً للتبادل في المستقبل.

الآن الجزء الخطير 1 - شخص ما يريد تحريك المؤشر داخل cols x rows :

  • تحريك cols للخلف في فواصل الأسطر - بداية محتوى المحطة الحالي
  • كل فاصل سطر يشير إلى خط طرفي حقيقي
  • تحميل العناصر في نموذج الخلية لصفحة واحدة فقط (لست متأكدًا بعد مما إذا كان يمكن حذفها أيضًا من خلال وضع سلسلة ذكي)
  • قم بالعمل السيئ: إذا تم طلب تغييرات في السمات ، فنحن نفتقد إلى الحظ وعلينا الرجوع إلى نموذج الخلية الكاملة أو نموذج تقسيم وإدراج سلسلة (قد يؤدي هذا الأخير إلى أداء سيئ)
  • تتدفق البيانات مرة أخرى ، الآن مع السلاسل المتدهورة وبيانات attrs في المخزن المؤقت لتلك الصفحة

الآن الجزء الخطير 2 - يريد العارض رسم شيء ما:

  • يعتمد kinda على العارض إذا كنا بحاجة إلى التعمق في نموذج خلية أو يمكننا فقط توفير إزاحات السلسلة مع فواصل الأسطر و attrs

الايجابيات:

  • تدفق بيانات سريع جدًا
  • الأمثل للالأكثر شيوعا InputHandler طريقة - print
  • يجعل الخطوط المتدفقة عند تغيير الحجم النهائي ممكنًا

سلبيات:

  • تقريبًا كل طريقة أخرى InputHandler ستكون خطرة بمعنى مقاطعة نموذج التدفق هذا والحاجة إلى بعض تجريد الخلايا الوسيطة
  • تكامل العارض غير واضح (بالنسبة لي على الأقل atm)
  • قد تؤدي إلى تدهور أداء اللعنات مثل التطبيقات (تحتوي عادةً على المزيد من التسلسلات "الخطرة")

حسنًا ، هذه مسودة أولية للفكرة ، بعيدة كل البعد عن كونها قابلة للاستخدام لأن الكثير من التفاصيل لم يتم تناولها بعد. إسب. يمكن أن تصبح الأجزاء "الخطرة" سيئة بسبب العديد من مشاكل الأداء (مثل تدهور المخزن المؤقت بسلوك gc أسوأ ، إلخ)

تضمين التغريدة

السلاسل لا تستحق الضغط عليها في ArrayBuffers.

أعتقد أن موناكو تخزن المخزن المؤقت في ArrayBuffer s وهي ذات أداء عالٍ جدًا. لم أتعمق كثيرًا في التنفيذ حتى الآن.

اسب. يحاول تجنب الانقسامات والانقسامات السيئة

اي واحدة؟

لقد كنت أفكر في أننا يجب أن نفكر في الاستفادة بشكل أكبر من كون التمرير الخلفي غير قابل للتغيير بطريقة ما.

كانت إحدى الأفكار هي فصل التمرير للخلف عن قسم منفذ العرض. بمجرد أن ينتقل الخط إلى التمرير للخلف ، يتم دفعه إلى بنية بيانات التمرير. يمكنك تخيل كائنين CircularList كائنين ، أحدهما تم تحسين خطوطه بحيث لا تتغير أبدًا ، والآخر للعكس.

Tyriar حول التمرير - نعم نظرًا لأنه لا يمكن الوصول إلى هذا أبدًا بواسطة المؤشر ، فقد يوفر بعض الذاكرة لإسقاط تجريد الخلية للأسطر التي تم تمريرها للخارج.

تضمين التغريدة
من المنطقي تخزين السلاسل في ArrayBuffer إذا كان بإمكاننا قصر التحويل على واحد (ربما الأخير لمخرج التصيير). هذا أفضل قليلاً من التعامل مع الخيط في كل مكان. سيكون هذا ممكنًا نظرًا لأن node-pty يمكن أن تقدم بيانات أولية أيضًا (وأيضًا يمكن لـ websocket تزويدنا ببيانات أولية).

اسب. يحاول تجنب الانقسامات والانقسامات السيئة

اي واحدة؟

النهج برمته هو تجنب _ تصغير_ الانقسامات على الإطلاق . إذا لم يطلب أي شخص انتقال المؤشر إلى البيانات المخزنة مؤقتًا ، فلن يتم تقسيم السلاسل مطلقًا ويمكن أن تنتقل مباشرة إلى العارض (إذا كان مدعومًا). لا توجد انقسامات خلية وتنضم لاحقًا على الإطلاق.

jerch حسنًا ، إذا تم توسيع منفذ العرض ، أعتقد أننا قد نسحب أيضًا التمرير للخلف عند حذف السطر؟ لست متأكدًا بنسبة 100٪ من ذلك أو حتى لو كان سلوكًا صحيحًا.

Tyriar آه الحق. لست متأكدًا من هذا الأخير أيضًا ، أعتقد أن xterm الأصلي يسمح بذلك فقط من أجل تمرير الماوس أو شريط التمرير الحقيقي. حتى SD / SU لا ينقل محتوى Scrollbuffer مرة أخرى إلى منفذ العرض الطرفي "النشط".

هل يمكن أن تدلني على مصدر محرر موناكو ، حيث يتم استخدام ArrayBuffer؟ يبدو أنني لا أستطيع أن أجده بنفسي: أحمر الخدود:

حسنًا ، أعدنا قراءة مواصفات TextEncoder / Decoder ، مع ArrayBuffers من node-pty حتى الواجهة الأمامية ، نحن عالقون أساسًا مع utf-8 ، ما لم نترجمها بالطريقة الصعبة في مرحلة ما. جعل xterm.js utf-8 مدركًا؟ Idk ، قد يتضمن ذلك العديد من حسابات نقطة التشفير الوسيطة لأحرف unicode الأعلى. الايجابيات - سيوفر الذاكرة لأحرف أسكي.

rebornix هل يمكن أن تعطينا بعض المؤشرات إلى المكان الذي تخزن فيه موناكو المخزن المؤقت؟

فيما يلي بعض الأرقام للمصفوفات المكتوبة والمحلل اللغوي الجديد (كان من الأسهل اعتماده):

  • UTF-8 (Uint8Array): print يقفز الإجراء من 190 ميجابايت / ثانية إلى 290 ميجابايت / ثانية
  • UTF-16 (Uint16Array): يقفز الإجراء print من 190 ميجابايت / ثانية إلى 320 ميجابايت / ثانية

أداء UTF-16 بشكل عام أفضل بكثير ، ولكن كان ذلك متوقعًا لأن المحلل اللغوي مُحسَّن لذلك. يعاني UTF-8 من حساب نقطة التشفير الوسيطة.

تستهلك السلسلة المراد تحويلها من المصفوفة المكتوبة حوالي 4٪ من وقت تشغيل JS من مقياس الأداء الخاص بي ls -lR /usr/lib (دائمًا أقل بكثير من 100 مللي ثانية ، ويتم ذلك عبر حلقة في InputHandler.parse ). لم أختبر التحويل العكسي (يتم ذلك ضمنيًا في أجهزة الصراف الآلي في InputHandller.print على مستوى الخلية بالخلية). وقت التشغيل الإجمالي أسوأ قليلاً من السلاسل (الوقت المحفوظ في المحلل اللغوي لا يعوض وقت التحويل). قد يتغير هذا عندما يتم كتابة الأجزاء الأخرى أيضًا على علم بالمصفوفة.

ولقطات الشاشة المقابلة (تم اختبارها بـ ls -lR /usr/lib ):

بالسلاسل:
grafik

مع Uint16Array:
grafik

اختلاف الملاحظة لـ EscapeSequenceParser.parse ، والذي يمكن أن يربح من مصفوفة مكتوبة (~ 30٪ أسرع). يقوم InputHandler.parse بالتحويل ، وبالتالي فهو أسوأ بالنسبة لإصدار المصفوفة المكتوبة. كما أن لدى GC Minor المزيد من المهام للصفيف المكتوب (منذ أن ألقيت المصفوفة بعيدًا).

تحرير: يمكن رؤية جانب آخر في لقطات الشاشة - تصبح GC ذات صلة بوقت تشغيل يصل إلى 20٪ تقريبًا ، والإطارات طويلة المدى (التي تحمل علامة حمراء) كلها مرتبطة بـ GC.

مجرد فكرة أخرى جذرية إلى حد ما:

  1. إنشاء ذاكرة افتراضية تستند إلى arraybuffer ، شيء كبير (> 5 ميجابايت)
    إذا كانت المصفوفة تحتوي على طول مضاعفات 4 محولات شفافة من int8 إلى int16 إلى int32 فمن الممكن أن تكون الأنواع. يُرجع المُخصص فهرسًا مجانيًا على Uint8Array ، ويمكن تحويل هذا المؤشر إلى مركز Uint16Array أو Uint32Array عن طريق إزاحة بسيطة في البت.
  2. اكتب السلاسل الواردة في الذاكرة كنوع uint16_t لـ UTF-16.
  3. يعمل المحلل اللغوي على مؤشرات السلسلة ويستدعي الأساليب في InputHandler مع مؤشرات لهذه الذاكرة بدلاً من شرائح السلسلة.
  4. أنشئ المخزن المؤقت لبيانات المحطة الطرفية داخل الذاكرة الافتراضية كمصفوفة عازلة للحلقة من نوع هيكلي بدلاً من كائنات JS الأصلية ، ربما مثل هذا (لا يزال يعتمد على الخلية):
struct Cell {
    uint32_t *char_start;  // start pointer of cell content (JS with pointers hurray!)
    uint8_t length;        // length of content (8 bit here is sufficient)
    uint32_t attr;         // text attributes (might grow to hold true color someday)
    uint8_t width;         // wcwidth (maybe merge with other member, always < 4)
    .....                  // some other cell based stuff
}

الايجابيات:

  • يتجاهل كائنات JS وبالتالي GC حيثما أمكن ذلك (سيبقى عدد قليل فقط من الكائنات المحلية)
  • هناك حاجة إلى نسخة بيانات أولية واحدة فقط في الذاكرة الظاهرية
  • تقريبًا لا توجد تكاليف malloc و free (يعتمد على ذكاء المخصّص / التخصيص)
  • سيوفر الكثير من الذاكرة (يتجنب حمل ذاكرة كائنات JS)

سلبيات:

  • مرحبًا بكم في برنامج Cavascript Horror Show: Scream:
  • صعب التنفيذ ، يغير كل شيء نوعا ما
  • فائدة السرعة غير واضحة حتى يتم تنفيذها بالفعل

:ابتسامة:

من الصعب تنفيذها ، يغير كل شيء نوعا ما

هذا أقرب إلى كيفية عمل موناكو ، تذكرت منشور المدونة هذا الذي يناقش استراتيجية تخزين البيانات الوصفية للشخصية https://code.visualstudio.com/blogs/2017/02/08/syntax-highlighting-optimizations

نعم هذا هو نفس الفكرة في الأساس.

آمل أن إجابتي على المكان الذي تخزن فيه موناكو المخزن المؤقت لم يفت الأوان.

أنا وأليكس نؤيد Array Buffer وفي معظم الأوقات يمنحنا أداءً جيدًا. بعض الأماكن التي نستخدم فيها ArrayBuffer:

نحن نستخدم سلاسل بسيطة للمخزن المؤقت للنص بدلاً من Array Buffer حيث يسهل معالجة سلسلة V8

  • نقوم بعمل التشفير / فك التشفير في بداية تحميل الملف ، لذلك يتم تحويل الملفات إلى سلسلة JS. يقرر V8 ما إذا كان سيتم استخدام بايت واحد أو اثنين لتخزين حرف.
  • نجري تعديلات على المخزن المؤقت للنص في كثير من الأحيان ، ومن السهل التعامل مع السلاسل.
  • نحن نستخدم الوحدة الأصلية nodejs ولدينا إمكانية الوصول إلى الأجزاء الداخلية V8 عند الضرورة.

القائمة التالية هي مجرد ملخص سريع للمفاهيم المثيرة للاهتمام التي عثرت عليها والتي قد تساعد في تقليل استخدام الذاكرة و / أو وقت التشغيل:

  • FlatJS (https://github.com/lars-t-hansen/flatjs) - لغة وصفية للمساعدة في الترميز باستخدام أكوام تستند إلى arraybuffer
  • http://2ality.com/2017/01/shared-array-buffer.html (تم الإعلان عنه كجزء من ES2017 ، قد يكون المستقبل غير مؤكد بسبب Specter ، إلى جانب هذه الفكرة الواعدة للغاية مع التزامن الحقيقي والذرات الحقيقية)
  • webassembly / asm.js (الحالة الحالية؟ يمكن استخدامها حتى الآن؟ لم تتابع تطورها لبعض الوقت ، واستخدم emscripten إلى asm.js منذ سنوات مع C lib للعبة AI مع نتائج مبهرة رغم ذلك)
  • https://github.com/AssemblyScript/assemblyscript

للحصول على ملخص هنا ، إليك طريقة اختراق سريعة لكيفية "دمج" سمات النص.

الكود مدفوع بشكل أساسي بفكرة حفظ الذاكرة لبيانات المخزن المؤقت (سيعاني وقت التشغيل ، ولم يتم اختباره بعد). إسب. ستجعل سمات النص مع RGB للمقدمة والخلفية (بمجرد دعمها) xterm.js يأكل الكثير من الذاكرة بواسطة الخلية الحالية حسب تخطيط الخلية. يحاول الكود التحايل على هذا من خلال استخدام أطلس ref-count القابل لتغيير الحجم للسمات. يعد هذا خيارًا من خيارات imho نظرًا لأن المحطة الطرفية الواحدة لا تكاد تحتوي على أكثر من مليون خلية ، مما يؤدي إلى زيادة حجم الأطلس إلى 1M * entry_size إذا اختلفت جميع الخلايا.

تحتاج الخلية نفسها فقط إلى الاحتفاظ بفهرس أطلس السمة. عند إجراء تغييرات على الخلية ، يجب أن يكون الفهرس القديم غير مراجع والمرجع الجديد. سيحل فهرس الأطلس محل سمة سمة الكائن الطرفي وسيتم تغييره بنفسه في SGR.

يعالج الأطلس حاليًا سمات النص فقط ، ولكن يمكن توسيعه ليشمل جميع سمات الخلية إذا لزم الأمر. في حين أن المخزن المؤقت الحالي يحتوي على رقمين 32 بت لبيانات السمة (4 مع RGB في تصميم المخزن المؤقت الحالي) ، فإن الأطلس سيقللها إلى رقم 32 بت واحد فقط. يمكن تعبئة مدخلات الأطلس بشكل أكبر أيضًا.

interface TextAttributes {
    flags: number;
    foreground: number;
    background: number;
}

const enum AtlasEntry {
    FLAGS = 1,
    FOREGROUND = 2,
    BACKGROUND = 3
}

class TextAttributeAtlas {
    /** data storage */
    private data: Uint32Array;
    /** flag lookup tree, not happy with that yet */
    private flagTree: any = {};
    /** holds freed slots */
    private freedSlots: number[] = [];
    /** tracks biggest idx to shortcut new slot assignment */
    private biggestIdx: number = 0;
    constructor(size: number) {
        this.data = new Uint32Array(size * 4);
    }
    private setData(idx: number, attributes: TextAttributes): void {
        this.data[idx] = 0;
        this.data[idx + AtlasEntry.FLAGS] = attributes.flags;
        this.data[idx + AtlasEntry.FOREGROUND] = attributes.foreground;
        this.data[idx + AtlasEntry.BACKGROUND] = attributes.background;
        if (!this.flagTree[attributes.flags])
            this.flagTree[attributes.flags] = [];
        if (this.flagTree[attributes.flags].indexOf(idx) === -1)
            this.flagTree[attributes.flags].push(idx);
    }

    /**
     * convenient method to inspect attributes at slot `idx`.
     * For better performance atlas idx and AtlasEntry
     * should be used directly to avoid number conversions.
     * <strong i="10">@param</strong> {number} idx
     * <strong i="11">@return</strong> {TextAttributes}
     */
    getAttributes(idx: number): TextAttributes {
        return {
            flags: this.data[idx + AtlasEntry.FLAGS],
            foreground: this.data[idx + AtlasEntry.FOREGROUND],
            background: this.data[idx + AtlasEntry.BACKGROUND]
        };
    }

    /**
     * Returns a slot index in the atlas for the given text attributes.
     * To be called upon attributes changes, e.g. by SGR.
     * NOTE: The ref counter is set to 0 for a new slot index, thus
     * values will get overwritten if not referenced in between.
     * <strong i="12">@param</strong> {TextAttributes} attributes
     * <strong i="13">@return</strong> {number}
     */
    getSlot(attributes: TextAttributes): number {
        // find matching attributes slot
        const sameFlag = this.flagTree[attributes.flags];
        if (sameFlag) {
            for (let i = 0; i < sameFlag.length; ++i) {
                let idx = sameFlag[i];
                if (this.data[idx + AtlasEntry.FOREGROUND] === attributes.foreground
                    && this.data[idx + AtlasEntry.BACKGROUND] === attributes.background) {
                    return idx;
                }
            }
        }
        // try to insert into a previously freed slot
        const freed = this.freedSlots.pop();
        if (freed) {
            this.setData(freed, attributes);
            return freed;
        }
        // else assign new slot
        for (let i = this.biggestIdx; i < this.data.length; i += 4) {
            if (!this.data[i]) {
                this.setData(i, attributes);
                if (i > this.biggestIdx)
                    this.biggestIdx = i;
                return i;
            }
        }
        // could not find a valid slot --> resize storage
        const data = new Uint32Array(this.data.length * 2);
        for (let i = 0; i < this.data.length; ++i)
            data[i] = this.data[i];
        const idx = this.data.length;
        this.data = data;
        this.setData(idx, attributes);
        return idx;
    }

    /**
     * Increment ref counter.
     * To be called for every terminal cell, that holds `idx` as text attributes.
     * <strong i="14">@param</strong> {number} idx
     */
    ref(idx: number): void {
        this.data[idx]++;
    }

    /**
     * Decrement ref counter. Once dropped to 0 the slot will be reused.
     * To be called for every cell that gets removed or reused with another value.
     * <strong i="15">@param</strong> {number} idx
     */
    unref(idx: number): void {
        this.data[idx]--;
        if (!this.data[idx]) {
            let treePart = this.flagTree[this.data[idx + AtlasEntry.FLAGS]];
            treePart.splice(treePart.indexOf(this.data[idx]), 1);
        }
    }
}

let atlas = new TextAttributeAtlas(2);
let a1 = atlas.getSlot({flags: 12, foreground: 13, background: 14});
atlas.ref(a1);
// atlas.unref(a1);
let a2 = atlas.getSlot({flags: 12, foreground: 13, background: 15});
atlas.ref(a2);
let a3 = atlas.getSlot({flags: 13, foreground: 13, background: 16});
atlas.ref(a3);
let a4 = atlas.getSlot({flags: 13, foreground: 13, background: 16});
console.log(atlas);
console.log(a1, a2, a3, a4);
console.log('a1', atlas.getAttributes(a1));
console.log('a2', atlas.getAttributes(a2));
console.log('a3', atlas.getAttributes(a3));
console.log('a4', atlas.getAttributes(a4));

يحرر:
تبلغ عقوبة وقت التشغيل صفرًا تقريبًا ، بالنسبة إلى مقياس الأداء الخاص بي بـ ls -lR /usr/lib فإنه يضيف أقل من 1 مللي ثانية إلى إجمالي وقت التشغيل البالغ حوالي 2.3 ثانية. ملاحظة جانبية مثيرة للاهتمام - يعيّن الأمر أقل من 64 فتحة سمات نصية مختلفة لإخراج 5 ميغابايت من البيانات وسيوفر أكثر من 20 ميغابايت بمجرد تنفيذه بالكامل.

قم بعمل بعض نماذج العلاقات العامة الأولية لاختبار بعض التغييرات على المخزن المؤقت (انظر https://github.com/xtermjs/xterm.js/pull/1528#issue-196949371 لمعرفة الفكرة العامة وراء التغييرات):

  • PR # 1528: أطلس السمة
  • PR # 1529: إزالة wcwidth و charCode من المخزن المؤقت
  • PR # 1530: استبدل السلسلة في المخزن المؤقت بقيمة نقاط التشفير / قيمة فهرس تخزين الخلية

jerch قد يكون من الجيد الابتعاد عن كلمة أطلس لهذا الغرض بحيث يعني "أطلس" دائمًا "نسيج أطلس". شيء مثل المخزن أو ذاكرة التخزين المؤقت من المحتمل أن يكون أفضل؟

حسنًا ، "ذاكرة التخزين المؤقت" جيدة.

أعتقد أنني انتهيت من اختبار العلاقات العامة. يرجى أيضًا إلقاء نظرة على تعليقات العلاقات العامة للحصول على خلفية الملخص التقريبي التالي.

عرض:

  1. أنشئ AttributeCache للاحتفاظ بكل ما يلزم لتصميم خلية طرفية واحدة. راجع # 1528 للحصول على إصدار حساب مرجع مبكر يمكنه أيضًا الاحتفاظ بمواصفات ألوان حقيقية. يمكن أيضًا مشاركة ذاكرة التخزين المؤقت بين مثيلات طرفية مختلفة إذا لزم الأمر لحفظ ذاكرة إضافية في تطبيقات طرفية متعددة.
  2. أنشئ StringStorage للاحتفاظ بسلاسل بيانات محتوى المحطة الطرفية القصيرة. يتجنب الإصدار # 1530 تخزين سلاسل أحرف مفردة من خلال "التحميل الزائد" لمعنى المؤشر. يجب نقل wcwidth هنا.
  3. تقليص CharData من [number, string, number, number] إلى [number, number] ، حيث تكون الأرقام مؤشرات (أرقام فهرسة) إلى:

    • إدخال AttributeCache

    • StringStorage

من غير المحتمل أن تتغير السمات كثيرًا ، لذا فإن رقم 32 بت واحد سيوفر الكثير من الذاكرة بمرور الوقت. المؤشر StringStorage هو نقطة ترميز Unicode حقيقية لأحرف واحدة ، وبالتالي يمكن استخدامه كإدخال code CharData . يمكن الوصول إلى السلسلة الفعلية بواسطة StringStorage.getString(idx) . يمكن الوصول إلى الحقل الرابع wcwidth من CharData بواسطة StringStorage.wcwidth(idx) (لم يتم تنفيذه بعد). لا توجد أي عقوبة تقريبًا لوقت التشغيل للتخلص من code و wcwidth CharData (تم اختباره في # 1529).

  1. انقل المنكمش CharData إلى تطبيق عازل كثيف يعتمد على Int32Array . تم اختباره أيضًا في # 1530 مع فئة كعب (بعيدًا عن أن تعمل بكامل طاقتها) ، ومن المرجح أن تكون الفوائد النهائية:

    • مساحة ذاكرة أصغر بنسبة 80٪ من المخزن المؤقت للطرف (من 5.5 ميجابايت إلى 0.75 ميجابايت)

    • أسرع قليلاً (غير قابل للاختبار بعد ، أتوقع أن أكسب 20٪ - 30٪ سرعة)

    • تحرير: أسرع بكثير - انخفض وقت تشغيل البرنامج النصي لـ ls -lR /usr/lib إلى 1.3 ثانية (الرئيسي عند 2.1 ثانية) بينما لا يزال المخزن المؤقت القديم نشطًا للتعامل مع المؤشر ، بمجرد إزالته أتوقع أن ينخفض ​​وقت التشغيل إلى أقل من 1 ثانية

الجانب السلبي - الخطوة 4 هي عمل كثير جدًا لأنها ستحتاج إلى بعض إعادة العمل على واجهة المخزن المؤقت. لكن مهلا - لتوفير 80٪ من ذاكرة الوصول العشوائي والاستمرار في الحصول على أداء وقت التشغيل ، لا يعد الأمر كبيرًا ، أليس كذلك؟ :ابتسامة:

هناك مشكلة أخرى تعثرت فيها - تمثيل الخلية الفارغة الحالي. يمكن أن تحتوي الخلية Imho على 3 حالات:

  • فارغ : حالة الخلية الأولية ، لم تتم كتابة أي شيء إليها بعد أو تم حذف المحتوى. يبلغ عرضه 1 ولكن لا يوجد محتوى. يُستخدم حاليًا في blankLine و eraseChar ، ولكن مع وجود مساحة كمحتوى.
  • فارغ : خلية بعد حرف عرض كامل للإشارة إلى أنه ليس لها عرض للتمثيل المرئي.
  • عادي : تحتوي الخلية على بعض المحتوى ولها عرض مرئي (1 أو 2 ، ربما أكبر بمجرد أن ندعم أشكال حرف يدوية حقيقية / ثنائية الاتجاه ، لست متأكدًا من ذلك حتى الآن)

المشكلة التي أراها هنا هي أن الخلية الفارغة لا يمكن تمييزها عن الخلية العادية مع إدخال مسافة ، وكلاهما يبدوان متماثلين في مستوى المخزن المؤقت (نفس المحتوى ونفس العرض). لم أكتب أيًا من كود العارض / الإخراج ولكني أتوقع أن يؤدي هذا إلى مواقف محرجة في مقدمة الإخراج. إسب. قد يصبح التعامل مع الطرف الأيمن من الخط مرهقًا.
طرف به 15 عمودًا ، أولًا بعض خرج السلسلة ، تم لفه حوله:

1: 'H', 'e', 'l', 'l', 'o', ' ', 't', 'e', 'r', 'm', 'i', 'n', 'a', 'l', ' '
2: 'w', 'o', 'r', 'l', 'd', '!', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '

مقابل قائمة مجلد بـ ls :

1: 'R', 'e', 'a', 'd', 'm', 'e', '.', 'm', 'd', ' ', ' ', ' ', ' ', ' ', ' '
2: 'f', 'i', 'l', 'e', 'A', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '

يحتوي المثال الأول مساحة الحقيقي بعد كلمة 'محطة'، المثال الثاني لم تصل إلى الخلايا بعد "Readme.md. الطريقة التي يتم تمثيلها بها على مستوى المخزن المؤقت منطقية تمامًا بالنسبة للحالة القياسية لطباعة الأشياء كإخراج طرفي على الشاشة (يجب أخذ الغرفة على أي حال) ، ولكن للأدوات التي تحاول التعامل مع سلاسل المحتوى مثل تحديد الماوس أو مدير إعادة التدفق ، لم يعد واضحًا ، من أين تأتي المساحات.

يؤدي هذا بشكل أو بآخر إلى السؤال التالي - كيفية تحديد طول المحتوى الفعلي في السطر (عدد الخلايا التي تحتوي على شيء من الجانب الأيسر)؟ طريقة بسيطة من شأنها أن تحسب الخلايا الفارغة من الجانب الأيمن ، ومرة ​​أخرى ، فإن المعنى المزدوج من الأعلى يجعل من الصعب تحديد ذلك.

عرض:
Imho هذا سهل الإصلاح باستخدام بعض العناصر النائبة الأخرى للخلايا الفارغة ، على سبيل المثال حرف تحكم أو سلسلة فارغة واستبدال تلك الموجودة في عملية التصيير إذا لزم الأمر. ربما يمكن أن يستفيد عارض الشاشة أيضًا من هذا ، لأنه قد لا يضطر إلى التعامل مع هذه الخلايا على الإطلاق (يعتمد على طريقة إنشاء الإخراج).

راجع للشغل ، بالنسبة للسلسلة الملفوفة أعلاه ، يؤدي هذا أيضًا إلى مشكلة isWrapped ، وهو أمر ضروري لتغيير حجم إعادة التدفق أو معالجة تحديد النسخ واللصق بشكل صحيح. Imho لا يمكننا إزالة ذلك ، لكننا بحاجة إلى دمجه بشكل أفضل مما هو عليه في أجهزة الصراف الآلي.

jerch عمل مبهر! : مبتسم:

1 قم ببناء AttributeCache لاحتواء كل ما هو مطلوب لتصميم خلية طرفية واحدة. راجع # 1528 للحصول على إصدار حساب مرجع مبكر يمكنه أيضًا الاحتفاظ بمواصفات ألوان حقيقية. يمكن أيضًا مشاركة ذاكرة التخزين المؤقت بين مثيلات طرفية مختلفة إذا لزم الأمر لحفظ ذاكرة إضافية في تطبيقات طرفية متعددة.

أدلى ببعض التعليقات على # 1528.

2 قم ببناء StringStorage لعقد سلاسل بيانات محتوى طرفي قصيرة. يتجنب الإصدار # 1530 تخزين سلاسل أحرف مفردة من خلال "التحميل الزائد" لمعنى المؤشر. يجب نقل wcwidth هنا.

أدلى ببعض التعليقات على # 1530.

4 انقل CharData المنكمش إلى تطبيق Int32Array كثيف قائم على المخزن المؤقت. تم اختباره أيضًا في # 1530 مع فئة كعب (بعيدًا عن أن تعمل بكامل طاقتها) ، ومن المرجح أن تكون الفوائد النهائية:

لم يتم بيع هذه الفكرة تمامًا بعد ، أعتقد أنها ستضربنا بشدة عندما ننفذ إعادة التدفق. يبدو أنه يمكن تنفيذ كل خطوة من هذه الخطوات إلى حد كبير حتى نتمكن من رؤية كيفية سير الأمور ومعرفة ما إذا كان من المنطقي القيام بذلك بمجرد الانتهاء من 3.

هناك مشكلة أخرى تعثرت فيها - تمثيل الخلية الفارغة الحالي. يمكن أن تحتوي الخلية Imho على 3 حالات

في ما يلي مثال على الخطأ الذي ظهر من https://github.com/xtermjs/xterm.js/issues/1286 ،: +1: للتمييز بين خلايا المسافات والخلايا "الفارغة"

راجع للشغل ، بالنسبة للسلسلة الملفوفة أعلاه ، يؤدي هذا أيضًا إلى مشكلة isWrapped ، وهو أمر ضروري لتغيير حجم إعادة التدفق أو معالجة تحديد النسخ واللصق بشكل صحيح. Imho لا يمكننا إزالة ذلك ، لكننا بحاجة إلى دمجه بشكل أفضل مما هو عليه في أجهزة الصراف الآلي.

أرى isWrapped يختفي عندما نتعامل مع https://github.com/xtermjs/xterm.js/issues/622 لأن CircularList ستحتوي فقط على صفوف غير مغلفة.

لم يتم بيع هذه الفكرة تمامًا بعد ، أعتقد أنها ستضربنا بشدة عندما ننفذ إعادة التدفق. يبدو أنه يمكن تنفيذ كل خطوة من هذه الخطوات إلى حد كبير حتى نتمكن من رؤية كيفية سير الأمور ومعرفة ما إذا كان من المنطقي القيام بذلك بمجرد الانتهاء من 3.

نعم أنا معك (ما زلت ممتعًا للعب بهذا النهج المختلف تمامًا). يمكن اختيار 1 و 2 ، ويمكن تطبيق 3 اعتمادًا على 1 أو 2. 4 اختياري ، يمكننا فقط التمسك بتخطيط المخزن المؤقت الحالي. يتم توفير الذاكرة على النحو التالي:

  1. 1 + 2 + 3 في CircularList : يوفر 50٪ (~ 2.8 ميجابايت من ~ 5.5 ميجابايت)
  2. 1 + 2 + 3 + 4 في منتصف الطريق - فقط ضع بيانات الخط في صفيف مكتوب ولكن التزم بالوصول إلى فهرس الصف: يوفر 82٪ (~ 0.9 ميجابايت)
  3. 1 + 2 + 3 + 4 مصفوفة كثيفة بالكامل مع مؤشر حسابي: يوفر 87٪ (~ 0.7 ميجابايت)

1. سهل التنفيذ للغاية ، سيظل سلوك الذاكرة مع التمرير الأكبر حجمًا يظهر التحجيم السيئ كما هو موضح هنا https://github.com/xtermjs/xterm.js/pull/1530#issuecomment -403542479 ولكن على مستوى أقل سمية
2. يصعب تنفيذه قليلاً (يلزم وجود المزيد من الحركات غير المباشرة على مستوى الخط) ، ولكنه سيجعل من الممكن الحفاظ على واجهة برمجة التطبيقات الأعلى التي تبلغ Buffer سليمة. Imho الخيار المناسب - حفظ ذاكرة كبيرة ولا يزال من السهل دمجها.
3. حفظ الذاكرة بنسبة 5٪ أكثر من الخيار 2 ، الذي يصعب تنفيذه ، سيؤدي إلى تغيير لمس كل واجهة برمجة التطبيقات وبالتالي قاعدة الشفرة بالكامل حرفيًا. Imho أكثر من الاهتمام الأكاديمي أو لأيام ممطرة مملة ليتم تنفيذها لول.

Tyriar لقد

  • تكون معالجة البيانات داخل جزء wasm أسرع قليلاً (5-10٪).
  • المكالمات من JS إلى wasm تخلق بعض النفقات العامة وتأكل جميع الفوائد المذكورة أعلاه. في الواقع ، كان أبطأ بنسبة 20٪.
  • سيكون "الثنائي" أصغر من نظيره في JS (لم يتم قياسه حقًا لأنني لم أقم بتطبيق كل الأشياء).
  • للحصول على JS <--> انتقال wasm بسهولة ، هناك حاجة إلى بعض bloatcode للتعامل مع أنواع JS (فقط فعلت ترجمة السلسلة).
  • لا يمكننا تجنب ترجمة JS إلى wasm نظرًا لأن متصفح DOM والأحداث لا يمكن الوصول إليها هناك. يمكن استخدامه فقط للأجزاء الأساسية ، والتي لم تعد ذات أهمية في الأداء بعد الآن (بجانب استهلاك الذاكرة).

ما لم نرغب في إعادة كتابة libs الأساسية بالكامل في الصدأ (أو أي لغة أخرى قادرة على wasm) لا يمكننا الحصول على أي شيء من الانتقال إلى wasm lang imho. ميزة إضافية في الوقت الحاضر هي حقيقة أن معظمها يدعم معالجة الذاكرة الصريحة (يمكن أن تساعدنا في حل مشكلة المخزن المؤقت) ، والجوانب السلبية هي إدخال لغة مختلفة تمامًا في مشروع يركز بشكل أساسي على TS / JS (حاجز كبير لإضافات الكود) وتكاليف الترجمة بين wasm و JS land.

TL ؛ DR
xterm.js هو على نطاق واسع إلى عناصر JS العامة مثل DOM والأحداث للحصول على أي شيء من webassembly حتى لإعادة كتابة الأجزاء الأساسية.

jerch تحقيق لطيف: مبتسم:

المكالمات من JS إلى wasm تخلق بعض النفقات العامة وتأكل جميع الفوائد المذكورة أعلاه. في الواقع ، كان أبطأ بنسبة 20٪.

كانت هذه هي المشكلة الرئيسية في تحول موناكو إلى موطنها أيضًا وهو ما أبلغني بشكل أساسي بموقفي (على الرغم من أن ذلك كان مع وحدة العقدة الأصلية ، وليس wasm). أعتقد أن العمل مع ArrayBuffer s حيثما كان ذلك ممكنًا يجب أن يمنحنا أفضل توازن بين الكمال والبساطة (سهولة ضمنية ، حاجز للدخول).

Tyriar سأحاول التوصل إلى AttributeStorage للاحتفاظ ببيانات RGB. لست متأكدًا بعد من BST ، بالنسبة لحالة الاستخدام النموذجية مع عدد قليل فقط من إعدادات اللون في جلسة المحطة الطرفية ، سيكون هذا أسوأ في وقت التشغيل ، ربما يجب أن يكون هذا بمثابة انخفاض في وقت التشغيل بمجرد أن تتجاوز الألوان حدًا معينًا. كما سيزيد استهلاك الذاكرة كثيرًا مرة أخرى ، على الرغم من أنه سيظل يحفظ الذاكرة نظرًا لأنه يتم تخزين السمات مرة واحدة فقط وليس مع كل خلية واحدة (أسوأ سيناريو مع كل خلية تحمل سمات مختلفة ستعاني على الرغم من ذلك).
هل تعلم لماذا تستند قيمة الألوان الحالية fg و bg 256 على 9 بت بدلاً من 8 بت؟ ما هو البت الإضافي المستخدم؟ هنا: https://github.com/xtermjs/xterm.js/blob/6691f809069a549b4808cd2e055398d2da15db37/src/InputHandler.ts#L1596
هل يمكن أن تعطيني تخطيط البت الحالي وهو attr ؟ أعتقد أن نهجًا مشابهًا مثل "المعنى المزدوج" لمؤشر StringStorage يمكن أن يوفر مزيدًا من الذاكرة ، لكن هذا يتطلب MSB attr ليتم تخصيصها لتمييز المؤشر وعدم استخدامها لأي غرض آخر. قد يحد هذا من إمكانية دعم المزيد من إشارات السمات لاحقًا (لأن FLAGS يستخدم بالفعل 7 بتات) ، هل ما زلنا نفتقد بعض العلامات الأساسية التي من المحتمل أن تظهر؟

يمكن تعبئة attr 32 بت

# 256 indexed colors
32:       0 (no RGB color)
31..25:   flags (7 bits)
24..17:   fg (8 bits, see question above)
16..9:    bg
8..1:     unused

# RGB colors
32:       1 (RGB color)
31..25:   flags (7 bits)
24..1:    pointer to RGB data (address space is 2^24, which should be sufficient)

بهذه الطريقة ، يحتاج التخزين فقط إلى الاحتفاظ ببيانات RGB في رقمين 32 بت بينما يمكن أن تبقى العلامات في الرقم attr .

jerch بالمناسبة لقد أرسلت لك بريدًا إلكترونيًا ، ربما تم التهامه بواسطة مرشح البريد العشوائي مرة أخرى

هل تعلم لماذا تستند قيمة fg و bg 256 الحالية إلى 9 بت بدلاً من 8 بت؟ ما هو البت الإضافي المستخدم؟

أعتقد أنه يُستخدم للون fg / bg الافتراضي (والذي قد يكون غامقًا أو فاتحًا) ، لذا فهو في الواقع 257 لونًا.

https://github.com/xtermjs/xterm.js/pull/756/files

هل يمكن أن تعطيني تخطيط البت الحالي من attr؟

أعتقد أنه هذا:

19+:     flags (see `FLAGS` enum)
18..18:  default fg flag
17..10:  256 fg
9..9:    default bg flag
8..1:    256 bg

يمكنك أن ترى ما هبطت عليه لـ truecolor في العلاقات العامة القديمة https://github.com/xtermjs/xterm.js/pull/756/files :

/**
 * Character data, the array's format is:
 * - string: The character.
 * - number: The width of the character.
 * - number: Flags that decorate the character.
 *
 *        truecolor fg
 *        |   inverse
 *        |   |   underline
 *        |   |   |
 *   0b 0 0 0 0 0 0 0
 *      |   |   |   |
 *      |   |   |   bold
 *      |   |   blink
 *      |   invisible
 *      truecolor bg
 *
 * - number: Foreground color. If default bit flag is set, color is the default
 *           (inherited from the DOM parent). If truecolor fg flag is true, this
 *           is a 24-bit color of the form 0xxRRGGBB, if not it's an xterm color
 *           code ranging from 0-255.
 *
 *        red
 *        |       blue
 *   0x 0 R R G G B B
 *      |     |
 *      |     green
 *      default color bit
 *
 * - number: Background color. The same as foreground color.
 */
export type CharData = [string, number, number, number, number];

في هذا كان لدي علمان ؛ واحد للون الافتراضي (سواء تجاهل كل بتات اللون) والآخر لـ truecolor (سواء كنت تريد 256 أو 16 مل لون).

قد يحد هذا من إمكانية دعم المزيد من إشارات السمات لاحقًا (لأن FLAGS يستخدم بالفعل 7 بتات) ، فهل ما زلنا نفتقد بعض العلامات الأساسية التي من المحتمل أن تظهر؟

نعم ، نريد بعض المساحة للأعلام الإضافية ، على سبيل المثال https://github.com/xtermjs/xterm.js/issues/580 ، https://github.com/xtermjs/xterm.js/issues/1145 ، أود قل على الأقل اترك أكثر من 3 بتات حيثما أمكن ذلك.

بدلاً من بيانات المؤشر داخل Attr نفسه ، يمكن أن يكون هناك خريطة أخرى تحتوي على مراجع لبيانات rgb؟ mapAttrIdxToRgb: { [idx: number]: RgbData

Tyriar آسف ، لم يكن متصلاً التهامه بواسطة مرشح البريد العشوائي. هل يمكنك إعادة إرسالها من فضلك؟ :احمر خدود:

لعبت قليلاً مع هياكل بيانات بحث أكثر ذكاءً لتخزين attrs. أكثر الأشياء الواعدة فيما يتعلق بالمساحة ووقت تشغيل البحث / الإدراج هي الأشجار والقائد كبديل أرخص. من الناحية النظرية لول. من الناحية العملية ، لا يمكن لأي منهما أن يتفوق على بحثي البسيط عن المصفوفة والذي يبدو غريبًا جدًا بالنسبة لي (خطأ في الكود في مكان ما؟)
لقد قمت بتحميل ملف اختبار هنا https://gist.github.com/jerch/ff65f3fb4414ff8ac84a947b3a1eec58 مع مصفوفة مقابل شجرة حمراء-سوداء ذات ميل يسار ، والتي تختبر ما يصل إلى 10 ملايين إدخال (وهي مساحة العنونة الكاملة تقريبًا). لا تزال المصفوفة متقدمة جدًا مقارنة بـ LLRB ، وأظن أن التعادل سيكون حول 10M. تم اختباره على جهاز الكمبيوتر المحمول القديم الخاص بي 7ys ، ربما يمكن لشخص ما اختباره أيضًا بل وأفضل - قم بتوجيهي إلى بعض الأخطاء في التضمين / الاختبارات.

فيما يلي بعض النتائج (بالأرقام الجارية):

prefilled             time for inserting 1000 * 1000 (summed up, ms)
items                 array        LLRB
100-10000             3.5 - 5      ~13
100000                ~12          ~15
1000000               8            ~18
10000000              20-25        21-28

ما يفاجئني حقًا هو حقيقة أن بحث المصفوفة الخطية لا يُظهر أي نمو في المناطق السفلية على الإطلاق ، فهو يصل إلى 10 آلاف مدخلات مستقرة عند 4 مللي ثانية تقريبًا (قد يكون متعلقًا بذاكرة التخزين المؤقت). يُظهر اختبار 10M وقت تشغيل أسوأ من المتوقع ، ربما بسبب ترحيل الذاكرة على الإطلاق. ربما يكون JS بعيدًا عن الجهاز مع JIT وكل الخيارات / عمليات الإلغاء تحدث ، ما زلت أعتقد أنهم غير قادرين على التخلص من خطوة التعقيد (على الرغم من أن LLRB يبدو ثقيلًا على _n_ وبالتالي تحريك نقطة التعادل لـ O ( ن) مقابل O (تسجيل الدخول) لأعلى)

بالمناسبة مع البيانات العشوائية ، يكون الاختلاف أسوأ.

أعتقد أنه يُستخدم للون fg / bg الافتراضي (والذي قد يكون غامقًا أو فاتحًا) ، لذا فهو في الواقع 257 لونًا.

إذن هذا يميز SGR 39 أو SGR 49 عن أحد ألوان اللوحة الثمانية؟

بدلاً من بيانات المؤشر داخل Attr نفسه ، يمكن أن يكون هناك خريطة أخرى تحتوي على مراجع لبيانات rgb؟ mapAttrIdxToRgb: {[idx: number]: RgbData

قد يؤدي هذا إلى مراوغة أخرى مع استخدام ذاكرة إضافية. من خلال الاختبارات المذكورة أعلاه ، اختبرت أيضًا الفرق بين الاحتفاظ دائمًا بالأعلام في attrs مقابل حفظها مع بيانات RGB في التخزين. نظرًا لأن الاختلاف يبلغ 0.5 مللي ثانية لكل مليون إدخال ، فلن أذهب إلى إعداد attrs المعقد ، وبدلاً من ذلك انسخ العلامات إلى وحدة التخزين بمجرد تعيين RGB. ما زلت سأختار التمييز 32 بت بين attrs المباشر مقابل المؤشر لأن هذا سيتجنب التخزين على الإطلاق لخلايا غير RGB.

أعتقد أيضًا أن ألوان الألواح الثمانية الافتراضية لـ fg / bg ليست ممثلة بشكل كافٍ في المخزن المؤقت حاليًا. من الناحية النظرية ، يجب أن يدعم الجهاز أوضاع الألوان التالية:

  1. SGR 39 + SGR 49 اللون الافتراضي لـ fg / bg (قابل للتخصيص)
  2. SGR 30-37 + SGR 40-47 8 لوحة ألوان منخفضة لـ fg / bg (قابلة للتخصيص)
  3. لوحة ألوان عالية SGR 90-97 + SGR 100-107 8 لـ fg / bg (قابلة للتخصيص)
  4. لوحة ألوان مفهرسة SGR 38;5;n + SGR 48;5;n 256 لـ fg / bg (قابلة للتخصيص)
  5. SGR 38;2;r;g;b + SGR 48;2;r;g;b RGB لـ fg / bg (غير قابل للتخصيص)

الخيار 2.) و 3.) يمكن دمجهما معًا في بايت واحد (معاملتهما على أنهما 16 لونًا لوح ألوان fg / bg) ، 4.) يأخذ 2 بايت و 5.) أخيرًا سيستغرق 6 بايت. ما زلنا بحاجة إلى بعض البتات للإشارة إلى وضع اللون.
لنعكس هذا على مستوى المخزن المؤقت ، سنحتاج إلى ما يلي:

bits        for
2           fg color mode (0: default, 1: 16 palette, 2: 256, 3: RGB)
2           bg color mode (0: default, 1: 16 palette, 2: 256, 3: RGB)
8           fg color for 16 palette and 256
8           bg color for 16 palette and 256
10          flags (currently 7, 3 more reserved for future usage)
----
30

لذلك نحن بحاجة إلى 30 بت من رقم 32 بت ، مع ترك 2 بت مجانًا لأغراض أخرى. يمكن أن تحتوي البتة 32 على المؤشر مقابل علامة Attr المباشرة التي تحذف التخزين للخلايا غير RGB.

أقترح أيضًا اختتام وصول attr إلى فئة ملائمة لعدم كشف تفاصيل التنفيذ للخارج (انظر ملف الاختبار أعلاه ، هناك إصدار مبكر من فئة TextAttributes لتحقيق ذلك).

عذرًا ، لم تكن متصلاً بالإنترنت لبضعة أيام وأخشى أن يتم التهام البريد الإلكتروني بواسطة عامل تصفية البريد العشوائي. هل يمكنك إعادة إرسالها من فضلك؟

مستاء

أوه بالمناسبة ، هذه الأرقام أعلاه للبحث عن المصفوفة مقابل llrb هي حماقة - أعتقد أنه قد أفسدها المحسن الذي قام ببعض الأشياء الغريبة في حلقة for. مع إعداد اختبار مختلف قليلاً ، فإنه يُظهر بوضوح أن O (n) مقابل O (log n) ينمو في وقت مبكر (مع 1000 عنصر مملوء مسبقًا بشكل أسرع مع الشجرة).

الوضع الحالي:

بعد، بعدما:

أحد التحسينات البسيطة إلى حد ما هو تسوية مصفوفة المصفوفات في مصفوفة واحدة. أي بدلاً من BufferLine من الأعمدة _N_ التي تحتوي على مصفوفة _data من خلايا _N_ CharData ، حيث كل CharData عبارة عن مصفوفة من 4 ، فقط يكون لديك واحد مصفوفة من عناصر _4 * N_. هذا يزيل الحمل الزائد في المصفوفات _N_. كما أنه يحسن موقع ذاكرة التخزين المؤقت ، لذا يجب أن يكون أسرع. العيب هو رمز أكثر تعقيدًا وقبحًا ، لكن يبدو أنه يستحق ذلك.

كمتابعة لتعليقي السابق ، يبدو أنه من المفيد التفكير في استخدام عدد متغير من العناصر في المصفوفة _data لكل خلية. بعبارة أخرى ، تمثيل ذو حالة. قد تكون التغييرات العشوائية في الموضع أكثر تكلفة ، لكن المسح الخطي من بداية السطر يمكن أن يكون سريعًا جدًا ، خاصة وأن المصفوفة البسيطة محسّنة لموقع التخزين المؤقت. سيكون الإخراج المتسلسل النموذجي سريعًا ، كما هو الحال في العرض.

بالإضافة إلى تقليل المساحة ، تتمثل ميزة عدد متغير من العناصر لكل خلية في زيادة المرونة: السمات الإضافية (مثل لون 24 بت) ، أو التعليقات التوضيحية لخلايا أو نطاقات معينة ، أو الحروف الرسومية أو
عناصر DOM المتداخلة.

@ PerBothner Thx لأفكارك! نعم ، لقد اختبرت بالفعل تخطيط الصفيف الكثيف الفردي باستخدام مؤشر حسابي ، فإنه يُظهر أفضل استخدام للذاكرة. تنشأ المشاكل عندما يتعلق الأمر بتغيير الحجم ، فهذا يعني بشكل أساسي إعادة بناء جزء الذاكرة بالكامل (النسخ) أو النسخ السريع إلى جزء أكبر وإعادة تنظيم الأجزاء. هذا هو تماما إكسب. و imho غير مبرر بحفظ الذاكرة (تم اختباره في بعض الملاعب العامة المدرجة أعلاه ، كان التوفير حوالي 10 ٪ تقريبًا مقارنة بتنفيذ الخط العازل الجديد).

حول تعليقك الثاني - لقد ناقشنا ذلك بالفعل لأنه سيجعل التعامل مع الخطوط الملفوفة أسهل. في الوقت الحالي ، قررنا اتباع نهج الصف X col لتخطيط المخزن المؤقت الجديد وتنفيذ ذلك أولاً. أعتقد أننا يجب أن نعالج هذا مرة أخرى بمجرد أن نقوم بتنفيذ تغيير حجم إعادة التدفق.

حول إضافة أشياء إضافية إلى المخزن المؤقت: نقوم حاليًا بما تفعله معظم المحطات الطرفية الأخرى - يتم تحديد تقدم المؤشر بواسطة wcwidth مما يضمن البقاء متوافقًا مع فكرة pty / termios حول كيفية تخطيط البيانات. هذا يعني في الأساس أننا نتعامل على مستوى المخزن المؤقت فقط مع أشياء مثل الأزواج البديلة والجمع بين الأحرف. يمكن تطبيق أي قواعد انضمام أخرى "ذات مستوى أعلى" بواسطة رابط الأحرف في العارض (المستخدم حاليًا بواسطة https://github.com/xtermjs/xterm-addon-ligatures للوصلات المركبة). كان لدي علاقات عامة مفتوحة أيضًا لدعم حروف الكتابة أحادية الرمز في مستوى المخزن المؤقت المبكر ، لكنني أعتقد أننا لا نستطيع القيام بذلك في تلك المرحلة نظرًا لأن معظم الخلفيات pty ليس لديها أي فكرة عن هذا (هل هناك أي شيء على الإطلاق؟) وسننتهي مع تكتلات شار غريبة . الشيء نفسه ينطبق على دعم BIDI الحقيقي ، وأعتقد أنه من الأفضل عمل حروف الكتابة و BIDI في مرحلة العارض للحفاظ على حركات المؤشر / الخلية سليمة.

يبدو دعم عقد DOM المرتبطة بالخلايا مثيرًا للاهتمام حقًا ، وأنا أحب هذه الفكرة. هذا غير ممكن حاليًا في نهج مباشر نظرًا لأن لدينا خلفيات مختلفة للعارض (DOM ، و canvas 2D ، وعارض webgl الجديد اللامع) ، أعتقد أنه لا يزال من الممكن تحقيق ذلك لجميع العارضين من خلال وضع تراكب حيث لا يكون مدعومًا أصلاً (فقط عارض DOM سيفعل تكون قادرة على القيام بذلك مباشرة). سنحتاج إلى نوع من واجهة برمجة التطبيقات على مستوى المخزن المؤقت للإعلان عن تلك الأشياء وحجمها ويمكن للعارض أن يتعامل مع الأعمال القذرة. أعتقد أننا يجب أن نناقش / نتتبع هذا في مسألة منفصلة.

شكرا لاستجابة مفصلة.

_ "تظهر المشكلات عندما يتعلق الأمر بتغيير الحجم ، فهذا يعني في الأساس إعادة بناء جزء الذاكرة بالكامل (النسخ) أو النسخ السريع إلى جزء أكبر وإعادة تنظيم الأجزاء." _

هل تقصد: عند تغيير الحجم ، سيتعين علينا نسخ عناصر _4 * N_ بدلاً من عناصر _N_ فقط؟

قد يكون من المنطقي أن تحتوي المصفوفة على جميع الخلايا لأسطر منطقية (غير مغلفة). على سبيل المثال ، افترض سطرًا مكونًا من 180 حرفًا ومحطة عرضًا بطول 80 عمودًا. في هذه الحالة يمكن أن يكون لديك 3 BufferLine مثيلات تشترك جميعها في نفس المخزن المؤقت _4 * 180_ _data ، لكن كل BufferLine سيحتوي أيضًا على تعويض البداية.

حسنًا ، كان لدي كل شيء في مجموعة كبيرة واحدة تم إنشاؤها بواسطة [cols] x [rows] x [needed single cell space] . لذلك لا يزال يعمل بشكل أساسي كـ "قماش" بارتفاع وعرض محددين. هذا فعال للذاكرة وسريع لتدفق الإدخال العادي ، ولكن بمجرد استدعاء insertCell / deleteCell (تغيير الحجم سيفعل ذلك) ، الذاكرة الكاملة خلف الموضع الذي يحدث فيه الإجراء يجب أن يتم إزاحتها. بالنسبة إلى التمرير الصغير (<10k) ، فهذه ليست مشكلة أيضًا ، فهي حقًا أداة عرض لأكثر من 100 ألف سطر.
لاحظ أن المصفوفة المكتوبة الحالية لا يزال يتعين عليها القيام بهذه التحولات ، ولكنها أقل سمية حيث يتعين عليها فقط نقل محتوى الذاكرة إلى نهاية السطر.
فكرت في تنسيقات مختلفة للتحايل على التحولات المكلفة ، فالحقل الرئيسي لحفظ التحولات غير المنطقية للذاكرة سيكون في الواقع فصل التمرير للخلف عن "الصفوف الطرفية الفعالة" (الأحدث حتى terminal.rows ) نظرًا لأن هذه هي فقط تم تغييرها بواسطة قفزات المؤشر وإدراجها / حذفها.

تعد مشاركة الذاكرة الأساسية بواسطة عدة كائنات خط عازلة فكرة مثيرة للاهتمام لحل مشكلة الالتفاف. لست متأكدًا حتى الآن من كيفية عمل هذا بشكل موثوق دون تقديم معالجة صريحة للمراجع وما إلى ذلك. في إصدار آخر ، حاولت أن أفعل كل شيء باستخدام معالجة صريحة للذاكرة ، لكن عداد المرجع كان أداة عرض حقيقية وشعرت بالخطأ في أرض GC. (انظر # 1633 للأولويات)

تحرير: بالمناسبة ، كان التعامل الصريح مع الذاكرة على قدم المساواة مع نهج "الذاكرة لكل سطر" الحالي ، كنت آمل في الحصول على أداء أفضل بسبب موقع ذاكرة التخزين المؤقت الأفضل ، أعتقد أنه تم التهامه من خلال الخبرة الأكثر قليلاً. معالجة الذاكرة في تجريد JS.

يبدو دعم عقد DOM المرتبطة بالخلايا مثيرًا للاهتمام حقًا ، وأنا أحب هذه الفكرة. هذا غير ممكن حاليًا في نهج مباشر نظرًا لأن لدينا خلفيات مختلفة للعارض (DOM ، و canvas 2D ، وعارض webgl الجديد اللامع) ، أعتقد أنه لا يزال من الممكن تحقيق ذلك لجميع العارضين من خلال وضع تراكب حيث لا يكون مدعومًا أصلاً (فقط عارض DOM سيفعل تكون قادرة على القيام بذلك مباشرة).

إنه خارج الموضوع قليلاً ولكني أرى أننا في النهاية لدينا عقد DOM مرتبطة بالخلايا داخل منفذ العرض والتي ستعمل بشكل مشابه لطبقات عرض اللوحة. وبهذه الطريقة سيتمكن المستهلكون من "تزيين" الخلايا باستخدام HTML و CSS ولن يحتاجوا إلى الدخول إلى Canvas API.

قد يكون من المنطقي أن تحتوي المصفوفة على جميع الخلايا لأسطر منطقية (غير مغلفة). على سبيل المثال ، افترض سطرًا مكونًا من 180 حرفًا ومحطة عرضًا بطول 80 عمودًا. في هذه الحالة ، يمكن أن يكون لديك 3 مثيلات BufferLine تشترك جميعها في نفس المخزن المؤقت 4 * 180-element _data ، لكن كل BufferLine سيحتوي أيضًا على إزاحة البداية.

تم التقاط خطة إعادة التدفق التي تم ذكرها أعلاه في https://github.com/xtermjs/xterm.js/issues/622#issuecomment -375403572 ، بشكل أساسي نريد الحصول على المخزن المؤقت الفعلي غير المغلف ثم عرض في الأعلى يديره الخطوط الجديدة للوصول السريع إلى أي خط معين (تحسين أيضًا لتغيير الحجم الأفقي).

قد يكون استخدام نهج المصفوفة الكثيفة شيئًا يمكننا النظر إليه ، ولكن يبدو أنه لن يكون يستحق عناء إضافي في إدارة فواصل الأسطر غير المغلفة في مثل هذه المصفوفة ، والفوضى التي تحدث عندما يتم قطع الصفوف من أعلى المخزن المؤقت للتمرير. بغض النظر ، لا أعتقد أننا يجب أن ننظر في مثل هذه التغييرات حتى يتم الانتهاء من # 791 ونحن نبحث في # 622.

مع PR # 1796 ، يحصل المحلل اللغوي على دعم مصفوفة مكتوبة مما يفتح الباب لمزيد من التحسينات الخادمة ، ومن ناحية أخرى أيضًا إلى ترميزات الإدخال الأخرى.

في الوقت الحالي ، قررت استخدام Uint16Array ، نظرًا لسهولة تحويله ذهابًا وإيابًا باستخدام سلاسل JS. هذا يحد اللعبة أساسًا إلى UCS2 / UTF16 ، بينما المحلل اللغوي في الإصدار الحالي يمكنه أيضًا التعامل مع UTF32 (UTF8 غير مدعوم). تم تصميم المخزن المؤقت الطرفي المستند إلى الصفيف المكتوب حاليًا لـ UTF32 ، ويتم تحويل UTF16 -> UTF32 في InputHandler.print . من هنا فصاعدًا ، هناك عدة اتجاهات ممكنة:

  • اجعل كل UTF16 ، وبالتالي قم بتحويل المخزن المؤقت الطرفي إلى UTF16 أيضًا
    نعم ، ما زلت لم تحدد الطريق الذي يجب أن تسلكه هنا ، ولكن اختبرت العديد من تخطيطات المخزن المؤقت وتوصلت إلى استنتاج مفاده أن رقم 32 بت يعطي مساحة كافية لتخزين كود charcode الفعلي + wcwidth + إمكانية الجمع بين الفائض (تم التعامل معه بشكل مختلف تمامًا) ، بينما لا يمكن لـ 16bit القيام بذلك دون التضحية بتشفير الرموز الثمينة. لاحظ أنه حتى مع وجود المخزن المؤقت UTF16 لا يزال يتعين علينا إجراء تحويل UTF32 ، حيث يعمل wcwidth على نقاط الترميز الموحدة. لاحظ أيضًا أن المخزن المؤقت المستند إلى UTF16 سيوفر المزيد من الذاكرة لأكواد الأحرف الأقل ، في الواقع نادرًا ما يحدث أعلى من رموز أحرف مستوى BMP. هذا لا يزال بحاجة إلى بعض التحقيق.
  • جعل المحلل اللغوي UTF32
    هذا بسيط للغاية ، فقط استبدل جميع المصفوفات المكتوبة بمتغير 32 بت. الجانب السلبي - يجب أن يتم التحويل من UTF16 إلى UTF32 مسبقًا ، مما يعني أنه يتم تحويل المدخلات بالكامل ، حتى تسلسلات الهروب التي لن يتم تشكيلها أبدًا من أي رمز حرف> 255.
  • اجعل wcwidth UTF16 متوافقًا
    نعم ، إذا اتضح أن UTF16 أكثر ملاءمة للمخزن المؤقت للطرف ، فيجب القيام بذلك.

حول UTF8: لا يستطيع المحلل اللغوي حاليًا التعامل مع تسلسلات UTF8 الأصلية ، ويرجع ذلك أساسًا إلى حقيقة أن الأحرف الوسيطة تتعارض مع أحرف التحكم C1. يحتاج UTF8 أيضًا إلى معالجة تدفق مناسبة مع حالات وسيطة إضافية ، وهذا أمر سيئ ولا ينبغي إضافة imho إلى المحلل اللغوي. سيتم التعامل مع UTF8 بشكل أفضل مسبقًا ، وربما مع حق التحويل إلى UTF32 لتسهيل التعامل مع نقطة الرمز في كل مكان.

فيما يتعلق بترميز إدخال UTF8 المحتمل وتخطيط المخزن المؤقت الداخلي ، أجريت اختبارًا تقريبيًا. لاستبعاد التأثير الأعلى بكثير لعارض اللوحة القماشية على إجمالي وقت التشغيل ، قمت بذلك باستخدام عارض webgl القادم. من خلال المعيار المرجعي ls -lR /usr/lib بي ، أحصل على النتائج التالية:

  • سيد + عارض webgl الحالي:
    grafik

  • فرع الملعب ، يطبق # 1796 ، أجزاء # 1811 وعارض webgl:
    grafik

يقوم فرع الملعب بتحويل مبكر من UTF8 إلى UTF32 قبل إجراء التحليل والتخزين (يضيف التحويل حوالي 30 مللي ثانية). يتم الحصول على السرعة بشكل أساسي من خلال الوظيفتين الرائعتين أثناء تدفق الإدخال ، EscapeSequenceParser.parse (120 مللي ثانية مقابل 35 مللي ثانية) و InputHandler.print (350 مللي ثانية مقابل 75 مللي ثانية). يستفيد كلاهما كثيرًا من تبديل المصفوفة المكتوب عن طريق توفير مكالمات .charCodeAt .
لقد قارنت هذه النتائج أيضًا بمصفوفة UTF16 متوسطة الكتابة - EscapeSequenceParser.parse أسرع قليلاً (~ 25 مللي ثانية) لكن InputHandler.print يتأخر بسبب الاقتران البديل المطلوب والبحث عن نقطة الشفرة في wcwidth (120 مللي ثانية).
لاحظ أيضًا أنني وصلت بالفعل إلى الحد الأقصى الذي يمكن أن يوفره النظام لبيانات ls (i7 مع SSD) - تضيف التسريع المكتسب وقت الخمول بدلاً من جعل التشغيل أسرع.

ملخص:
Imho أسرع معالجة للمدخلات يمكننا الحصول عليها هي مزيج من نقل UTF8 + UTF32 لتمثيل المخزن المؤقت. في حين أن نقل UTF8 يحتوي على أفضل حزم بايت للإدخال الطرفي النموذجي ويزيل التحويلات غير المنطقية من pty عبر عدة طبقات من المخازن المؤقتة حتى Terminal.write ، يمكن للمخزن المؤقت المستند إلى UTF32 تخزين البيانات بسرعة كبيرة. يأتي الأخير مع مساحة ذاكرة أعلى قليلاً من UTF16 بينما UTF16 أبطأ قليلاً بسبب معالجة الأحرف الأكثر تعقيدًا مع المزيد من المراوغات.

استنتاج:
يجب أن نذهب مع تخطيط المخزن المؤقت المستند إلى UTF32 في الوقت الحالي. يجب أن نفكر أيضًا في التبديل إلى ترميز إدخال UTF8 ، ولكن هذا لا يزال بحاجة إلى مزيد من التفكير حول تغييرات واجهة برمجة التطبيقات (API) والآثار المترتبة على الدمجين (يبدو أن آلية ipc للإلكترون لا يمكنها التعامل مع البيانات الثنائية بدون تشفير BASE64 وتغليف JSON ، والذي من شأنه أن يبطل جهود الأداء).

تخطيط المخزن المؤقت لدعم الألوان الحقيقي القادم:

حاليًا ، يكون تخطيط المخزن المؤقت المستند إلى الصفيف هو التالي (خلية واحدة):

|    uint32_t    |    uint32_t    |    uint32_t    |
|      attrs     |    codepoint   |     wcwidth    |

حيث يحتوي attrs على جميع العلامات المطلوبة + ألوان FG و BG المستندة إلى 9 بت. يستخدم codepoint 21 بت (الحد الأقصى هو 0x10FFFF لـ UTF32) + 1 بت للإشارة إلى دمج الأحرف و wcwidth 2 بت (يتراوح من 0 إلى 2).

الفكرة هي إعادة ترتيب البتات إلى حزمة أفضل لإفساح المجال لقيم RGB الإضافية:

  • ضع wcwidth في أجزاء عالية غير مستخدمة codepoint
  • قم بتقسيم السمات إلى مجموعة FG و BG ذات 32 بت ، وتوزيع الأعلام على وحدات بت غير مستخدمة
|             uint32_t             |        uint32_t         |        uint32_t         |
|              content             |            FG           |            BG           |
| comb(1) wcwidth(2) codepoint(21) | flags(8) R(8) G(8) B(8) | flags(8) R(8) G(8) B(8) |

وتتمثل ميزة هذا النهج في الوصول الرخيص نسبيًا إلى كل قيمة من خلال وصول مؤشر واحد والحد الأقصى. عمليات 2 بت (و / أو + التحول).

تعد مساحة الذاكرة مستقرة بالنسبة للمتغير الحالي ، ولكنها لا تزال عالية جدًا مع 12 بايت لكل خلية. يمكن تحسين هذا بشكل أكبر من خلال التضحية ببعض وقت التشغيل من خلال التبديل إلى UTF16 و attr غير مباشر:

|        uint16_t        |              uint16_t               |
|    BMP codepoint(16)   | comb(1) wcwidth(2) attr pointer(13) |

الآن نحن أقل من 4 بايت لكل خلية + بعض المساحة للأترس. الآن يمكن إعادة تدوير Attrs للخلايا الأخرى أيضًا. أنجزت مهمة ياي! - إهم ثانية ...

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

  • أتر المراوغة
    يحتاج مؤشر Attr إلى بحث إضافي عن الذاكرة في حاوية بيانات أخرى.
  • تطابق Attr
    لتوفير مساحة حقًا باستخدام النهج الثاني ، يجب مطابقة Attr المحدد مقابل attrs المحفوظة بالفعل. هذا إجراء مرهق ، نهج مباشر من خلال النظر ببساطة في جميع القيم الموجودة في O (n) لـ n attrs المحفوظة ، انتهت تجارب شجرة RB الخاصة بي تقريبًا بلا فائدة للذاكرة بينما لا تزال في O (log n) ، مقارنةً بـ الوصول إلى الفهرس في نهج 32 بت مع O (1). بالإضافة إلى أن الشجرة لديها وقت تشغيل أسوأ لعدد قليل من العناصر المحفوظة (تدفع إلى حد ما> 100 إدخال مع ضمني شجرة RB).
  • الاقتران البديل UTF16
    مع مصفوفة مكتوبة 16 بت ، يجب أن نتحلل إلى UTF16 لنقاط التشفير ، والتي أدخلت عقوبة وقت التشغيل أيضًا (كما هو موضح في التعليق أعلاه). لاحظ أنه نادراً ما تحدث نقاط تشفير أكبر من BMP ، ولا يزال التحقق وحده مما إذا كانت نقطة الشفرة ستشكل زوجًا بديلًا يضيف 50 مللي ثانية تقريبًا.

جاذبية الطريقة الثانية هي توفير الذاكرة الإضافي. لذلك قمت باختباره مع فرع playground (انظر التعليق أعلاه) بتطبيق مُعدّل BufferLine :

grafik

نعم ، لقد عدنا نوعًا ما إلى حيث بدأنا قبل التغيير إلى المصفوفات المكتوبة UTF8 + في المحلل اللغوي. انخفض استخدام الذاكرة من 1.5 ميجابايت إلى ~ 0.7 ميجابايت على الرغم من (تطبيق تجريبي يحتوي على 87 خلية و 1000 سطر للتمرير للخلف).

من هنا فصاعدًا ، يتعلق الأمر بتوفير الذاكرة مقابل السرعة. نظرًا لأننا قمنا بالفعل بحفظ الكثير من الذاكرة عن طريق التبديل من مصفوفات js إلى المصفوفات المكتوبة (انخفض من 5.6 ميجابايت تقريبًا إلى 1.5 ميجابايت تقريبًا لكومة C ++ ، مما أدى إلى قطع سلوك JS Heap السام و GC) أعتقد أنه يجب علينا الذهاب هنا مع المتغير الأسرع. بمجرد ظهور مشكلة ملحة في استخدام الذاكرة مرة أخرى ، لا يزال بإمكاننا التبديل إلى تخطيط مخزن مؤقت أكثر إحكاما كما هو موضح في الطريقة الثانية هنا.

أوافق على ذلك ، دعنا نحسن السرعة طالما أن استهلاك الذاكرة ليس مصدر قلق. أرغب أيضًا في تجنب المراوغة قدر الإمكان لأنها تجعل قراءة الشفرة وصيانتها أكثر صعوبة. لدينا بالفعل الكثير من المفاهيم والتعديلات في قاعدة الشفرة الخاصة بنا والتي تجعل من الصعب على الأشخاص (بمن فيهم أنا 😅) اتباع تدفق الكود - ويجب دائمًا تبرير إدخال المزيد منها لسبب وجيه للغاية. IMO حفظ ميغا بايت أخرى من الذاكرة لا يبرر ذلك.

ومع ذلك ، فأنا أستمتع حقًا بالقراءة والتعلم من تمارينك ، شكرًا لمشاركتها بمثل هذا التفصيل الرائع!

mofux نعم هذا صحيح - تعقيد الكود أعلى من ذلك بكثير (يُقرأ بديل UTF16 مسبقًا ، وحسابات نقطة الشفرة الوسيطة ، وحاوية الشجرة مع عد المرجع على إدخالات Attr).
ونظرًا لأن تخطيط 32 بت عبارة عن ذاكرة مسطحة في الغالب (يحتاج دمج الأحرف فقط إلى المراوغة) ، فهناك المزيد من التحسينات الممكنة (أيضًا جزء من # 1811 ، لم يتم اختباره بعد للعارض).

هناك ميزة واحدة كبيرة للتوجيه غير المباشر إلى كائن Attr: إنه أكثر قابلية للتوسعة. يمكنك إضافة التعليقات التوضيحية أو الحروف الرسومية أو قواعد الرسم المخصصة. يمكنك تخزين معلومات الارتباط بطريقة ربما تكون أنظف وأكثر كفاءة. ربما حدد واجهة ICellPainter تعرف كيفية عرض خليتها ، ويمكنك أيضًا تعليق الخصائص المخصصة عليها.

تتمثل إحدى الأفكار في استخدام صفيفين لكل BufferLine: صفيف Uint32Array و ICellPainter ، مع عنصر واحد لكل خلية. إن ICellPainter الحالي هو خاصية لحالة المحلل اللغوي ، ولذا فأنت تعيد استخدام نفس ICellPainter طالما أن حالة اللون / السمة لا تتغير. إذا كنت بحاجة إلى إضافة خصائص خاصة إلى خلية ، فعليك أولاً استنساخ ICellPainter (إذا كان من الممكن مشاركته).

يمكنك تخصيص ICellPainter مسبقًا لمجموعات الألوان / السمات الأكثر شيوعًا - على الأقل يكون لديك كائن فريد يتوافق مع الألوان / السمات الافتراضية.

يمكن تنفيذ تغييرات النمط (مثل تغيير ألوان المقدمة / الخلفية الافتراضية) بمجرد تحديث مثيل (مثيلات) ICellPainter المطابق ، دون الحاجة إلى تحديث كل خلية.

هناك تحسينات ممكنة: على سبيل المثال ، استخدم مثيلات ICellPainter مختلفة للأحرف أحادية العرض ومزدوجة العرض (أو الأحرف ذات العرض الصفري). (هذا يوفر 2 بت في كل عنصر Uint32Array.) هناك 11 بت سمة متوفرة في Uint32Array (أكثر إذا قمنا بتحسين أحرف BMP). يمكن استخدامها لترميز مجموعات الألوان / السمات الأكثر شيوعًا / فائدة ، والتي يمكن استخدامها لفهرسة مثيلات ICellPainter الأكثر شيوعًا. إذا كان الأمر كذلك ، يمكن تخصيص مصفوفة ICellPainter بشكل كسول - أي فقط إذا كانت بعض الخلايا في السطر تتطلب رسام ICellPainter "أقل شيوعًا".

يمكن أيضًا إزالة المصفوفة المدمجة للأحرف غير BMP وتخزينها في ICellPainter. (يتطلب ذلك رسامًا فريدًا لـ ICellPainter لكل حرف غير BMP ، لذلك توجد مقايضة هنا.)

PerBothner نعم ،

ملاحظات قليلة حول ما جربته في عدة قواعد اختبار:

  • محتوى سلسلة الخلية
    قادمًا بنفسي من C ++ ، حاولت أن أنظر إلى المشكلة كما أفعل في C ++ ، لذلك بدأت بمؤشرات للمحتوى. كان هذا مؤشر سلسلة بسيط ، لكنه يشير في معظم الأحيان إلى سلسلة أحرف واحدة. يا للتبذير. لذلك كان أول تحسين لي هو التخلص من تجريد السلسلة عن طريق حفظ نقطة الشفرة مباشرةً بدلاً من العنوان (أسهل بكثير في C / C ++ منه في JS). ضاعف هذا تقريبًا وصول القراءة / الكتابة مع توفير 12 بايت لكل خلية (8 بايت مؤشر + 4 بايت على السلسلة ، 64 بت مع 32 بت wchar_t). Sidenote - نصف زيادة السرعة هنا مرتبط بذاكرة التخزين المؤقت (ذاكرة التخزين المؤقت مفقودة بسبب مواقع السلاسل العشوائية). أصبح هذا واضحًا من خلال الحل البديل الخاص بي لدمج محتوى الخلية - جزء كبير من الذاكرة قمت بفهرسته عندما كان codepoint يحتوي على مجموعة البت المدمجة (كان الوصول أسرع هنا بسبب موقع ذاكرة تخزين مؤقت أفضل ، تم اختباره باستخدام valgrind). تم نقل زيادة السرعة إلى JS ولم يكن ذلك كبيرًا نظرًا للسلسلة المطلوبة لتحويل الأرقام (لا يزال أسرع) ، ولكن حفظ الذاكرة كان أكبر (تخمين بسبب بعض غرفة الإدارة الإضافية لأنواع JS). كانت المشكلة هي StringStorage العالمي للأشياء المدمجة مع إدارة الذاكرة الصريحة ، وهو مضاد كبير في JS. كان الإصلاح السريع لذلك هو الكائن _combined ، والذي يقوم بتفويض عملية التنظيف إلى GC. لا يزال موضوعًا للتغيير ، ويهدف إلى تخزين محتوى السلسلة التعسفي المتعلق بالخلية (فعل ذلك مع وضع حروف الكتابة في الاعتبار ، لكننا لن نراهم قريبًا لأنهم غير مدعومين من قبل أي خلفية). لذلك هذا هو المكان المناسب لتخزين محتوى سلسلة إضافية على أساس خلية تلو الأخرى.
  • أترس
    مع Attrs بدأت "فكر بشكل كبير" - مع AttributeStorage عالمي لجميع السمات التي تم استخدامها في جميع الحالات الطرفية (راجع https://github.com/jerch/xterm.js/tree/AttributeStorage). من ناحية الذاكرة ، نجح هذا الأمر جيدًا ، ويرجع ذلك أساسًا إلى أن ppl لا تستخدم سوى مجموعة صغيرة من attrs حتى مع دعم الألوان الحقيقي. لم يكن الأداء جيدًا - ويرجع ذلك أساسًا إلى حساب المرجع (كان على كل خلية إلقاء نظرة خاطفة على هذه الذاكرة الأجنبية مرتين) ومطابقة attr. وعندما حاولت اعتماد شيء المرجع على JS شعرت بالخطأ - النقطة التي ضغطت فيها على زر "STOP". بين ذلك ، اتضح أننا وفرنا بالفعل أطنانًا من الذاكرة ومكالمات GC عن طريق التبديل إلى صفيف مكتوب ، وبالتالي فإن تخطيط الذاكرة المسطح الأكثر تكلفة قليلاً يمكن أن يسدد ميزة السرعة هنا.
    ما اختبرتُه yday (التعليق الأخير) كان مصفوفة مكتوبة ثانية على مستوى السطر للأترس مع الشجرة من https://github.com/jerch/xterm.js/tree/AttributeStorage للمطابقة (تشبه إلى حد كبير فكرة ICellPainter الخاصة بك ). حسنًا ، النتائج ليست واعدة ، لذلك أميل إلى التخطيط المسطح 32 بت في الوقت الحالي.

الآن اتضح أن هذا التصميم المسطح 32 بت قد تم تحسينه للأشياء الشائعة والإضافات غير الشائعة غير ممكنة معه. حقيقي. حسنًا ، لا يزال لدينا علامات (غير معتادة عليها ، لذا لا يمكنني تحديد ما هي قادرة عليه الآن) ، ونعم - لا تزال هناك بتات مجانية في المخزن المؤقت (وهو أمر جيد للاحتياجات المستقبلية ، على سبيل المثال ، يمكننا استخدامها أعلام عن معاملة خاصة وما شابه).

Tbh بالنسبة لي ، من المؤسف أن يؤدي تخطيط 16 بت مع تخزين attrs إلى هذا السوء ، ولا يزال خفض استخدام الذاكرة إلى النصف يمثل مشكلة كبيرة (خاصة عندما يبدأ ppl في استخدام أسطر التمرير> 10 كيلو بايت) ، ولكن عقوبة وقت التشغيل وتعقيد الكود يفوق الوزن تحتاج mem أعلى أجهزة الصراف الآلي imho.

هل يمكنك توضيح فكرة ICellPainter؟ ربما فاتني بعض الميزات المهمة حتى الآن.

كان هدفي لـ DomTerm هو تمكين وتشجيع تفاعل أكثر ثراءً فقط ما يتم تمكينه بواسطة محاكي طرفي تقليدي. يتيح استخدام تقنيات الويب العديد من الأشياء المثيرة للاهتمام ، لذلك سيكون من العار التركيز فقط على كونك محاكي طرفي تقليدي سريع. خاصة وأن العديد من حالات الاستخدام لـ xterm.js (مثل REPLs لـ IDEs) يمكن أن تستفيد حقًا من تجاوز النص البسيط. يعمل Xterm.js جيدًا في جانب السرعة (هل يشتكي أي شخص من السرعة؟) ، لكنه لا يعمل جيدًا في الميزات (يشتكي الأشخاص من فقدان الألوان

_ "هل يمكنك توضيح فكرة ICellPainter؟" _

بشكل عام ، يقوم ICellPainter بتغليف جميع البيانات لكل خلية باستثناء رمز / قيمة الحرف ، والتي تأتي من Uint32Array. هذا بالنسبة لخلايا الأحرف "العادية" - بالنسبة للصور المضمنة و "المربعات" الأخرى ، قد لا يكون رمز / قيمة الحرف منطقيًا.

interface ICellPainter {
    drawOnCanvas(ctx: CanvasRenderingContext2D, code: number, x: number, y: number);
    // transitional - to avoid allocating IGlyphIdentifier we should replace
    //  uses by pair of ICellPainter and code.  Also, a painter may do custom rendering,
    // such that there is no 'code' or IGlyphIdentifier.
    asGlyph(code: number): IGlyphIdentifier;
    width(): number; // in pixels for flexibility?
    height(): number;
    clone(): ICellPainter;
}

يمكن أن يتم تعيين خلية إلى ICellPainter بطرق مختلفة. من الواضح أن كل BufferLine يحتوي على مصفوفة ICellPainter ، ولكن هذا يتطلب مؤشر 8 بايت (على الأقل) لكل خلية. أحد الاحتمالات هو دمج المصفوفة المدمجة مع مصفوفة ICellPainter: إذا تم تعيين IS_COMBINED_BIT_MASK ، فإن ICellPainter يتضمن أيضًا السلسلة المدمجة. تحسين آخر محتمل هو استخدام البتات المتوفرة في Uint32Array كفهرس في مصفوفة: يضيف ذلك بعض التعقيد والمباشرة الإضافية ، ولكنه يوفر مساحة.

أود أن أشجعنا على التحقق مما إذا كان بإمكاننا القيام بذلك بالطريقة التي يقوم بها محرر موناكو (أعتقد أنهم وجدوا طريقة ذكية وفعالة حقًا). بدلاً من تخزين هذه المعلومات في المخزن المؤقت ، فإنها تسمح لك بإنشاء decorations . تقوم بإنشاء زخرفة لنطاق صف / عمود وسوف تلتزم بهذا النطاق:

// decorations are buffer-dependant (we need to know which buffer to decorate)
const decoration = buffer.createDecoration({
  type: 'link',
  data: 'https://www.google.com',
  range: { startRow: 2, startColumn: 5, endRow: 2, endColumn: 25 }
});

في وقت لاحق ، يمكن للعارض التقاط تلك الزخارف ورسمها.

يرجى مراجعة هذا المثال الصغير الذي يوضح كيف تبدو واجهة برمجة تطبيقات محرر موناكو:
https://microsoft.github.io/monaco-editor/playground.html#interacting -with-the-editor-line-and-inline-decorations

بالنسبة لأشياء مثل عرض الصور داخل محطة موناكو ، يستخدم مفهوم مناطق الرؤية التي يمكن رؤيتها (من بين مفاهيم أخرى) في مثال هنا:
https://microsoft.github.io/monaco-editor/playground.html#interacting -with-the-editor-listen-to-mouse-events

PerBothner Thx للتوضيح و

نخطط في النهاية لنقل سلسلة الإدخال + المخزن المؤقت إلى عامل الويب في المستقبل. وبالتالي ، فإن الغرض من المخزن المؤقت هو العمل على مستوى مجرد ولا يمكننا استخدام أي عناصر ذات صلة بالعرض / التمثيل حتى الآن مثل مقاييس البكسل أو أي عقد DOM. أرى احتياجاتك لهذا نظرًا لأن DomTerm قابلة للتخصيص بدرجة كبيرة ، ولكن أعتقد أنه يجب علينا القيام بذلك باستخدام واجهة برمجة تطبيقات للعلامة الداخلية المحسّنة ويمكننا التعلم هنا من monaco / vscode (thx for th pointersmofux).
أرغب حقًا في الحفاظ على المخزن المؤقت الأساسي نظيفًا من الأشياء غير المألوفة ، فربما ينبغي أن نناقش استراتيجيات العلامات الممكنة مع مشكلة جديدة؟

ما زلت غير راضٍ عن نتيجة نتائج اختبار تخطيط 16 بت. نظرًا لأن القرار النهائي لم يتم إلحاحه بعد (لن نرى أيًا من هذا قبل 3.11) ، سأستمر في اختباره مع بعض التغييرات (لا يزال الحل الأكثر إثارة بالنسبة لي من البديل 32 بت).

|             uint32_t             |        uint32_t         |        uint32_t         |
|              content             |            FG           |            BG           |
| comb(1) wcwidth(2) codepoint(21) | flags(8) R(8) G(8) B(8) | flags(8) R(8) G(8) B(8) |

أعتقد أيضًا أننا يجب أن نبدأ بشيء قريب من هذا للبدء ، يمكننا استكشاف خيارات أخرى لاحقًا ولكن من المحتمل أن يكون هذا هو الأسهل للتشغيل. من المؤكد أن سمة المراوغة لها وعد IMO حيث لا توجد عادة العديد من السمات المميزة في جلسة المحطة الطرفية.

أود أن أشجعنا على التحقق مما إذا كان بإمكاننا القيام بذلك بالطريقة التي يقوم بها محرر موناكو (أعتقد أنهم وجدوا طريقة ذكية وفعالة حقًا). بدلاً من تخزين هذه المعلومات في المخزن المؤقت ، فإنها تسمح لك بإنشاء زخارف. تقوم بإنشاء زخرفة لنطاق صف / عمود وسوف تلتزم بهذا النطاق:

شيء من هذا القبيل هو المكان الذي أود أن أرى الأشياء تسير فيه. كانت إحدى الأفكار التي خطرت لي على طول هذه الأسطر هي السماح للتضمينات بإرفاق عناصر DOM بنطاقات لتمكين رسم الأشياء المخصصة. هناك 3 أشياء يمكنني التفكير فيها في الوقت الحالي وأود تحقيقها من خلال هذا:

  • ارسم رابط تسطير بهذه الطريقة (سوف يبسط كيفية رسمها بشكل ملحوظ)
  • السماح بالعلامات على الصفوف ، مثل * أو شيء من هذا القبيل
  • اسمح للصفوف بـ "وميض" للإشارة إلى حدوث شيء ما

يمكن تحقيق كل ذلك من خلال تراكب وهو نوع سهل الوصول إليه من واجهة برمجة التطبيقات (يعرض عقدة DOM) ويمكن أن يعمل بغض النظر عن نوع العارض.

لست متأكدًا من رغبتنا في الدخول في مجال السماح للتضمينات بتغيير كيفية رسم ألوان الخلفية والأمام.


jerch ، سأضع هذا في 3.11.0 علامة فارقة لأنني أعتبر أن هذه المشكلة قد انتهت عندما نزيل تطبيق مصفوفة JS المخطط له في ذلك الوقت. من المخطط أيضًا دمج

أيضًا ، من المحتمل أن يكون من الأفضل مناقشة الكثير من هذه المناقشة اللاحقة على https://github.com/xtermjs/xterm.js/issues/484 و https://github.com/xtermjs/xterm.js/issues/1852 (تم إنشاؤه لعدم وجود مشكلة في الديكور).

Tyriar Woot - مغلق أخيرًا: sweat_smile:

🎉 🕺 🍾

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات

القضايا ذات الصلة

circuitry2 picture circuitry2  ·  4تعليقات

pfitzseb picture pfitzseb  ·  3تعليقات

jerch picture jerch  ·  3تعليقات

parisk picture parisk  ·  3تعليقات

LB-J picture LB-J  ·  3تعليقات