Penyimpanan
Saat ini buffer kami menggunakan terlalu banyak memori, terutama untuk aplikasi yang meluncurkan banyak terminal dengan set scrollback besar. Misalnya, demo menggunakan terminal 160x24 dengan 5000 scrollback diisi membutuhkan sekitar 34mb memori (lihat https://github.com/Microsoft/vscode/issues/29840#issuecomment-314539964), ingat itu hanya satu terminal dan monitor 1080p akan kemungkinan menggunakan terminal yang lebih luas. Juga, untuk mendukung truecolor (https://github.com/sourcelair/xterm.js/issues/484), setiap karakter perlu menyimpan 2 jenis number
yang hampir menggandakan konsumsi memori saat ini dari penyangga.
Pengambilan teks baris yang lambat
Ada masalah lain karena perlu mengambil teks baris yang sebenarnya dengan cepat. Alasan mengapa ini lambat adalah karena cara data ditata; baris berisi array karakter, masing-masing memiliki string karakter tunggal. Jadi kita akan membangun string dan kemudian akan segera diambil untuk pengumpulan sampah. Sebelumnya kita tidak perlu melakukan ini sama sekali karena teks ditarik dari buffer baris (secara berurutan) dan dirender ke DOM. Namun, ini menjadi hal yang semakin berguna untuk dilakukan saat kami meningkatkan xterm.js lebih lanjut, fitur seperti pilihan dan tautan menarik data ini. Sekali lagi menggunakan contoh scrollback 160x24/5000, dibutuhkan 30-60ms untuk menyalin seluruh buffer pada Macbook Pro Pertengahan 2014.
Mendukung masa depan
Masalah potensial lainnya di masa depan adalah ketika kita melihat memperkenalkan beberapa model tampilan yang mungkin perlu menduplikasi beberapa atau semua data dalam buffer, hal semacam ini akan diperlukan untuk mengimplementasikan reflow (https://github.com/sourcelair /xterm.js/issues/622) dengan benar (https://github.com/sourcelair/xterm.js/pull/644#issuecomment-298058556) dan mungkin juga diperlukan untuk mendukung pembaca layar dengan benar (https://github.com /sourcelair/xterm.js/issues/731). Tentu akan bagus untuk memiliki ruang gerak dalam hal memori.
Diskusi ini dimulai di https://github.com/sourcelair/xterm.js/issues/484 , ini membahas lebih detail dan mengusulkan beberapa solusi tambahan.
Saya condong ke solusi 3 dan bergerak menuju solusi 5 jika ada waktu dan itu menunjukkan peningkatan yang nyata. Akan menyukai umpan balik! /cc @jerch , @mofux , @rauchg , @parisk
Ini pada dasarnya apa yang kita lakukan sekarang, hanya dengan truecolor fg dan bg ditambahkan.
// [0]: charIndex
// [1]: width
// [2]: attributes
// [3]: truecolor bg
// [4]: truecolor fg
type CharData = [string, number, number, number, number];
type LineData = CharData[];
kelebihan
Kontra
Ini akan menyimpan string terhadap garis daripada garis, ini mungkin akan melihat keuntungan yang sangat besar dalam seleksi dan menghubungkan dan akan lebih berguna seiring berjalannya waktu memiliki akses cepat ke seluruh string baris.
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;
kelebihan
Int32Array
Kontra
Menarik atribut keluar dan mengaitkannya dengan rentang. Karena tidak akan pernah ada atribut yang tumpang tindih, ini dapat ditata secara berurutan.
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
}
}
kelebihan
.flags
alih-alih [0]
)Kontra
Idenya di sini adalah untuk memanfaatkan fakta bahwa umumnya tidak ada banyak gaya dalam satu sesi terminal, jadi kita tidak boleh membuat sesedikit yang diperlukan dan menggunakannya kembali.
// [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;
}
kelebihan
.flags
alih-alih [0]
)Kontra
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;
}
kelebihan
.flags
alih-alih [0]
)Kontra
CharAttributes
per blok?CharAttributeEntry
Ini mengambil solusi 3 tetapi juga menambahkan dengan malas mengevaluasi string teks untuk akses cepat ke teks baris. Karena kita juga menyimpan karakter di CharData
kita bisa dengan malas mengevaluasinya.
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;
}
kelebihan
.flags
alih-alih [0]
)Kontra
Int32Array
tidak akan berfungsi karena perlu waktu lama untuk mengonversi int kembali menjadi karakter.Pendekatan lain yang dapat digabungkan: gunakan indexeddb, websql atau filesystem api untuk mengeluarkan entri scrollback yang tidak aktif ke disk
Usulan yang bagus. Saya setuju dengan itu 3. adalah cara terbaik untuk saat ini karena memungkinkan kita menghemat memori sambil mendukung warna asli juga.
Jika kita sampai di sana dan segala sesuatunya terus berjalan dengan baik, kita kemudian dapat mengoptimalkan seperti yang diusulkan di 5. atau dengan cara lain apa pun yang muncul di benak kita saat itu dan masuk akal.
3. bagus 👍.
@mofux , sementara pasti ada kasus penggunaan untuk menggunakan teknik yang didukung penyimpanan disk untuk mengurangi jejak memori, ini dapat menurunkan pengalaman pengguna perpustakaan di lingkungan browser yang meminta izin pengguna untuk menggunakan penyimpanan disk.
Tentang Mendukung masa depan :
Semakin saya memikirkannya, semakin banyak gagasan untuk memiliki WebWorker
yang melakukan semua pekerjaan berat untuk mengurai data tty, mempertahankan buffer baris, mencocokkan tautan, mencocokkan token pencarian, dan semacamnya menarik bagi saya. Pada dasarnya melakukan pekerjaan berat di utas latar belakang yang terpisah tanpa memblokir ui. Tapi saya pikir ini harus menjadi bagian dari diskusi terpisah mungkin menuju rilis 4.0
+100 tentang WebWorker di masa mendatang, tetapi saya pikir kami perlu mengubah daftar versi browser yang kami dukung karena tidak semuanya dapat menggunakannya...
Ketika saya mengatakan Int32Array
, ini akan menjadi array biasa jika tidak didukung oleh lingkungan.
@mofux pemikiran yang baik dengan WebWorker
di masa depan 👍
@AndrienkoAleksandr ya jika kita ingin menggunakan WebWorker
kita harus tetap mendukung alternatif juga melalui deteksi fitur.
Wah bagus daftarnya :)
Saya juga cenderung condong ke 3. karena menjanjikan pemotongan besar dalam konsumsi memori untuk lebih dari 90% dari penggunaan terminal biasa. Optimalisasi memori imho harus menjadi tujuan utama pada tahap ini. Pengoptimalan lebih lanjut untuk kasus penggunaan tertentu mungkin berlaku di atas ini (apa yang terlintas dalam pikiran saya: "kanvas seperti aplikasi" seperti ncurses dan semacamnya akan menggunakan banyak pembaruan sel tunggal dan agak menurunkan daftar [start, end]
dari waktu ke waktu) .
@AndrienkoAleksandr ya saya suka ide webworker juga karena bisa mengangkat _some_ beban dari mainthread. Masalah di sini adalah (selain fakta bahwa itu mungkin tidak didukung oleh semua sistem target yang diinginkan) adalah _some_ - bagian JS bukan masalah besar lagi dengan semua pengoptimalan, xterm.js telah melihat dari waktu ke waktu. Masalah nyata dari segi kinerja adalah tata letak/rendering browser...
@mofux Hal paging ke beberapa "memori asing" adalah ide yang bagus, meskipun itu harus menjadi bagian dari beberapa abstraksi yang lebih tinggi dan bukan dari "berikan widget terminal interaktif" yang xterm.js. Ini dapat dicapai dengan addon imho.
Di luar topik: Melakukan beberapa tes dengan array vs. typedarrays vs. asm.js. Yang bisa saya katakan - OMG, ini seperti 1 : 1,5 : 10
untuk beban dan set variabel sederhana (pada FF bahkan lebih). Jika kecepatan JS murni benar-benar mulai sakit, "gunakan asm" mungkin ada untuk menyelamatkan. Tapi saya akan melihat ini sebagai upaya terakhir karena itu akan menyiratkan perubahan mendasar. Dan webassembly belum siap untuk dikirim.
Di luar topik: Melakukan beberapa tes dengan array vs. typedarrays vs. asm.js. Yang bisa saya katakan - OMG, ini seperti 1: 1,5: 10 untuk beban dan set variabel sederhana (pada FF bahkan lebih)
@jerch untuk memperjelas, apakah array vs typedarrays adalah 1:1 hingga 1:5?
Woops tangkapan yang bagus dengan koma - maksud saya 10:15:100
segi kecepatan. Tetapi hanya pada array yang diketik FF yang sedikit lebih cepat dari array normal. asm setidaknya 10 kali lebih cepat daripada array js di semua browser - diuji dengan FF, webkit (Safari), blink/V8 (Chrome, Opera).
@jerch keren, kecepatan 50% dari typedarrays selain memori yang lebih baik pasti layak untuk diinvestasikan untuk saat ini.
Ide untuk menghemat memori - mungkin kita bisa menghilangkan width
untuk setiap karakter. Akan mencoba mengimplementasikan versi wcwidth yang lebih murah.
@jerch kita perlu mengaksesnya sedikit, dan kita tidak bisa malas memuatnya atau apa pun karena ketika reflow datang kita akan membutuhkan lebar setiap karakter di buffer. Bahkan jika itu cepat, kami mungkin masih ingin menyimpannya.
Mungkin lebih baik menjadikannya opsional, dengan asumsi 1 jika tidak ditentukan:
type CharData = [string, number?]; // not sure if this is valid syntax
[
// 'a'
['a'],
// '文'
['文', 2],
// after wide
['', 0],
...
]
@Tyriar Ya - karena saya sudah menulisnya, silakan lihat di PR #798
Speedup adalah 10 hingga 15 kali di komputer saya dengan biaya 16k byte untuk tabel pencarian. Mungkin kombinasi keduanya dimungkinkan jika masih diperlukan.
Beberapa tanda lainnya yang akan kami dukung di masa mendatang: https://github.com/sourcelair/xterm.js/issues/580
Pikiran lain: Hanya bagian bawah terminal ( Terminal.ybase
to Terminal.ybase + Terminal.rows
) yang dinamis. Scrollback yang membentuk sebagian besar data benar-benar statis, mungkin kita bisa memanfaatkan ini. Saya tidak tahu ini sampai baru-baru ini, tetapi bahkan hal-hal seperti menghapus baris (DL, CSI Ps M) tidak membawa scrollback kembali ke bawah melainkan menyisipkan baris lain. Demikian pula, gulir ke atas (SU, CSI Ps S) menghapus item di Terminal.scrollTop
dan menyisipkan item di Terminal.scrollBottom
.
Mengelola bagian dinamis bawah terminal secara mandiri dan mendorong untuk menggulir kembali ketika saluran didorong keluar dapat menghasilkan beberapa keuntungan yang signifikan. Misalnya, bagian bawah bisa lebih bertele-tele untuk mendukung modifikasi atribut, akses lebih cepat, dll. Sedangkan scrollback bisa lebih dari format arsip seperti yang diusulkan di atas.
Pemikiran lain: mungkin ide yang lebih baik untuk membatasi CharAttributeEntry
ke baris karena begitulah cara kerja sebagian besar aplikasi. Juga jika terminal diubah ukurannya maka bantalan "kosong" ditambahkan ke kanan yang tidak memiliki gaya yang sama.
misalnya:
Di sebelah kanan perbedaan merah/hijau adalah sel "kosong" tanpa gaya.
@Tyriar
Adakah kesempatan untuk memasukkan kembali masalah ini ke dalam agenda? Setidaknya untuk program intensif keluaran, cara berbeda untuk menyimpan data terminal dapat menghemat banyak memori dan waktu. Beberapa hibrida 2/3/4 akan memberikan peningkatan throughput yang besar, jika kita dapat menghindari pemisahan dan penyimpanan karakter tunggal dari string input. Juga menyimpan atribut hanya setelah diubah akan membantu menghemat memori.
Contoh:
Dengan parser baru, kita dapat menyimpan banyak karakter input tanpa mengotak-atik atribut, karena kita tahu bahwa mereka tidak akan berubah di tengah string itu. Atribut string itu dapat disimpan di beberapa struktur data atau atribut lain bersama dengan wcwidths (ya kita masih membutuhkannya untuk menemukannya) dan jeda baris dan berhenti. Ini pada dasarnya akan menyerahkan model sel saat data masuk.
Masalah muncul jika ada sesuatu yang masuk dan ingin memiliki representasi penelusuran data terminal (mis. penyaji atau urutan pelarian/pengguna ingin memindahkan kursor). Kita masih harus melakukan perhitungan sel jika itu terjadi, tetapi seharusnya cukup untuk melakukan ini hanya untuk konten di dalam kolom dan baris terminal. (Belum yakin tentang konten yang digulir, yang mungkin lebih dapat di-cache dan murah untuk digambar ulang.)
@jerch Saya akan bertemu @mofux suatu hari di Praha dalam beberapa minggu dan kami akan melakukan/memulai beberapa perbaikan internal tentang bagaimana atribut teks ditangani yang mencakup ini
Dari https://github.com/xtermjs/xterm.js/pull/1460#issuecomment -390500944
Algo agak mahal karena setiap karakter perlu dievaluasi dua kali
@jerch jika Anda memiliki ide tentang akses teks yang lebih cepat dari buffer, beri tahu kami. Saat ini sebagian besar hanya satu karakter seperti yang Anda tahu, tetapi bisa berupa ArrayBuffer
, string, dll. Saya telah berpikir bahwa kita harus berpikir untuk mengambil lebih banyak keuntungan dari scrollback yang entah bagaimana tidak dapat diubah.
Yah saya telah banyak bereksperimen dengan ArrayBuffers di masa lalu:
Array
mengenai runtime untuk metode tipikal (mungkin masih kurang dioptimalkan oleh vendor mesin)new UintXXArray
jauh lebih buruk daripada pembuatan array literal dengan []
TextEncoder
meskipun)Temuan saya tentang ArrayBuffer
menyarankan untuk tidak menggunakannya untuk data string karena penalti konversi. Secara teori terminal dapat menggunakan ArrayBuffer
dari node-pty hingga data terminal (ini akan menghemat beberapa konversi dalam perjalanan ke frontend), tidak yakin apakah rendering dapat dilakukan dengan cara itu, saya pikir untuk membuat hal itu selalu membutuhkan konversi uint16_t
menjadi string
final. Tetapi bahkan satu pembuatan string terakhir akan memakan sebagian besar runtime yang disimpan - dan lebih jauh lagi, akan mengubah terminal secara internal menjadi binatang C-ish yang jelek. Oleh karena itu saya menyerah pendekatan ini.
TL;DR ArrayBuffer
lebih unggul jika Anda dapat mengalokasikan dan menggunakan kembali struktur data. Untuk yang lainnya, array normal lebih baik. String tidak layak untuk dimasukkan ke dalam ArrayBuffers.
Ide baru yang saya buat mencoba untuk menurunkan pembuatan string sebanyak mungkin, khususnya. mencoba untuk menghindari perpecahan jahat dan bergabung. Ini agak didasarkan pada ide ke-2 Anda di atas dengan metode InputHandler.print
, wcwidth dan perhentian baris:
print
sekarang mendapatkan seluruh string hingga beberapa baris terminalwcwidth(string) % cols
\n
(penghentian garis keras): memajukan kursor dengan satu baris, menandai posisi dalam daftar penunjuk sebagai pemutusan paksa\r
: memuat konten baris terakhir (dari posisi kursor saat ini ke jeda baris terakhir) ke dalam beberapa buffer baris untuk ditimpa\r
tidak diperlukan abstraksi sel atau pemisahan stringcols
x rows
(mereka hanya mengubah flag attr yang disimpan bersama dengan seluruh string)Btw wcwidths adalah bagian dari algo grapheme, jadi ini mungkin dapat dipertukarkan di masa depan.
Sekarang bagian berbahaya 1 - seseorang ingin memindahkan kursor di dalam cols
x rows
:
cols
mundur dalam jeda baris - awal dari konten terminal saat iniSekarang bagian berbahaya 2 - penyaji ingin menggambar sesuatu:
Kelebihan:
InputHandler
paling umum - print
Kontra:
InputHandler
akan berbahaya dalam arti mengganggu model aliran ini dan kebutuhan beberapa abstraksi sel perantaraNah ini adalah konsep kasar dari ide tersebut, jauh dari atm yang bisa digunakan karena banyak detail yang belum tercakup. khususnya bagian "berbahaya" bisa menjadi buruk dengan banyak masalah kinerja (seperti menurunkan buffer dengan perilaku gc yang lebih buruk, dll pp)
@jerch
String tidak layak untuk dimasukkan ke dalam ArrayBuffers.
Saya pikir Monaco menyimpan buffer di ArrayBuffer
s dan kinerjanya cukup tinggi. Saya belum melihat terlalu dalam ke implementasinya.
khususnya mencoba menghindari perpecahan yang buruk dan bergabung
Yang mana?
Saya sudah berpikir kita harus berpikir tentang mengambil lebih banyak keuntungan dari scrollback menjadi tidak berubah entah bagaimana.
Satu ide adalah memisahkan scrollback dari bagian viewport. Setelah sebuah baris masuk ke scrollback, itu akan didorong ke dalam struktur data scrollback. Anda dapat membayangkan 2 objek CircularList
, yang garisnya dioptimalkan untuk tidak pernah berubah, satu untuk sebaliknya.
@Tyriar Tentang scrollback - ya karena ini tidak pernah dapat dijangkau oleh kursor, mungkin menghemat beberapa memori untuk hanya menjatuhkan abstraksi sel untuk menggulir keluar baris.
@Tyriar
Masuk akal untuk menyimpan string di ArrayBuffer jika kita dapat membatasi konversi menjadi satu (mungkin yang terakhir untuk output render). Itu sedikit lebih baik daripada penanganan string di semua tempat. Ini dapat dilakukan karena node-pty juga dapat memberikan data mentah (dan juga soket web dapat memberi kita data mentah).
khususnya mencoba menghindari perpecahan yang buruk dan bergabung
Yang mana?
Seluruh pendekatan adalah untuk menghindari perpecahan _minimize_ sama sekali . Jika tidak ada yang meminta kursor melompat ke data buffer, string tidak akan pernah terpecah dan bisa langsung menuju ke penyaji (jika didukung). Tidak ada sel yang terbelah dan kemudian bergabung sama sekali.
@jerch baik itu bisa jika viewport diperluas, saya pikir kita juga dapat menarik scrollback ketika garis dihapus? Tidak 100% yakin tentang itu atau bahkan jika itu perilaku yang benar.
@Tyriar Ah benar. Tidak yakin tentang yang terakhir, saya pikir xterm asli memungkinkan ini hanya untuk pengguliran mouse atau scrollbar asli. Bahkan SD/SU tidak memindahkan konten scrollbuffer kembali ke viewport terminal "aktif".
Bisakah Anda mengarahkan saya ke sumber editor monaco, di mana ArrayBuffer digunakan? Sepertinya saya tidak dapat menemukannya sendiri :blush:
Hmm baca ulang spesifikasi TextEncoder/Decoder, dengan ArrayBuffers dari node-pty hingga frontend kita pada dasarnya terjebak dengan utf-8, kecuali kita menerjemahkannya dengan cara yang sulit di beberapa titik. Membuat xterm.js utf-8 sadar? Idk, ini akan melibatkan banyak perhitungan codepoint menengah untuk karakter unicode yang lebih tinggi. Proside - itu akan menghemat memori untuk karakter ascii.
@rebornix bisakah Anda memberi kami beberapa petunjuk ke mana monaco menyimpan buffer?
Berikut adalah beberapa angka untuk array yang diketik dan parser baru (lebih mudah diadopsi):
print
melompat dari 190 MB/dtk menjadi 290 MB/dtkprint
melompat dari 190 MB/dtk menjadi 320 MB/dtkSecara keseluruhan UTF-16 berkinerja jauh lebih baik, tetapi itu diharapkan karena parser dioptimalkan untuk itu. UTF-8 menderita dari perhitungan codepoint perantara.
Konversi string ke array yang diketik memakan ~4% JS runtime dari benchmark saya ls -lR /usr/lib
(selalu jauh di bawah 100 md, dilakukan melalui loop dalam InputHandler.parse
). Saya tidak menguji konversi terbalik (ini secara implisit dilakukan atm di InputHandller.print
pada sel demi tingkat sel). Runtime keseluruhan sedikit lebih buruk daripada dengan string (waktu yang disimpan dalam parser tidak mengkompensasi waktu konversi). Ini mungkin berubah ketika bagian lain juga menyadari array yang diketik.
Dan tangkapan layar yang sesuai (diuji dengan ls -lR /usr/lib
):
dengan string:
dengan Uint16Array:
Perhatikan perbedaan untuk EscapeSequenceParser.parse
, yang dapat mengambil untung dari array yang diketik (~30% lebih cepat). InputHandler.parse
melakukan konversi, sehingga lebih buruk untuk versi array yang diketik. Juga GC Minor memiliki lebih banyak hal yang harus dilakukan untuk array yang diketik (karena saya membuang array).
Sunting: Aspek lain dapat dilihat di tangkapan layar - GC menjadi relevan dengan ~20% runtime, frame yang berjalan lama (berbendera merah) semuanya terkait dengan GC.
Hanya ide lain yang agak radikal:
int8
hingga int16
hingga int32
jenis dimungkinkan. Pengalokasi mengembalikan indeks gratis pada Uint8Array
, penunjuk ini dapat dikonversi ke posisi Uint16Array
atau Uint32Array
dengan sedikit pergeseran sederhana.uint16_t
ketik untuk UTF-16.InputHandler
dengan pointer ke memori ini, bukan irisan string.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
}
Kelebihan:
malloc
dan free
(tergantung kepintaran pengalokasi/dealokator)Kontra:
:senyum:
sangat menyenangkan untuk diterapkan, mengubah segalanya
Ini lebih dekat dengan cara kerja Monaco, saya ingat posting blog ini yang membahas strategi menyimpan metadata karakter https://code.visualstudio.com/blogs/2017/02/08/syntax-highlighting-optimizations
Yup itu pada dasarnya ide yang sama.
Semoga jawaban saya tentang tempat monaco menyimpan buffer tidak terlambat.
Alex dan saya mendukung Array Buffer dan sebagian besar waktu itu memberi kami kinerja yang baik. Beberapa tempat kami menggunakan ArrayBuffer:
Kami menggunakan string sederhana untuk buffer teks alih-alih Array Buffer karena string V8 lebih mudah untuk dimanipulasi
Sematkan tautan
Daftar berikut hanyalah ringkasan singkat dari konsep menarik yang saya temukan yang mungkin membantu menurunkan penggunaan memori dan/atau runtime:
Untuk mendapatkan penjumlahan di sini, berikut adalah retasan cepat bagaimana kita bisa "menggabungkan" atribut teks.
Kode ini terutama didorong oleh ide untuk menghemat memori untuk data buffer (runtime akan menderita, belum diuji berapa banyak). khususnya atribut teks dengan RGB untuk latar depan dan latar belakang (setelah didukung) akan membuat xterm.js memakan banyak memori oleh sel saat ini dengan tata letak sel. Kode mencoba untuk menghindari ini dengan penggunaan atlas penghitungan ref yang dapat diubah ukurannya untuk atribut. Ini adalah pilihan karena satu terminal hampir tidak akan menampung lebih dari 1 juta sel, yang akan menumbuhkan atlas menjadi 1M * entry_size
jika semua sel berbeda.
Sel itu sendiri hanya perlu menyimpan indeks atlas atribut. Pada perubahan sel, indeks lama harus tidak direferensikan dan yang baru direferensikan. Indeks atlas akan menggantikan atribut atribut dari objek terminal dan akan berubah sendiri di SGR.
Atlas saat ini hanya membahas atribut teks, tetapi dapat diperluas ke semua atribut sel jika diperlukan. Sementara buffer terminal saat ini menampung 2 angka 32bit untuk data atribut (4 dengan RGB dalam desain buffer saat ini), atlas akan menguranginya menjadi hanya satu angka 32bit. Entri atlas dapat dikemas lebih lanjut juga.
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));
Sunting:
Penalti runtime hampir nol, untuk benchmark saya dengan ls -lR /usr/lib
itu menambahkan kurang dari 1 msec ke total runtime ~2,3 s. Catatan tambahan yang menarik - perintah menetapkan kurang dari 64 slot atribut teks yang berbeda untuk output data sebesar 5 MB dan akan menghemat lebih dari 20 MB setelah diterapkan sepenuhnya.
Membuat beberapa PR prototipe untuk menguji beberapa perubahan pada buffer (lihat https://github.com/xtermjs/xterm.js/pull/1528#issue-196949371 untuk gagasan umum di balik perubahan):
@jerch mungkin ide yang baik untuk menjauh dari kata atlas untuk ini sehingga "atlas" selalu berarti "atlas tekstur". Sesuatu seperti toko atau cache mungkin akan lebih baik?
oh ok, "cache" baik-baik saja.
Kira saya sudah selesai dengan PR testbed. Silakan lihat juga komentar PR untuk mendapatkan latar belakang ringkasan kasar berikut.
Usul:
AttributeCache
untuk menampung semua yang diperlukan untuk menata satu sel terminal. Lihat #1528 untuk versi penghitungan ref awal yang juga dapat menyimpan spesifikasi warna sebenarnya. Cache juga dapat dibagikan di antara instance terminal yang berbeda jika diperlukan untuk menyimpan memori lebih lanjut di beberapa aplikasi terminal.StringStorage
untuk menampung string data konten terminal pendek. Versi di #1530 bahkan menghindari penyimpanan string char tunggal dengan "membebani" arti pointer. wcwidth
harus dipindahkan ke sini.CharData
dari [number, string, number, number]
menjadi [number, number]
, di mana angkanya adalah pointer (nomor indeks) ke:AttributeCache
entriStringStorage
entriAtribut tidak mungkin banyak berubah, jadi satu nomor 32bit akan menghemat banyak memori dari waktu ke waktu. Penunjuk StringStorage
adalah titik kode unicode nyata untuk karakter tunggal, oleh karena itu dapat digunakan sebagai entri code
dari CharData
. String sebenarnya dapat diakses oleh StringStorage.getString(idx)
. Bidang keempat wcwidth
dari CharData
dapat diakses oleh StringStorage.wcwidth(idx)
(belum diimplementasikan). Hampir tidak ada hukuman runtime dari menyingkirkan code
dan wcwidth
di CharData
(diuji di #1529).
CharData
menjadi padat Int32Array
pelaksanaan penyangga berdasarkan. Juga diuji di #1530 dengan kelas rintisan (jauh dari berfungsi penuh), manfaat akhir kemungkinan adalah:ls -lR /usr/lib
turun menjadi 1,3 detik (master pada 2,1 detik) sementara buffer lama masih aktif untuk penanganan kursor, setelah dihapus, saya berharap runtime turun di bawah 1 detikKelemahan - langkah 4 cukup banyak bekerja karena akan memerlukan beberapa pengerjaan ulang pada antarmuka buffer. Tapi hei - untuk menghemat 80% RAM dan tetap mendapatkan kinerja runtime, itu bukan masalah besar, bukan? :senyum:
Ada masalah lain yang saya temukan - representasi sel kosong saat ini. Imho sel dapat memiliki 3 status:
blankLine
dan eraseChar
, tetapi dengan spasi sebagai konten.Masalah yang saya lihat di sini adalah bahwa sel kosong tidak dapat dibedakan dari sel normal dengan spasi yang dimasukkan, keduanya terlihat sama pada tingkat buffer (konten yang sama, lebar yang sama). Saya tidak menulis kode penyaji/output apa pun tetapi saya berharap ini mengarah pada situasi canggung di bagian depan output. khususnya penanganan ujung kanan garis mungkin menjadi rumit.
Terminal dengan 15 cols, pertama beberapa output string, yang dililitkan:
1: 'H', 'e', 'l', 'l', 'o', ' ', 't', 'e', 'r', 'm', 'i', 'n', 'a', 'l', ' '
2: 'w', 'o', 'r', 'l', 'd', '!', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '
versus daftar folder dengan ls
:
1: 'R', 'e', 'a', 'd', 'm', 'e', '.', 'm', 'd', ' ', ' ', ' ', ' ', ' ', ' '
2: 'f', 'i', 'l', 'e', 'A', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '
Contoh pertama berisi spasi nyata setelah kata 'terminal', contoh kedua tidak pernah menyentuh sel setelah 'Readme.md'. Cara itu diwakili pada tingkat buffer masuk akal untuk kasus standar untuk mencetak barang-barang sebagai output terminal ke layar (ruangan perlu diambil lagian), tetapi untuk alat yang mencoba menangani string konten seperti pemilihan mouse atau manajer reflow tidak jelas lagi, dari mana ruang itu berasal.
Kurang lebih ini mengarah ke pertanyaan berikutnya - bagaimana menentukan panjang konten aktual dalam satu baris (jumlah sel yang berisi sesuatu dari sisi kiri)? Pendekatan sederhana akan menghitung sel kosong dari sisi kanan, sekali lagi makna ganda dari atas membuat ini sulit untuk ditentukan.
Usul:
Imho ini mudah diperbaiki dengan menggunakan beberapa placeholder lain untuk sel kosong, misalnya karakter kontrol atau string kosong dan menggantinya dalam proses render jika diperlukan. Mungkin penyaji layar juga bisa mendapatkan keuntungan dari ini, karena mungkin tidak harus menangani sel-sel itu sama sekali (tergantung pada cara output dihasilkan).
Btw, untuk string yang dibungkus di atas ini juga mengarah ke masalah isWrapped
, yang penting untuk mengubah ukuran atau penanganan pemilihan salin&tempel yang benar. Imho kita tidak bisa menghapusnya, tetapi perlu mengintegrasikannya lebih baik daripada atm.
@jerch kerja yang mengesankan! :senyum:
1 Bangun AttributeCache untuk menampung semua yang diperlukan untuk menata satu sel terminal. Lihat #1528 untuk versi penghitungan ref awal yang juga dapat menyimpan spesifikasi warna sebenarnya. Cache juga dapat dibagikan di antara instance terminal yang berbeda jika diperlukan untuk menyimpan memori lebih lanjut di beberapa aplikasi terminal.
Membuat beberapa komentar di #1528.
2 Bangun StringStorage untuk menampung string data konten terminal pendek. Versi di #1530 bahkan menghindari penyimpanan string char tunggal dengan "membebani" arti pointer. wcwidth harus dipindahkan ke sini.
Membuat beberapa komentar di #1530.
4 Pindahkan CharData yang menyusut ke dalam implementasi buffer berbasis Int32Array yang padat. Juga diuji di #1530 dengan kelas rintisan (jauh dari berfungsi penuh), manfaat akhir kemungkinan adalah:
Belum sepenuhnya terjual pada ide ini, saya pikir itu akan menggigit kita ketika kita menerapkan reflow. Tampaknya masing-masing langkah ini dapat dilakukan secara berurutan sehingga kita dapat melihat bagaimana keadaan berjalan dan melihat apakah masuk akal untuk melakukan ini setelah kita menyelesaikan 3 langkah.
Ada masalah lain yang saya temukan - representasi sel kosong saat ini. Imho sel dapat memiliki 3 status
Berikut ini contoh bug yang keluar dari https://github.com/xtermjs/xterm.js/issues/1286 , :+1: untuk membedakan sel spasi putih dan sel "kosong"
Btw, untuk string yang dibungkus di atas ini juga mengarah ke masalah isWrapped, yang penting untuk mengubah ukuran atau penanganan pemilihan salin&tempel yang benar. Imho kita tidak bisa menghapusnya, tetapi perlu mengintegrasikannya lebih baik daripada atm.
Saya melihat isWrapped
akan hilang ketika kami menangani https://github.com/xtermjs/xterm.js/issues/622 karena CircularList hanya akan berisi baris yang tidak terbungkus.
Belum sepenuhnya terjual pada ide ini, saya pikir itu akan menggigit kita ketika kita menerapkan reflow. Tampaknya masing-masing langkah ini dapat dilakukan secara berurutan sehingga kita dapat melihat bagaimana keadaan berjalan dan melihat apakah masuk akal untuk melakukan ini setelah kita menyelesaikan 3 langkah.
Ya, saya bersamamu (masih menyenangkan untuk bermain-main dengan pendekatan yang sama sekali berbeda ini). 1 dan 2 dapat dipilih, 3 dapat diterapkan tergantung pada 1 atau 2. 4 adalah opsional, kita dapat tetap berpegang pada tata letak buffer saat ini. Penghematan memori seperti ini:
CircularList
: menghemat 50% (~2.8 MB dari ~5.5 MB)1. sangat mudah diimplementasikan, perilaku memori dengan scrollBack yang lebih besar masih akan menunjukkan penskalaan yang buruk seperti yang ditunjukkan di sini https://github.com/xtermjs/xterm.js/pull/1530#issuecomment -403542479 tetapi pada tingkat yang kurang beracun
2. Sedikit lebih sulit untuk diterapkan (beberapa tipuan lagi di tingkat baris diperlukan), tetapi akan memungkinkan untuk menjaga API yang lebih tinggi dari Buffer
tetap utuh. Imho pilihan untuk pergi - simpan mem besar dan masih mudah untuk diintegrasikan.
3. 5% lebih banyak mem save daripada opsi 2, sulit diterapkan, akan mengubah menyentuh semua API dan dengan demikian secara harfiah seluruh basis kode. Imho lebih dari kepentingan akademis atau untuk hari-hari hujan yang membosankan untuk diterapkan lol.
@Tyriar Saya melakukan beberapa tes lebih lanjut dengan rust untuk penggunaan webassembly dan menulis ulang parser. Perhatikan bahwa keterampilan karat saya agak "berkarat" karena saya belum mendalaminya, oleh karena itu berikut ini mungkin hasil dari kode karat yang lemah. Hasil:
Kecuali jika kita ingin menulis ulang seluruh lib inti dalam rust (atau bahasa lain yang mampu wasm), kita tidak dapat memperoleh apa pun dari pindah ke wasm lang imho. Kelebihan dari wasm langs saat ini adalah kenyataan bahwa sebagian besar mendukung penanganan memori eksplisit (dapat membantu kami dengan masalah buffer), kerugiannya adalah pengenalan bahasa yang sama sekali berbeda ke dalam proyek yang berfokus pada TS/JS (penghalang tinggi untuk penambahan kode) dan biaya translasi antara wasm dan JS land.
TL;DR
xterm.js adalah untuk secara luas ke hal-hal JS umum seperti DOM dan acara untuk mendapatkan apa pun dari webassembly bahkan untuk menulis ulang bagian inti.
@jerch investigasi yang bagus :smiley:
Panggilan dari JS ke wasm membuat beberapa overhead dan memakan semua manfaat dari atas. Bahkan itu ~ 20% lebih lambat.
Ini adalah masalah utama untuk monaco yang menjadi asli juga yang terutama menginformasikan pendirian saya (meskipun itu dengan modul simpul asli, bukan wasm). Saya percaya bekerja dengan ArrayBuffer
s di mana pun memungkinkan akan memberi kita keseimbangan terbaik antara kinerja dan kesederhanaan (mudah impl, penghalang masuk).
@Tyriar Akan mencoba membuat AttributeStorage untuk menyimpan data RGB. Belum yakin tentang BST, untuk kasus penggunaan tipikal dengan hanya beberapa pengaturan warna dalam sesi terminal, ini akan menjadi lebih buruk di runtime, mungkin ini harus menjadi runtime drop-in setelah warna melampaui batas yang diberikan. Juga konsumsi memori akan meningkat banyak lagi, meskipun masih akan menghemat memori karena atribut disimpan hanya sekali dan tidak bersama dengan setiap sel (skenario terburuk dengan setiap sel yang memiliki atribut berbeda akan menderita).
Tahukah Anda mengapa nilai warna fg
dan bg
256 warna saat ini berbasis 9 bit, bukan 8 bit? Untuk apa bit tambahan digunakan? Di sini: https://github.com/xtermjs/xterm.js/blob/6691f809069a549b4808cd2e055398d2da15db37/src/InputHandler.ts#L1596
Bisakah Anda memberi saya tata letak bit saat ini attr
? Saya pikir pendekatan serupa seperti "makna ganda" untuk pointer StringStorage dapat lebih menghemat memori tetapi itu akan membutuhkan MSB attr
untuk dicadangkan untuk perbedaan pointer dan tidak digunakan untuk tujuan lain. Ini mungkin membatasi kemungkinan untuk mendukung flag atribut lebih lanjut di kemudian hari (karena FLAGS
sudah menggunakan 7 bit), apakah kita masih kehilangan beberapa flag fundamental yang kemungkinan akan datang?
Angka 32 bit attr
dalam istilah buffer dapat dikemas seperti ini:
# 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)
Dengan cara ini penyimpanan hanya perlu menyimpan data RGB dalam dua angka 32 bit sementara flag dapat tetap berada di angka attr
.
@jerch ngomong -ngomong saya mengirimi Anda email, mungkin dimakan oleh filter spam lagi
Tahukah Anda mengapa nilai fg dan bg 256 warna saat ini berbasis 9 bit, bukan 8 bit? Untuk apa bit tambahan digunakan?
Saya pikir ini digunakan untuk warna fg/bg default (yang bisa gelap atau terang), jadi sebenarnya 257 warna.
https://github.com/xtermjs/xterm.js/pull/756/files
Bisakah Anda memberi saya sedikit tata letak attr saat ini?
Saya pikir ini:
19+: flags (see `FLAGS` enum)
18..18: default fg flag
17..10: 256 fg
9..9: default bg flag
8..1: 256 bg
Anda dapat melihat apa yang saya dapatkan untuk truecolor di PR lama 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];
Jadi dalam hal ini saya memiliki 2 bendera; satu untuk warna default (apakah mengabaikan semua bit warna) dan satu untuk warna asli (apakah akan melakukan 256 atau 16 juta warna).
Ini mungkin membatasi kemungkinan untuk mendukung flag atribut lebih lanjut di kemudian hari (karena FLAGS sudah menggunakan 7 bit), apakah kita masih kehilangan beberapa flag fundamental yang kemungkinan akan datang?
Ya, kami menginginkan ruang untuk flag tambahan, misalnya https://github.com/xtermjs/xterm.js/issues/580, https://github.com/xtermjs/xterm.js/issues/1145, saya akan katakan setidaknya tinggalkan> 3 bit jika memungkinkan.
Alih-alih data penunjuk di dalam attr itu sendiri, mungkin ada peta lain yang menyimpan referensi ke data rgb? mapAttrIdxToRgb: { [idx: number]: RgbData
@Tyriar Maaf, beberapa hari tidak online dan saya khawatir emailnya dimakan oleh filter spam. Bisa tolong kirim ulang? :merah:
Dimainkan sedikit dengan struktur data pencarian yang lebih pintar untuk penyimpanan attrs. Yang paling menjanjikan mengenai ruang dan runtime search/insert adalah tree dan skiplist sebagai alternatif yang lebih murah. Secara teori loh. Dalam praktiknya, keduanya tidak dapat mengungguli pencarian array sederhana saya yang tampaknya sangat aneh bagi saya (bug dalam kode di suatu tempat?)
Saya mengunggah file uji di sini https://Gist.github.com/jerch/ff65f3fb4414ff8ac84a947b3a1eec58 dengan array vs. pohon merah-hitam yang condong ke kiri, yang menguji hingga 10 juta entri (yang hampir merupakan ruang pengalamatan lengkap). Masih jauh di depan dibandingkan dengan LLRB, saya menduga titik impasnya sekitar 10 juta. Diuji pada laptop lama saya yang berusia 7 tahun, mungkin seseorang dapat mengujinya juga dan bahkan lebih baik - arahkan saya ke beberapa bug di impl/tes.
Berikut adalah beberapa hasil (dengan angka yang berjalan):
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
Apa yang benar-benar mengejutkan saya adalah kenyataan bahwa pencarian array linier tidak menunjukkan pertumbuhan di daerah yang lebih rendah sama sekali, hingga 10k entri stabil pada ~ 4ms (mungkin terkait cache). Tes 10M menunjukkan runtime yang lebih buruk dari yang diharapkan, mungkin karena mem paging apa pun. Mungkin JS jauh dari mesin dengan JIT dan semua opts/deopts terjadi, tetap saja saya pikir mereka tidak dapat menghilangkan langkah kompleksitas (meskipun LLRB tampaknya berat pada satu _n_ sehingga memindahkan titik impas untuk O( n) vs. O(logn) ke atas)
Btw dengan data acak perbedaannya bahkan lebih buruk.
Saya pikir ini digunakan untuk warna fg/bg default (yang bisa gelap atau terang), jadi sebenarnya 257 warna.
Jadi ini membedakan SGR 39
atau SGR 49
dari salah satu dari 8 warna palet?
Alih-alih data penunjuk di dalam attr itu sendiri, mungkin ada peta lain yang menyimpan referensi ke data rgb? mapAttrIdxToRgb: { [idx: nomor]: RgbData
Ini akan memperkenalkan tipuan lain dengan penggunaan memori tambahan. Dengan pengujian di atas saya juga menguji perbedaan antara selalu memegang bendera di attrs vs menyimpannya bersama dengan data RGB di penyimpanan. Karena perbedaannya adalah ~ 0,5 ms untuk entri 1M, saya tidak akan menggunakan pengaturan attrs yang rumit ini, alih-alih menyalin flag ke penyimpanan setelah RGB diatur. Masih saya akan memilih perbedaan bit ke-32 antara attrs langsung vs pointer karena ini akan menghindari penyimpanan sama sekali untuk sel non RGB.
Juga saya pikir 8 warna palet default untuk fg/bg tidak cukup terwakili dalam buffer saat ini. Secara teori terminal harus mendukung mode warna berikut:
SGR 39
+ SGR 49
warna default untuk fg/bg (dapat disesuaikan)SGR 30-37
+ SGR 40-47
8 palet warna rendah untuk fg/bg (dapat disesuaikan)SGR 90-97
+ SGR 100-107
8 palet warna tinggi untuk fg/bg (dapat disesuaikan)SGR 38;5;n
+ SGR 48;5;n
256 palet terindeks untuk fg/bg (dapat disesuaikan)SGR 38;2;r;g;b
+ SGR 48;2;r;g;b
RGB untuk fg/bg (tidak dapat disesuaikan)Opsi 2.) dan 3.) dapat digabungkan menjadi satu byte (memperlakukannya sebagai satu palet 16 warna fg/bg), 4.) membutuhkan 2 byte dan 5.) akhirnya akan membutuhkan 6 byte lagi. Kami masih membutuhkan beberapa bit untuk menunjukkan mode warna.
Untuk mencerminkan hal ini pada level buffer, kita memerlukan yang berikut:
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
Jadi kita membutuhkan 30 bit dari angka 32 bit, meninggalkan 2 bit gratis untuk keperluan lain. Bit ke-32 dapat menahan penunjuk vs. flag attr langsung menghilangkan penyimpanan untuk sel non RGB.
Saya juga menyarankan untuk membungkus akses attr ke dalam kelas yang nyaman untuk tidak mengekspos detail implementasi ke luar (lihat file pengujian di atas, ada versi awal dari kelas TextAttributes
untuk mencapai ini).
Maaf, beberapa hari tidak online dan saya khawatir emailnya dimakan oleh filter spam. Bisa tolong kirim ulang?
Membenci
Oh btw angka-angka di atas untuk pencarian array vs llrb adalah omong kosong - saya pikir itu dimanjakan oleh pengoptimal yang melakukan beberapa hal aneh di for loop. Dengan pengaturan pengujian yang sedikit berbeda, ini dengan jelas menunjukkan O(n) vs. O(log n) tumbuh jauh lebih awal (dengan 1000 elemen yang telah diisi sebelumnya sudah lebih cepat dengan pohon).
Kondisi saat ini:
Setelah:
Salah satu optimasi yang cukup sederhana adalah dengan meratakan array-of-array menjadi satu array. Yaitu alih-alih BufferLine
dari _N_ kolom yang memiliki array _data
dari _N_ CharData
sel, di mana setiap CharData
adalah array 4, hanya memiliki satu array elemen _4*N_. Ini menghilangkan overhead objek dari array _N_. Ini juga meningkatkan lokalitas cache, jadi seharusnya lebih cepat. Kerugiannya adalah kode yang sedikit lebih rumit dan lebih buruk, tetapi tampaknya sepadan.
Sebagai tindak lanjut dari komentar saya sebelumnya, tampaknya perlu dipertimbangkan untuk menggunakan sejumlah variabel elemen dalam array _data
untuk setiap sel. Dengan kata lain representasi stateful. Perubahan acak dalam posisi akan lebih mahal, tetapi pemindaian linier dari awal baris bisa sangat cepat, terutama karena array sederhana dioptimalkan untuk lokalitas cache. Output sekuensial tipikal akan cepat, seperti halnya rendering.
Selain mengurangi ruang, keuntungan dari sejumlah variabel elemen per sel adalah peningkatan fleksibilitas: Atribut ekstra (seperti warna 24-bit), anotasi untuk sel atau rentang tertentu, mesin terbang atau
elemen DOM bersarang.
@PerBothner Terima kasih atas ide-ide Anda! Ya saya sudah menguji tata letak array padat tunggal dengan aritmatika pointer, ini menunjukkan pemanfaatan memori terbaik. Masalah muncul ketika mengubah ukuran, itu pada dasarnya berarti membangun kembali seluruh potongan memori (salin) atau menyalin cepat ke potongan yang lebih besar dan menyelaraskan kembali bagian. Ini cukup exp. dan saya tidak dibenarkan oleh penghematan mem (diuji di beberapa PR taman bermain yang tercantum di atas, penghematannya sekitar ~ 10% dibandingkan dengan implementasi garis buffer baru).
Tentang komentar kedua Anda - kami sudah membahasnya karena akan membuat penanganan garis yang dibungkus lebih mudah. Untuk saat ini kami memutuskan untuk menggunakan pendekatan baris X col untuk tata letak buffer baru dan menyelesaikannya terlebih dahulu. Saya pikir kita harus mengatasi ini lagi setelah kita melakukan implementasi reflow resize.
Tentang menambahkan hal-hal tambahan ke buffer: saat ini kami melakukan di sini apa yang dilakukan kebanyakan terminal lain - gerak maju kursor ditentukan oleh wcwidth
yang memastikan untuk tetap kompatibel dengan ide pty/termios bagaimana data harus ditata. Ini pada dasarnya berarti bahwa kami menangani pada level buffer hanya hal-hal seperti pasangan pengganti dan menggabungkan karakter. Aturan bergabung "tingkat lebih tinggi" lainnya dapat diterapkan oleh penggabung karakter di penyaji (saat ini digunakan oleh https://github.com/xtermjs/xterm-addon-ligatures untuk ligatur). Saya memiliki PR terbuka untuk juga mendukung grafem unicode pada tingkat buffer awal tetapi saya pikir kami tidak dapat melakukan ini pada tahap itu karena sebagian besar backend pty tidak memiliki gagasan tentang ini (apakah ada sama sekali?) dan kami akan berakhir dengan konglomerat char yang aneh . Hal yang sama berlaku untuk dukungan BIDI nyata, saya pikir grafem dan BIDI lebih baik dilakukan pada tahap penyaji untuk menjaga pergerakan kursor/sel tetap utuh.
Dukungan untuk node DOM yang dilampirkan ke sel terdengar sangat menarik, saya suka ide itu. Saat ini itu tidak mungkin dalam pendekatan langsung karena kami memiliki backend perender yang berbeda (DOM, kanvas 2D, dan perender webgl baru yang mengkilap), saya pikir ini masih dapat dicapai untuk semua perender dengan memposisikan overlay di tempat yang tidak didukung secara asli (hanya perender DOM yang akan melakukannya dapat melakukannya secara langsung). Kami akan membutuhkan semacam API pada tingkat buffer untuk mengumumkan hal itu dan ukurannya dan penyaji dapat melakukan pekerjaan kotor. Saya pikir kita harus mendiskusikan / melacak ini dengan masalah terpisah.
Terima kasih atas tanggapan terperinci Anda.
_"Masalah muncul ketika mengubah ukuran, itu pada dasarnya berarti membangun kembali seluruh potongan memori (salin) atau menyalin cepat ke potongan yang lebih besar dan menyelaraskan kembali bagian."_
Maksud Anda: saat mengubah ukuran, kita harus menyalin elemen _4*N_ daripada hanya elemen _N_?
Mungkin masuk akal jika array berisi semua sel untuk baris logis (tidak terbungkus). Misalnya, asumsikan garis 180 karakter dan terminal lebar 80 kolom. Dalam hal ini Anda dapat memiliki 3 instance BufferLine
semuanya berbagi buffer _4*180_-element _data
, tetapi setiap BufferLine
juga akan berisi offset awal.
Yah saya memiliki semuanya dalam satu array besar yang dibangun oleh [cols] x [rows] x [needed single cell space]
. Jadi pada dasarnya masih berfungsi sebagai "kanvas" dengan tinggi dan lebar tertentu. Ini benar-benar hemat memori dan cepat untuk aliran input normal, tetapi segera setelah insertCell
/ deleteCell
dipanggil (pengubahan ukuran akan melakukannya), seluruh memori di belakang posisi tempat tindakan berlangsung harus digeser. Untuk scrollback kecil (<10k) ini bahkan bukan masalah, ini benar-benar merupakan showstopper untuk> 100k baris.
Perhatikan impl array yang diketik saat ini masih harus melakukan perubahan itu, tetapi kurang beracun karena hanya perlu memindahkan konten memori hingga akhir baris.
Saya memikirkan tata letak yang berbeda untuk menghindari pergeseran yang mahal, bidang utama untuk menghemat pergeseran memori yang tidak masuk akal adalah dengan benar-benar memisahkan scrollback dari "baris terminal panas" (yang terbaru hingga terminal.rows
) karena hanya itu yang bisa diubah oleh kursor melompat dan menyisipkan/menghapus.
Berbagi memori yang mendasarinya dengan beberapa objek garis penyangga adalah ide yang menarik untuk memecahkan masalah pembungkus. Belum yakin bagaimana ini dapat bekerja dengan andal tanpa memperkenalkan penanganan ref eksplisit dan semacamnya. Di versi lain saya mencoba melakukan semuanya dengan penanganan memori eksplisit, tetapi penghitung ref adalah penghenti nyata dan merasa salah di tanah GC. (lihat #1633 untuk primitif)
Sunting: Btw penanganan memori eksplisit setara dengan pendekatan "memori per baris" saat ini, saya berharap kinerja yang lebih baik karena lokalitas cache yang lebih baik, kira itu dimakan oleh exp yang sedikit lebih banyak. mem penanganan dalam abstraksi JS.
Dukungan untuk node DOM yang dilampirkan ke sel terdengar sangat menarik, saya suka ide itu. Saat ini itu tidak mungkin dalam pendekatan langsung karena kami memiliki backend perender yang berbeda (DOM, kanvas 2D, dan perender webgl baru yang mengkilap), saya pikir ini masih dapat dicapai untuk semua perender dengan memposisikan overlay di tempat yang tidak didukung secara asli (hanya perender DOM yang akan melakukannya dapat melakukannya secara langsung).
Ini agak keluar dari topik tapi saya melihat kami akhirnya memiliki node DOM yang terkait dengan sel di dalam viewport yang akan bertindak mirip dengan lapisan render kanvas. Dengan begitu konsumen akan dapat "menghias" sel menggunakan HTML dan CSS dan tidak perlu masuk ke kanvas API.
Mungkin masuk akal jika array berisi semua sel untuk baris logis (tidak terbungkus). Misalnya, asumsikan garis 180 karakter dan terminal lebar 80 kolom. Dalam hal ini Anda dapat memiliki 3 instance BufferLine yang semuanya berbagi buffer _data 4*180-elemen yang sama, tetapi setiap BufferLine juga akan berisi offset awal.
Rencana reflow yang disebutkan di atas ditangkap di https://github.com/xtermjs/xterm.js/issues/622#issuecomment -375403572 , pada dasarnya kami ingin memiliki buffer yang sebenarnya tidak terbungkus dan kemudian tampilan di atas yang mengelola baris baru untuk akses cepat ke baris mana pun (juga mengoptimalkan pengubahan ukuran horizontal).
Menggunakan pendekatan array padat mungkin sesuatu yang bisa kita lihat, tetapi sepertinya itu tidak sepadan dengan biaya tambahan dalam mengelola jeda baris yang tidak terbungkus dalam array seperti itu, dan kekacauan yang muncul ketika baris dipangkas dari atas penyangga gulir balik. Terlepas dari itu, saya tidak berpikir kita harus melihat perubahan seperti itu sampai #791 selesai dan kita melihat #622.
Dengan PR #1796 parser mendapat dukungan array yang diketik yang membuka pintu untuk optimasi lebih lanjut serveral, di sisi lain juga untuk pengkodean input lainnya.
Untuk saat ini saya memutuskan untuk menggunakan Uint16Array
, karena mudah bolak-balik dapat dikonversi dengan string JS. Ini pada dasarnya membatasi permainan ke UCS2/UTF16, sedangkan parser dalam versi saat ini juga dapat menangani UTF32 (UTF8 tidak didukung). Buffer terminal berbasis array yang diketik saat ini ditata untuk UTF32, konversi UTF16 --> UTF32 dilakukan dalam InputHandler.print
. Dari sini ada beberapa arah yang mungkin:
wcwidth
kompatibel dengan UTF16Tentang UTF8: Pengurai saat ini tidak dapat menangani urutan UTF8 asli, terutama karena fakta bahwa karakter perantara berbenturan dengan karakter kontrol C1. Juga UTF8 membutuhkan penanganan aliran yang tepat dengan status perantara tambahan, itu buruk dan tidak boleh ditambahkan ke parser. UTF8 akan lebih baik ditangani sebelumnya, dan mungkin dengan hak konversi ke UTF32 untuk penanganan codepoint yang lebih mudah di semua tempat.
Mengenai kemungkinan pengkodean input UTF8 dan tata letak buffer internal, saya melakukan tes kasar. Untuk mengesampingkan dampak yang jauh lebih tinggi dari perender kanvas pada total runtime, saya melakukannya dengan perender webgl yang akan datang. Dengan patokan ls -lR /usr/lib
saya, saya mendapatkan hasil berikut:
master + perender webgl saat ini:
cabang taman bermain, menerapkan #1796, bagian dari #1811 dan penyaji webgl:
Cabang taman bermain melakukan konversi awal dari UTF8 ke UTF32 sebelum melakukan penguraian dan penyimpanan (konversi menambahkan ~ 30 ms). Percepatan terutama diperoleh oleh 2 fungsi hot selama aliran input, EscapeSequenceParser.parse
(120 md vs. 35 md) dan InputHandler.print
(350 md vs. 75 md). Keduanya mendapat banyak manfaat dari sakelar array yang diketik dengan menghemat panggilan .charCodeAt
.
Saya juga membandingkan hasil ini dengan array tipe menengah UTF16 - EscapeSequenceParser.parse
sedikit lebih cepat (~ 25 ms) tetapi InputHandler.print
tertinggal karena pasangan pengganti yang diperlukan dan pencarian codepoint di wcwidth
(120 md).
Perhatikan juga bahwa saya sudah pada batas sistem dapat menyediakan data ls
(i7 dengan SSD) - percepatan yang diperoleh menambah waktu idle alih-alih membuat proses lebih cepat.
Ringkasan:
Imho penanganan input tercepat yang bisa kita dapatkan adalah campuran transport UTF8 + UTF32 untuk representasi buffer. Sementara transport UTF8 memiliki kecepatan paket byte terbaik untuk input terminal tipikal dan menghilangkan konversi yang tidak masuk akal dari pty melalui beberapa lapisan buffer hingga Terminal.write
, buffer berbasis UTF32 dapat menyimpan data dengan cukup cepat. Yang terakhir hadir dengan jejak memori yang sedikit lebih tinggi daripada UTF16 sementara UTF16 sedikit lebih lambat karena penanganan karakter yang lebih rumit dengan lebih banyak tipuan.
Kesimpulan:
Kita harus menggunakan tata letak buffer berbasis UTF32 untuk saat ini. Kami juga harus mempertimbangkan untuk beralih ke pengkodean input UTF8, tetapi ini masih membutuhkan pemikiran lebih lanjut tentang perubahan API dan implikasinya untuk integrator (sepertinya mekanisme ipc elektron tidak dapat menangani data biner tanpa penyandian BASE64 dan pembungkusan JSON, yang akan melawan upaya perf).
Tata letak penyangga untuk dukungan warna sejati yang akan datang:
Saat ini tata letak buffer berbasis array yang diketik adalah sebagai berikut (satu sel):
| uint32_t | uint32_t | uint32_t |
| attrs | codepoint | wcwidth |
di mana attrs
berisi semua flag yang dibutuhkan + warna FG dan BG berbasis 9 bit. codepoint
menggunakan 21 bit (maks. adalah 0x10FFFF untuk UTF32) + 1 bit untuk menunjukkan penggabungan karakter dan wcwidth
2 bit (berkisar dari 0-2).
Idenya adalah mengatur ulang bit ke paket yang lebih baik untuk memberi ruang bagi nilai RGB tambahan:
wcwidth
ke bit tinggi codepoint
| 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) |
Keuntungan dari pendekatan ini adalah akses yang relatif murah ke setiap nilai dengan satu akses indeks dan maks. Operasi 2 bit (dan/atau + shift).
Jejak memori stabil untuk varian saat ini, tetapi masih cukup tinggi dengan 12 byte per sel. Ini dapat lebih dioptimalkan dengan mengorbankan beberapa runtime dengan beralih ke UTF16 dan tipuan attr
:
| uint16_t | uint16_t |
| BMP codepoint(16) | comb(1) wcwidth(2) attr pointer(13) |
Sekarang kita turun ke 4 byte per sel + beberapa ruang untuk attrs. Sekarang attrs dapat didaur ulang untuk sel lain juga. Yay misi tercapai! - Eh, sebentar...
Membandingkan jejak memori, pendekatan kedua jelas menang. Tidak demikian untuk runtime, ada tiga faktor utama yang meningkatkan banyak runtime:
Keseksian pendekatan kedua adalah penghematan memori tambahan. Oleh karena itu saya mengujinya dengan cabang taman bermain (lihat komentar di atas) dengan implementasi BufferLine
dimodifikasi:
Ya, kami agak kembali ke tempat kami memulai sebelum mengubah ke array yang diketik UTF8 + di parser. Penggunaan memori turun dari ~ 1,5 MB menjadi ~ 0,7 MB (aplikasi demo dengan 87 sel dan 1000 baris scrollback).
Dari sini masalah penghematan memori vs kecepatan. Karena kami telah menghemat banyak memori dengan beralih dari array js ke array yang diketik (turun dari ~ 5,6 MB menjadi ~ 1,5 MB untuk tumpukan C++, memotong perilaku Tumpukan JS dan GC yang beracun) saya pikir kita harus pergi ke sini dengan varian yang lebih cepat. Setelah penggunaan memori mendapat masalah mendesak lagi, kami masih dapat beralih ke tata letak buffer yang lebih ringkas seperti yang dijelaskan dalam pendekatan kedua di sini.
Saya setuju, mari kita optimalkan kecepatan selama konsumsi memori tidak menjadi masalah. Saya juga ingin menghindari tipuan sejauh mungkin karena membuat kode lebih sulit untuk dibaca dan dipelihara. Kami sudah memiliki cukup banyak konsep dan penyesuaian dalam basis kode kami yang mempersulit orang (termasuk saya ) untuk mengikuti alur kode - dan membawa lebih banyak dari ini harus selalu dibenarkan dengan alasan yang sangat bagus. IMO menyimpan megabyte memori lain tidak membenarkannya.
Namun demikian, saya sangat menikmati membaca dan belajar dari latihan Anda, terima kasih telah membagikannya dengan sangat detail!
@mofux Ya itu benar - kompleksitas kode jauh lebih tinggi (pengganti UTF16 baca dulu, kals codepoint menengah, wadah pohon dengan ref menghitung entri attr).
Dan karena tata letak 32 bit sebagian besar adalah memori datar (hanya menggabungkan karakter yang membutuhkan tipuan), ada lebih banyak pengoptimalan yang mungkin (juga bagian dari #1811, belum diuji untuk penyaji).
Ada satu keuntungan besar dari mengarahkan ke objek attr: Ini jauh lebih dapat diperluas. Anda dapat menambahkan anotasi, glyph, atau aturan melukis khusus. Anda dapat menyimpan informasi tautan dengan cara yang mungkin lebih bersih dan efisien. Mungkin mendefinisikan antarmuka ICellPainter
yang mengetahui cara merender selnya, dan Anda juga dapat menggantungkan properti khusus.
Satu ide adalah menggunakan dua larik per BufferLine: larik Uint32Array, dan ICellPainter, dengan masing-masing satu elemen untuk setiap sel. ICellPainter saat ini adalah properti dari status parser, jadi Anda cukup menggunakan kembali ICellPainter yang sama selama status warna/atribut tidak berubah. Jika Anda perlu menambahkan properti khusus ke sel, pertama-tama Anda mengkloning ICellPainter (jika mungkin dibagikan).
Anda dapat melakukan pra-alokasi ICellPainter untuk kombinasi warna/atribut yang paling umum - setidaknya memiliki objek unik yang sesuai dengan warna/atribut default.
Perubahan gaya (seperti mengubah warna latar depan/latar belakang default) dapat diterapkan hanya dengan memperbarui instance ICellPainter yang sesuai, tanpa harus memperbarui setiap sel.
Ada kemungkinan pengoptimalan: Misalnya, gunakan instance ICellPainter yang berbeda untuk karakter lebar tunggal dan lebar ganda (atau karakter lebar nol). (Itu menghemat 2 bit di setiap elemen Uint32Array.) Ada 11 bit atribut yang tersedia di Uint32Array (lebih banyak jika kita mengoptimalkan karakter BMP). Ini dapat digunakan untuk mengkodekan kombinasi warna/atribut yang paling umum/berguna, yang dapat digunakan untuk mengindeks instance ICellPainter yang paling umum. Jika demikian, array ICellPainter dapat dialokasikan dengan malas - yaitu hanya jika beberapa sel dalam baris memerlukan ICellPainter yang "kurang umum".
Seseorang juga dapat menghapus array _combined untuk karakter non-BMP, dan menyimpannya di ICellPainter. (Itu membutuhkan ICellPainter unik untuk setiap karakter non-BMP, jadi ada tradeoff di sini.)
@PerBothner Ya tipuan lebih fleksibel dan karenanya lebih cocok untuk ekstra yang tidak biasa. Tetapi karena mereka jarang, saya ingin tidak mengoptimalkannya sejak awal.
Beberapa catatan tentang apa yang saya coba di beberapa testbeds:
codepoint
memiliki bit gabungan yang disetel (akses lebih cepat di sini karena lokalitas cache yang lebih baik, diuji dengan valgrind). Dibawa ke JS, peningkatan kecepatan tidak terlalu bagus karena konversi string ke angka yang diperlukan (meskipun masih lebih cepat), tetapi penghematan mem bahkan lebih besar (tebak karena beberapa ruang manajemen tambahan untuk tipe JS). Masalahnya adalah StringStorage
global untuk hal-hal yang digabungkan dengan manajemen memori eksplisit, antipattern besar di JS. Perbaikan cepat untuk itu adalah objek _combined
, yang mendelegasikan pembersihan ke GC. Ini masih dapat diubah dan btw dimaksudkan untuk menyimpan konten string terkait sel sewenang-wenang (melakukan ini dengan mempertimbangkan grafem, tetapi kami tidak akan segera melihatnya karena tidak didukung oleh backend apa pun). Jadi ini adalah tempat untuk menyimpan konten string tambahan berdasarkan sel demi sel.AttributeStorage
untuk semua attrs yang pernah digunakan di semua instance terminal (lihat https://github.com/jerch/xterm.js/tree/AttributeStorage). Dari segi memori, ini bekerja dengan cukup baik, terutama karena ppl hanya menggunakan satu set kecil atribut bahkan dengan dukungan warna yang sebenarnya. Performanya tidak begitu baik - terutama karena penghitungan ref (setiap sel harus mengintip ke memori asing ini dua kali) dan attr matching. Dan ketika saya mencoba untuk mengadopsi hal ref ke JS rasanya salah - intinya saya menekan tombol "STOP". Di antara itu ternyata kami telah menghemat banyak memori dan panggilan GC dengan beralih ke array yang diketik, sehingga tata letak memori datar yang sedikit lebih mahal dapat melunasi keunggulan kecepatannya di sini.Sekarang tata letak 32 bit datar ini ternyata dioptimalkan untuk hal-hal umum dan tambahan yang tidak biasa tidak dimungkinkan dengannya. Benar. Yah kami masih memiliki penanda (tidak terbiasa dengan mereka jadi saya tidak bisa mengatakan sekarang apa yang mereka mampu), dan ya - masih ada bit gratis di buffer (yang merupakan hal yang baik untuk kebutuhan masa depan, misalnya kita bisa menggunakannya sebagai bendera untuk perlakuan khusus dan semacamnya).
Tbh bagi saya sangat disayangkan bahwa tata letak 16 bit dengan penyimpanan attrs berkinerja buruk, mengurangi separuh penggunaan memori masih merupakan masalah besar (terutama ketika ppl mulai menggunakan garis gulir> 10k), tetapi hukuman runtime dan kompleksitas kode melebihi semakin tinggi mem membutuhkan atm imho.
Bisakah Anda menguraikan ide ICellPainter? Mungkin saya melewatkan beberapa fitur penting sejauh ini.
Tujuan saya untuk DomTerm adalah untuk mengaktifkan dan mendorong interaksi yang lebih kaya seperti yang diaktifkan oleh emulator terminal tradisional. Menggunakan teknologi web memungkinkan banyak hal menarik, jadi sayang jika hanya fokus menjadi emulator terminal tradisional yang cepat. Terutama karena banyak kasus penggunaan untuk xterm.js (seperti REPL untuk IDE) benar-benar dapat mengambil manfaat dari melampaui teks sederhana. Xterm.js bekerja dengan baik di sisi kecepatan (adakah yang mengeluh tentang kecepatan?), tetapi tidak bekerja dengan baik pada fitur (orang - orang mengeluh tentang kehilangan truecolor dan grafik yang disematkan, misalnya). Saya pikir mungkin bermanfaat untuk sedikit lebih fokus pada fleksibilitas dan sedikit lebih sedikit pada kinerja.
_"Bisakah Anda menguraikan ide ICellPainter?"_
Secara umum, ICellPainter merangkum semua data per sel kecuali kode/nilai karakter, yang berasal dari Uint32Array. Itu untuk sel karakter "normal" - untuk gambar yang disematkan dan "kotak" lainnya, kode/nilai karakter mungkin tidak masuk akal.
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;
}
Memetakan sel ke ICellPainter dapat dilakukan dengan berbagai cara. Yang jelas adalah untuk setiap BufferLine memiliki array ICellPainter, tetapi itu membutuhkan pointer 8-byte (setidaknya) per sel. Salah satu kemungkinan adalah menggabungkan array _combined dengan array ICellPainter: Jika IS_COMBINED_BIT_MASK diatur, maka ICellPainter juga menyertakan string gabungan. Optimalisasi lain yang mungkin adalah dengan menggunakan bit yang tersedia di Uint32Array sebagai indeks ke dalam array: Itu menambahkan beberapa komplikasi dan tipuan ekstra, tetapi menghemat ruang.
Saya ingin mendorong kami untuk memeriksa apakah kami dapat melakukannya dengan cara monaco-editor melakukannya (saya pikir mereka menemukan cara yang sangat cerdas dan berkinerja). Alih-alih menyimpan informasi tersebut di buffer, mereka memungkinkan Anda untuk membuat decorations
. Anda membuat dekorasi untuk rentang baris/kolom dan itu akan menempel pada rentang itu:
// 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 }
});
Kemudian penyaji dapat mengambil dekorasi itu dan menggambarnya.
Silakan lihat contoh kecil ini yang menunjukkan bagaimana monaco-editor api terlihat:
https://microsoft.github.io/monaco-editor/playground.html#interacting -with-the-editor-line-and-inline-decorations
Untuk hal-hal seperti merender gambar di dalam terminal monaco menggunakan konsep zona tampilan yang dapat dilihat (di antara konsep lainnya) dalam contoh di sini:
https://microsoft.github.io/monaco-editor/playground.html#interacting -with-the-editor-listening-to-mouse-events
@PerBothner Terima kasih untuk klarifikasi dan sketsanya. Beberapa catatan tentang itu.
Kami akhirnya berencana untuk memindahkan rantai input + buffer ke webworker di masa mendatang. Jadi buffer dimaksudkan untuk beroperasi pada tingkat abstrak dan kami tidak dapat menggunakan hal-hal terkait render/representasi di sana seperti metrik piksel atau node DOM apa pun. Saya melihat kebutuhan Anda untuk ini karena DomTerm sangat dapat disesuaikan, tetapi saya pikir kita harus melakukannya dengan API penanda internal yang ditingkatkan dan dapat belajar di sini dari monaco/vscode (thx for th pointer @mofux).
Saya benar-benar ingin menjaga buffer inti bersih dari hal-hal yang tidak biasa, mungkin kita harus mendiskusikan kemungkinan strategi penanda dengan masalah baru?
Saya masih belum puas dengan hasil dari hasil pengujian layout 16 bit. Karena keputusan akhir belum mendesak (kita tidak akan melihat semua ini sebelum 3.11), saya akan terus mengujinya dengan beberapa perubahan (ini masih merupakan solusi yang lebih menarik bagi saya daripada varian 32 bit).
| 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) |
Saya juga berpikir kita harus memulai dengan sesuatu yang dekat dengan ini untuk memulai, kita dapat menjelajahi opsi lain nanti, tetapi ini mungkin yang paling mudah untuk dijalankan. Penipuan atribut pasti memiliki janji IMO karena biasanya tidak banyak atribut yang berbeda dalam sesi terminal.
Saya ingin mendorong kami untuk memeriksa apakah kami dapat melakukannya dengan cara monaco-editor melakukannya (saya pikir mereka menemukan cara yang sangat cerdas dan berkinerja). Alih-alih menyimpan informasi tersebut di buffer, mereka memungkinkan Anda untuk membuat dekorasi. Anda membuat dekorasi untuk rentang baris/kolom dan itu akan menempel pada rentang itu:
Sesuatu seperti ini adalah di mana saya ingin melihat hal-hal pergi. Satu ide yang saya miliki di sepanjang garis ini adalah mengizinkan penyemat untuk melampirkan elemen DOM ke rentang untuk memungkinkan hal-hal khusus digambar. Ada 3 hal yang dapat saya pikirkan saat ini yang ingin saya capai dengan ini:
Semua ini dapat dicapai dengan overlay dan ini adalah jenis API yang cukup mudah didekati (mengekspos simpul DOM) dan dapat bekerja terlepas dari jenis penyaji.
Saya tidak yakin kami ingin masuk ke bisnis yang mengizinkan penyemat mengubah cara warna latar belakang dan latar depan digambar.
@jerch Saya akan menempatkan ini pada tonggak 3.11.0 karena saya menganggap masalah ini selesai ketika kami menghapus implementasi array JS yang direncanakan untuk saat itu. https://github.com/xtermjs/xterm.js/pull/1796 juga direncanakan untuk digabungkan saat itu, tetapi masalah ini selalu dimaksudkan untuk meningkatkan tata letak memori buffer.
Juga, banyak dari diskusi nanti ini mungkin akan lebih baik dilakukan di https://github.com/xtermjs/xterm.js/issues/484 dan https://github.com/xtermjs/xterm.js/issues/1852 (dibuat karena tidak ada masalah dekorasi).
@Tyriar Woot - akhirnya ditutup :sweat_smile:
🎉 🕺 🍾
Komentar yang paling membantu
Kondisi saat ini:
Setelah: