メモリー
現在、特に大きなスクロールバックが設定された複数の端末を起動するアプリケーションの場合、バッファが大量のメモリを消費しています。 たとえば、5000スクロールバックが満たされた160x24端末を使用したデモでは約34MBのメモリが必要です(https://github.com/Microsoft/vscode/issues/29840#issuecomment-314539964を参照)。これは単一の端末であり、1080pモニターは幅の広い端末を使用する可能性があります。 また、truecolor(https://github.com/sourcelair/xterm.js/issues/484)をサポートするには、各文字に2つの追加のnumber
タイプを格納する必要があります。これにより、現在のメモリ消費量がほぼ2倍になります。バッファの。
行のテキストのフェッチが遅い
行の実際のテキストを迅速にフェッチする必要があるというもう1つの問題があります。 これが遅い理由は、データのレイアウト方法によるものです。 行には文字の配列が含まれ、それぞれに1つの文字列があります。 したがって、文字列を作成し、その直後にガベージコレクションを行います。 以前は、テキストが行バッファーから(順番に)プルされてDOMにレンダリングされるため、これを行う必要はまったくありませんでした。 ただし、xterm.jsをさらに改善するにつれて、選択やリンクなどの機能の両方がこのデータを取得するため、これはますます便利な方法になりつつあります。 再び160x24 / 5000のスクロールバックの例を使用すると、Mid-2014 Macbook Proでバッファ全体をコピーするのに30〜60ミリ秒かかります。
未来を支える
将来のもう1つの潜在的な問題は、バッファー内のデータの一部またはすべてを複製する必要があるビューモデルの導入を検討する場合、リフローを実装するためにこの種のことが必要になることです(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に向かっています。 フィードバックをお待ちしております! / cc @ jerch 、 @ mofux 、 @ rauchg 、 @ parisk
これは基本的に、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[];
長所
短所
これにより、行ではなく行に対して文字列が格納されます。これにより、選択とリンクが非常に大きくなり、行の文字列全体にすばやくアクセスできるようになると、より便利になります。
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
使用しているため、現在よりもメモリが少なくなっています短所
属性を引き出し、それらを範囲に関連付けます。 属性が重複することはないため、これを順番に配置できます。
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]
)短所
ここでの考え方は、通常、1つのターミナルセッションにはそれほど多くのスタイルがないという事実を活用することです。したがって、必要な数だけ作成して再利用するべきではありません。
// [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;
}
長所
.flags
代わりに[0]
)短所
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
オブジェクトの余分なオーバーヘッドこれは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]
)短所
Int32Array
内に格納すると、intを文字に戻すのに非常に長い時間がかかるため、機能しません。混在する可能性のある別のアプローチ:indexeddb、websql、またはファイルシステムAPIを使用して、非アクティブなスクロールバックエントリをディスクにページアウトします🤔
素晴らしい提案。 3.は、トゥルーカラーもサポートしながらメモリを節約できるため、今のところ最善の方法であることに同意します。
そこに到達し、物事が順調に進んでいる場合は、 5。で提案されているように、またはその時点で頭に浮かび、理にかなっている他の方法で最適化できます。
3.素晴らしいです👍。
@mofux 、ディスクストレージに裏打ちされた手法を使用してメモリフットプリントを削減するユースケースは確かにありますが、これにより、ディスクストレージの使用許可をユーザーに求めるブラウザ環境でのライブラリのユーザーエクスペリエンスが低下する可能性があります。
未来への支援について:
考えれば考えるほど、ttyデータの解析、行バッファーの保守、リンクの照合、検索トークンの照合などのすべての重い作業を実行するWebWorker
を用意するというアイデアが増えます。 基本的に、UIをブロックせずに、別のバックグラウンドスレッドで重い作業を行います。 しかし、これはおそらく4.0リリースに向けた別の議論の一部であるべきだと思います😉
将来的にはWebWorkerについて+100ですが、サポートしているブラウザのバージョンを変更する必要があると思います。すべてのブラウザで使用できるわけではないからです...
Int32Array
と言うと、環境でサポートされていない場合、これは通常の配列になります。
@mofuxは将来WebWorker
で良い考えをします👍
@AndrienkoAleksandrええ、 WebWorker
を使用したい場合は、機能検出を介して代替手段もサポートする必要があります。
うわーいいリスト:)
また、通常の端末使用量の90%以上でメモリ消費量を大幅に削減できるため、 3に傾く傾向があります。 この段階では、Imhoメモリの最適化を主な目標にする必要があります。 これに加えて、特定のユースケースのさらなる最適化が適用される可能性があります(私の頭に浮かぶのは、ncursesなどの「アプリのようなキャンバス」は大量の単一セルの更新を使用し、時間の経過とともに[start, end]
リストを劣化させることです) 。
@AndrienkoAleksandrええ、メインスレッドから_いくらかの_負担を取り除くことができるので、私もwebworkerのアイデアが好きです。 ここでの問題は(必要なすべてのターゲットシステムでサポートされていない可能性があるという事実に加えて)_some_です-JSの部分は、すべての最適化でもはやそれほど大したことではありません、xterm.jsは長い間見てきました。 パフォーマンス面での本当の問題は、ブラウザのレイアウト/レンダリングです...
@mofuxいくつかの「外部メモリ」へのページングは良い考えですが、xterm.jsのような「インタラクティブなターミナルウィジェットのようなもの」ではなく、より高度な抽象化の一部である必要があります。 これは、アドオンimhoによって実現できます。
オフトピック:配列対typedarrays対asm.jsでいくつかのテストを行いました。 私が言えることは-OMG、それは単純な変数のロードとセット(FFではさらに)の1 : 1,5 : 10
ようなものです。 純粋なJSの速度が本当に傷つき始めたら、「useasm」が救助のためにそこにあるかもしれません。 しかし、これは根本的な変化を意味するため、最後の手段と考えています。 そして、webassemblyはまだ出荷の準備ができていません。
オフトピック:配列対typedarrays対asm.jsでいくつかのテストを行いました。 私が言えることはすべて-OMG、単純な可変ロードとセットの場合は1:1,5:10のようなものです(FFではさらに)
@jerch明確にするために、配列とtypedarraysは1:1から1:5ですか?
カンマでうまくキャッチします-私は10:15:100
速度を意味しました。 ただし、FF型の配列でのみ、通常の配列よりもわずかに高速でした。 asmは、すべてのブラウザーでjs配列よりも少なくとも10倍高速です。FF、webkit(Safari)、blink / V8(Chrome、Opera)でテストされています。
@jerchクール、より良いメモリに加えてtypedarraysから50%のスピードアップは、今のところ間違いなく投資する価値があります。
メモリ節約のアイデア-すべての文字のwidth
を取り除くことができるかもしれません。 より安価なwcwidthバージョンを実装しようとします。
@jerchにはかなりアクセスする必要があり、リフローが発生するとバッファ内のすべての文字の幅が必要になるため、遅延読み込みなどを行うことはできません。 たとえそれが速かったとしても、私たちはそれを維持したいと思うかもしれません。
指定されていない場合は1と仮定して、オプションにする方がよい場合があります。
type CharData = [string, number?]; // not sure if this is valid syntax
[
// 'a'
['a'],
// '文'
['文', 2],
// after wide
['', 0],
...
]
@Tyriarうん-
ルックアップテーブルの16kバイトのコストで、私のコンピューターのスピードアップは10〜15倍です。 それでも必要な場合は、両方の組み合わせが可能かもしれません。
将来サポートするいくつかのフラグ: https :
別の考え:端末の下部( Terminal.ybase
からTerminal.ybase + Terminal.rows
)のみが動的です。 データの大部分を構成するスクロールバックは完全に静的であり、おそらくこれを活用できます。 私は最近までこれを知りませんでしたが、行の削除(DL、CSI Ps M)のようなものでさえ、スクロールバックを元に戻すのではなく、別の行を挿入します。 同様に、上にスクロール(SU、CSI Ps S)すると、 Terminal.scrollTop
のアイテムが削除され、 Terminal.scrollBottom
アイテムが挿入されます。
ターミナルの下部の動的部分を個別に管理し、ラインが押し出されたときにスクロールバックするように押すと、大幅な向上につながる可能性があります。 たとえば、下部の部分は、属性の変更やアクセスの高速化などを優先するために、より冗長にすることができますが、スクロールバックは、上記で提案したように、よりアーカイブ形式にすることができます。
別の考え:ほとんどのアプリケーションが機能しているように見えるので、 CharAttributeEntry
を行に制限することをお勧めします。 また、端末のサイズが変更されると、同じスタイルを共有しない「空白」のパディングが右側に追加されます。
例えば:
赤/緑の差分の右側には、スタイルが設定されていない「空白」のセルがあります。
@Tyriar
この問題を議題に戻すチャンスはありますか? 少なくとも出力集約型プログラムの場合、端末データを保持する別の方法で多くのメモリと時間を節約できる可能性があります。 入力文字列の単一文字を分割して保存することを回避できれば、2/3/4のハイブリッドによってスループットが大幅に向上します。 また、属性が変更されたときにのみ保存すると、メモリの節約に役立ちます。
例:
新しいパーサーを使用すると、属性をいじることなく大量の入力文字を保存できます。これは、それらが文字列の途中で変更されないことがわかっているためです。 その文字列の属性は、wcwidths(ええ、改行を見つけるために必要です)および改行と停止とともに、他のデータ構造または属性に保存できます。 これは基本的に、データが着信したときにセルモデルを放棄します。
何かがステップインし、端末データのドリルダウン表現が必要な場合(たとえば、レンダラーまたはエスケープシーケンス/ユーザーがカーソルを移動したい場合)に問題が発生します。 それが発生した場合でもセル計算を実行する必要がありますが、これはターミナルの列と行内のコンテンツに対してのみ実行するだけで十分です。 (スクロールアウトされたコンテンツについてはまだわかりません。さらにキャッシュ可能で、再描画するのに費用がかからない可能性があります。)
@jerch数週間以内にプラハで@mofuxに会う
https://github.com/xtermjs/xterm.js/pull/1460#issuecomment-390500944から
すべての文字を2回評価する必要があるため、アルゴは少し高価です
@jerchバッファからのテキストへのより高速なアクセスについてアイデアがあれば、お知らせください。 現在、ほとんどはご存知のように1文字ですが、 ArrayBuffer
や文字列などの可能性があります。スクロールバックが不変であることをもっと活用することを検討する必要があると考えていました。
さて、私は過去にArrayBuffersでたくさん実験しました:
Array
よりもわずかに悪いです(おそらくエンジンベンダーによってまだ最適化されていません)new UintXXArray
は、 []
したリテラル配列の作成よりもはるかに劣りますTextEncoder
部分的に実行可能です)ArrayBuffer
に関する私の調査結果は、変換ペナルティのために文字列データにそれらを使用しないことを示唆しています。 理論的には、ターミナルはnode-ptyからターミナルデータまでArrayBuffer
を使用できます(これにより、フロントエンドへの途中でいくつかの変換が節約されます)、その方法でレンダリングできるかどうかはわかりませんが、レンダリングすると思います常に最終的なuint16_t
からstring
への変換が必要です。 しかし、最後の1つの文字列の作成でさえ、保存されたランタイムのほとんどを消費します。さらに、端末を内部的に醜いCっぽい獣に変えてしまいます。 したがって、私はこのアプローチをあきらめました。
TL; DR ArrayBuffer
は、データ構造を事前に割り当てて再利用できる場合に優れています。 他のすべてについては、通常の配列の方が優れています。 文字列をArrayBuffersに圧縮する価値はありません。
私が思いついた新しいアイデアは、特に文字列の作成を可能な限り減らすことを試みています。 厄介な分割と結合を回避しようとします。 これは、新しいInputHandler.print
メソッド、wcwidth、およびline stopを念頭に置いた、上記の2番目のアイデアに基づいています。
print
は、複数の端末回線までの文字列全体を取得するようになりましたwcwidth(string) % cols
進めます\n
(ハード改行):カーソルを1行進め、ポインタリストの位置をハードブレークとしてマークします\r
:最後の行の内容(現在のカーソル位置から最後の改行まで)を行バッファーにロードして上書きします\r
場合でも、データは上記のように流れます。セルの抽象化や文字列の分割は必要ありません。cols
x rows
表現を要求しない限り、属性の変更は問題ありません(文字列全体とともに保存されるattrフラグを変更するだけです)ところで、wcwidthsは書記素アルゴリズムのサブセットであるため、これは将来交換可能になる可能性があります。
ここで危険な部分1-誰かがcols
x rows
内でカーソルを移動したいと考えています:
cols
改行で後方に移動-現在の端末コンテンツの先頭ここで危険な部分2-レンダラーは何かを描きたいと思っています:
長所:
InputHandler
メソッド用に最適化- print
短所:
InputHandler
メソッドは、このフローモデルを中断し、中間セルの抽象化が必要になるという意味で危険です。さて、これはアイデアの大まかなドラフトであり、多くの詳細がまだカバーされていないため、使用可能なATMにはほど遠いです。 Esp。 「危険な」部分は、多くのパフォーマンスの問題(さらに悪いgc動作でバッファを劣化させるなど)で厄介になる可能性があります。
@jerch
文字列をArrayBuffersに圧縮する価値はありません。
モナコはバッファをArrayBuffer
格納し、かなり高性能だと思います。 私はまだ実装をあまり深く調べていません。
特に厄介な分割と結合を回避しようとします
どれ?
どういうわけか不変であるスクロールバックをもっと利用することを考えるべきだと私は考えていました。
1つのアイデアは、スクロールバックをビューポートセクションから分離することでした。 行がスクロールバックに移行すると、スクロールバックデータ構造にプッシュされます。 2つのCircularList
オブジェクトを想像できます。1つは行が変更されないように最適化されており、もう1つはその逆です。
@Tyriarスクロール
@Tyriar
変換を1つに制限できる場合(レンダリング出力の最後の文字列)、文字列をArrayBufferに格納することは理にかなっています。 これは、あらゆる場所での文字列処理よりもわずかに優れています。 node-ptyは生データも提供できるため(Websocketも生データを提供できるため)、これは実行可能です。
特に厄介な分割と結合を回避しようとします
どれ?
全体的なアプローチは、 _minimize_分割をまったく回避することです。 バッファリングされたデータへのカーソルジャンプを誰も要求しない場合、文字列は分割されず、レンダラーに直接移動できます(サポートされている場合)。 セルが分割されず、後で結合されることはありません。
@jerchビューポートが展開されている場合は可能ですが、行が削除されたときにスクロールバックをプルすることもできると思いますか? それについて、またはそれが正しい動作であるとしても、100%確実ではありません。
@Tyriarああそうだね。 後者についてもよくわかりませんが、ネイティブxtermでは、実際のマウスまたはスクロールバーのスクロールでのみこれが許可されていると思います。 SD / SUでさえ、スクロールバッファのコンテンツを「アクティブな」ターミナルビューポートに戻しません。
ArrayBufferが使用されているmonacoエディターのソースを教えてください。 自分では見つけられないようです:blush:
うーん、TextEncoder / Decoderの仕様を読み直して、node-ptyからフロントエンドまでのArrayBuffersを使用して、ある時点で難しい方法で変換しない限り、基本的にutf-8でスタックします。 xterm.js utf-8を認識させますか? Idk、これには、より高いUnicode文字の多くの中間コードポイント計算が含まれます。 Proside-ASCII文字のメモリを節約します。
@rebornixモナコがバッファを格納する場所へのポインタを教えてください。
型付き配列と新しいパーサーの数値は次のとおりです(採用が容易でした)。
print
アクションが190 MB / sから290MB / sにジャンプprint
アクションが190 MB / sから320MB / sにジャンプします全体的にUTF-16のパフォーマンスははるかに優れていますが、パーサーがそのために最適化されているため、これは予想されていました。 UTF-8は、中間コードポイント計算に悩まされています。
文字列から型付き配列への変換は、ベンチマークls -lR /usr/lib
最大4%のJSランタイムを消費します(常に100ミリ秒をはるかに下回り、 InputHandler.parse
ループを介して実行されます)。 私は逆変換をテストしませんでした(これはセルレベルでセルのInputHandller.print
で暗黙的にatmで行われます)。 全体的な実行時間は文字列の場合よりもわずかに悪くなります(パーサーで節約された時間は変換時間を補正しません)。 これは、他の部分も配列対応で型指定されている場合に変更される可能性があります。
そして、対応するスクリーンショット( ls -lR /usr/lib
テスト済み):
文字列付き:
Uint16Arrayを使用:
EscapeSequenceParser.parse
違いに注意してください。これは、型付き配列から利益を得ることができます(〜30%高速)。 InputHandler.parse
が変換を行うため、型付き配列バージョンの場合はさらに悪化します。 また、GC Minorは、型付き配列に対してさらに多くのことを行う必要があります(配列を破棄するため)。
編集:スクリーンショットで別の側面を見ることができます-GCは約20%のランタイムに関連するようになり、長時間実行されるフレーム(赤いフラグ)はすべてGCに関連します。
もう1つのやや急進的なアイデア:
int8
からint16
、 int32
タイプが可能です。 アロケータはUint8Array
に空きインデックスを返します。このポインタは、単純なビットシフトでUint16Array
またはUint32Array
位置に変換できます。uint16_t
タイプとしてメモリに書き込みます。InputHandler
メソッドを呼び出します。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
}
長所:
malloc
とfree
コストはほとんどありません(アロケーター/デアロケーターの巧妙さに依存します)短所:
:笑顔:
実装するのは難しいです、ちょっとすべてを変更します😉
これはモナコの仕組みに近いです。キャラクターのメタデータを保存するための戦略について説明しているこのブログ投稿を思い出しましたhttps://code.visualstudio.com/blogs/2017/02/08/syntax-highlighting-optimizations
うん、それは基本的に同じ考えです。
モナコがバッファを保存する
Alexと私はArrayBufferを支持しており、ほとんどの場合、優れたパフォーマンスを提供します。 ArrayBufferを使用するいくつかの場所:
V8文字列は操作が簡単なため、配列バッファの代わりにテキストバッファに単純な文字列を使用します
コミットが追加されたときに壊れないように@rebornixのリンクを固定しました😃
次のリストは、メモリ使用量やランタイムの削減に役立つ可能性のある、私が見つけた興味深い概念の簡単な要約です。
ここで要約を実行するために、テキスト属性を「マージ」する方法を簡単に説明します。
このコードは主に、バッファデータ用のメモリを節約するという考えに基づいています(実行時間は長くなりますが、まだテストされていません)。 Esp。 前景と背景にRGBを使用したテキスト属性(サポートされると)により、xterm.jsは現在のセルごとのレイアウトで大量のメモリを消費します。 コードは、属性にサイズ変更可能な参照カウントアトラスを使用することで、これを回避しようとします。 1つの端末が100万を超えるセルを保持することはほとんどないため、これは選択肢の1つです。すべてのセルが異なる場合、アトラスは1M * entry_size
成長します。
セル自体は、属性アトラスのインデックスを保持する必要があります。 セルの変更時に、古いインデックスを参照解除し、新しいインデックスを参照する必要があります。 アトラスインデックスは、ターミナルオブジェクトの属性属性を置き換え、SGRでそれ自体が変更されます。
アトラスは現在、テキスト属性のみを対象としていますが、必要に応じてすべてのセル属性に拡張できます。 現在のターミナルバッファは属性データ用に2つの32ビット数を保持しますが(現在のバッファ設計ではRGBで4つ)、アトラスはそれを1つの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
ベンチマークで
バッファへの変更をテストするためにいくつかのプロトタイプPRを作成しました(変更の背後にある一般的な考え方については、https://github.com/xtermjs/xterm.js/pull/1528#issue-196949371を参照してください)。
@jerch 「アトラス」が常に「テクスチャアトラス」を意味するように、このためにアトラスという単語から離れることをお勧めします。 ストアやキャッシュのようなものがおそらくより良いでしょうか?
わかりました、「キャッシュ」は問題ありません。
テストベッドのPRは完了したと思います。 以下の大まかな要約の背景については、PRコメントもご覧ください。
提案:
AttributeCache
を作成して、単一のターミナルセルのスタイルを設定するために必要なすべてのものを保持します。 トゥルーカラースペックも保持できる初期の参照カウントバージョンについては、#1528を参照してください。 複数のターミナルアプリでさらにメモリを節約する必要がある場合は、キャッシュを異なるターミナルインスタンス間で共有することもできます。StringStorage
を作成して、短い端末コンテンツデータ文字列を保持します。 #1530のバージョンでは、ポインタの意味を「オーバーロード」することで、単一の文字列の格納も回避しています。 wcwidth
はここに移動する必要があります。CharData
を[number, string, number, number]
から[number, number]
に縮小します。ここで、数値は次のポインタ(インデックス番号)です。AttributeCache
エントリStringStorage
エントリ属性が大幅に変更される可能性は低いため、1つの32ビット数値で時間の経過とともに多くのメモリを節約できます。 StringStorage
ポインターは、単一文字の実際のUnicodeコードポイントであるため、 CharData
のcode
エントリとして使用できます。 実際の文字列には、 StringStorage.getString(idx)
からアクセスできます。 CharData
の4番目のフィールドwcwidth
は、 StringStorage.wcwidth(idx)
からアクセスできます(まだ実装されていません)。 CharData
(#1529でテスト済み)のcode
とwcwidth
を削除しても、実行時のペナルティはほとんどありません。
CharData
を高密度のInt32Array
ベースのバッファ実装に移動します。 また、#1530でスタブクラス(完全に機能することにはほど遠い)でテストされた場合、最終的な利点は次のようになります。ls -lR /usr/lib
スクリプトランタイムは1.3秒に低下し(マスターは2.1秒)、古いバッファーはカーソル処理のためにアクティブなままですが、削除するとランタイムが1秒未満に低下すると予想されます欠点-ステップ4は、バッファーインターフェイスでいくつかのやり直しが必要になるため、非常に多くの作業が必要です。 しかしねえ-RAMの80%を節約し、それでもランタイムパフォーマンスを得るには、それは大したことではありませんか? :笑顔:
私が偶然見つけた別の問題があります-現在の空のセル表現です。 私見セルは3つの状態を持つことができます:
blankLine
とeraseChar
で使用されていますが、コンテンツとしてここで私が見ている問題は、空のセルがスペースが挿入された通常のセルと区別できないことです。どちらもバッファレベルで同じように見えます(同じコンテンツ、同じ幅)。 私はレンダラー/出力コードを何も書きませんでしたが、これが出力フロントで厄介な状況につながることを期待しています。 Esp。 行の右端の処理は面倒になる可能性があります。
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', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '
最初の例には「terminal」という単語の後に実空間が含まれ、2番目の例には「Readme.md」の後のセルに触れたことはありません。 バッファレベルでの表現方法は、標準の場合、画面への端末出力としてコンテンツを印刷するのに最適です(とにかく部屋を空ける必要があります)が、マウスの選択などのコンテンツ文字列を処理しようとするツールの場合またはリフローマネージャは、スペースがどこから来ているのかが明確ではなくなりました。
多かれ少なかれ、これは次の質問につながります-行の実際のコンテンツの長さ(左側から何かを含むセルの量)を決定する方法は? 単純なアプローチでは、空のセルを右側から数えますが、上からの二重の意味により、これを判断するのは困難です。
提案:
これは、空のセルに他のプレースホルダー(たとえば、コントロール文字や空の文字列)を使用して、必要に応じてレンダリングプロセスでそれらを置き換えることで簡単に修正できます。 スクリーンレンダラーも、これらのセルをまったく処理する必要がない可能性があるため、これから恩恵を受けることができます(出力の生成方法によって異なります)。
ところで、これより上のラップされた文字列の場合、 isWrapped
問題も発生します。これは、リフローサイズ変更または正しいコピーアンドペースト選択処理に不可欠です。 私たちはそれを取り除くことはできませんが、ATMよりもうまく統合する必要があります。
@jerch印象的な作品! :スマイリー:
1 AttributeCacheを作成して、単一のターミナルセルのスタイルを設定するために必要なすべてのものを保持します。 トゥルーカラースペックも保持できる初期の参照カウントバージョンについては、#1528を参照してください。 複数のターミナルアプリでさらにメモリを節約する必要がある場合は、キャッシュを異なるターミナルインスタンス間で共有することもできます。
#1528にコメントしました。
2短い端末コンテンツデータ文字列を保持するStringStorageを構築します。 #1530のバージョンでは、ポインタの意味を「オーバーロード」することで、単一の文字列の格納も回避しています。 wcwidthをここに移動する必要があります。
#1530にコメントしました。
4縮小されたCharDataを高密度のInt32Arrayベースのバッファ実装に移動します。 また、#1530でスタブクラス(完全に機能することにはほど遠い)でテストされた場合、最終的な利点は次のようになります。
このアイデアはまだ完全には売れていませんが、リフローを実装するときに私たちを苦しめると思います。 これらの各ステップはほぼ順番に実行できるように見えるので、3つ完了したら、状況がどのように進行するかを確認し、これを実行することが理にかなっているかどうかを確認できます。
私が偶然見つけた別の問題があります-現在の空のセル表現です。 セルは3つの状態を持つことができます
これは、このhttps://github.com/xtermjs/xterm.js/issues/1286、:+ 1:から空白セルと「空の」セルを区別するために発生したバグの例です。
ところで、これより上のラップされた文字列の場合、isWrappedの問題も発生します。これは、リフローサイズ変更または正しいコピーアンドペースト選択処理に不可欠です。 私たちはそれを取り除くことはできませんが、ATMよりもうまく統合する必要があります。
CircularListにはラップされていない行しか含まれないため、 https://github.com/xtermjs/xterm.js/issues/622に取り組むと、 isWrapped
がなくなるのがわかります。
このアイデアはまだ完全には売れていませんが、リフローを実装するときに私たちを苦しめると思います。 これらの各ステップはほぼ順番に実行できるように見えるので、3つ完了したら、状況がどのように進行するかを確認し、これを実行することが理にかなっているかどうかを確認できます。
はい、私はあなたと一緒です(このまったく異なるアプローチで遊ぶのはまだ楽しいです)。 1と2はチェリーピックすることができ、3は1または2に応じて適用できます。4はオプションであり、現在のバッファーレイアウトに固執することができます。 メモリの節約は次のようになります。
CircularList
1 + 2 + 3:50%節約(〜5.5 MBの〜2.8MB)1.実装は非常に簡単です。scrollBackを大きくすると、メモリの動作はhttps://github.com/xtermjs/xterm.js/pull/1530#issuecomment -403542479に示すように、スケーリングが悪くなりますが、毒性は低くなります。
2.実装は少し難しいですが(行レベルでさらにいくつかの間接参照が必要です)、 Buffer
のより高いAPIをそのまま維持することが可能になります。 私は行くオプションをImho-大きなメモリを節約し、それでも統合するのは簡単です。
3.オプション2よりも5%多いmem saveは、実装が難しく、すべてのAPIに変更され、文字通りコードベース全体に変更されます。 アカデミックな興味や退屈な雨の日を実装するためにもっとImho。
@Tyriar WebAssemblyの使用法について錆を使用してさらにテストを行い、パーサーを書き直しました。 私のRustスキルは、まだ深く理解していないため、少し「さびた」ものであることに注意してください。したがって、以下は、弱いRustコードの結果である可能性があります。 結果:
コアライブラリ全体をrust(または他のwasm対応言語)で書き直したい場合を除いて、wasm langimhoに移行しても何も得られません。 最近のwasmlangsのプラスは、ほとんどが明示的なメモリ処理をサポートしているという事実です(バッファの問題に役立つ可能性があります)、欠点は、主にTS / JSに焦点を当てたプロジェクトにまったく異なる言語を導入することです(コード追加の高い障壁)そして、wasmとJSlandの間の翻訳コスト。
TL; DR
xterm.jsは、DOMやイベントなどの一般的なJSに幅広く対応し、コアパーツを書き直した場合でもWebAssemblyから何かを取得します。
@jerch素敵な調査:スマイリー:
JSからwasmへの呼び出しは、いくらかのオーバーヘッドを作成し、上記のすべての利点を食いつぶします。 実際、それは約20%遅くなりました。
これは、モナコがネイティブになるための主要な問題であり、主に私のスタンスを知らせてきました(ただし、それはネイティブノードモジュールであり、wasmではありませんでした)。 ArrayBuffer
可能な限り使用することで、パフォーマンスとシンプルさ(簡単な実装、参入障壁)のバランスが最適になると思います。
@TyriarRGBデータを保持するためのAttributeStorageを
現在のfg
とbg
256色の値が8ビットではなく9ビットベースである理由を知っていますか? 追加のビットは何に使用されますか? ここ: https :
attr
の現在のビットレイアウトを教えてください。 StringStorageポインターの「二重の意味」のような同様のアプローチでメモリをさらに節約できると思いますが、ポインターの区別のためにattr
のMSBを予約し、他の目的には使用しないでください。 これにより、後でさらに属性フラグをサポートする可能性が制限される可能性があります( FLAGS
すでに7ビットを使用しているため)、今後発生する可能性のあるいくつかの基本的なフラグがまだ不足していますか?
用語バッファ内の32ビットのattr
番号は、次のようにパックできます。
# 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データを2つの32ビット数で保持するだけでよく、フラグはattr
数のままでかまいません。
@jerchちなみに私はあなたにメールを送りました、おそらく再びスパムフィルターに食べられました😛
現在のfgおよびbg256色の値が8ビットではなく9ビットベースである理由を知っていますか? 追加のビットは何に使用されますか?
デフォルトの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
/**
* 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];
したがって、これには2つのフラグがありました。 1つはデフォルトカラー(すべてのカラービットを無視するかどうか)用で、もう1つはトゥルーカラー(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配列と左寄りの赤黒木で、最大1,000万のエントリ(ほぼ完全なアドレス空間)をテストします。 それでも、アレイはLLRBと比較してはるかに進んでいますが、損益分岐点は約10Mであると思われます。 私の7ysの古いラップトップでテストしましたが、誰かがそれをテストすることもできますし、さらに良いかもしれません-impl / testsのいくつかのバグを指摘してください。
ここにいくつかの結果があります(実行番号付き):
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
私が本当に驚いたのは、線形配列検索では下位領域での成長がまったく表示されないという事実です。最大10kエントリが最大4msで安定しています(キャッシュに関連している可能性があります)。 10Mテストでは、おそらくmemページングが原因で、予想よりも実行時間が悪いことが示されています。 たぶんJSはJITでマシンから遠く離れており、すべてのオプト/デオプトが発生していますが、それでも複雑なステップを排除することはできないと思います(LLRBは単一の_n_で重いように見えるため、O( n)対O(logn)以上)
ところで、ランダムデータの場合、違いはさらに悪化します。
デフォルトのfg / bgカラー(暗い色でも明るい色でもかまいません)に使用されていると思うので、実際には257色です。
それで、これは8つのパレットカラーの1つからSGR 39
またはSGR 49
を区別していますか?
attr自体の内部のポインターデータの代わりに、rgbデータへの参照を保持する別のマップがある可能性がありますか? mapAttrIdxToRgb:{[idx:番号]:RgbData
これにより、追加のメモリ使用量を伴う別の間接参照が導入されます。 上記のテストでは、フラグを常にattrsに保持することと、RGBデータとともにストレージに保存することの違いもテストしました。 1Mエントリの違いは約0.5msなので、この複雑なattrsセットアップには行きません。代わりに、RGBが設定されたら、フラグをストレージにコピーします。 それでも、RGB以外のセルのストレージをまったく回避するため、直接属性とポインターの32ビットの区別を使用します。
また、fg / bgのデフォルトの8パレットカラーは、現在バッファで十分に表現されていないと思います。 理論的には、端末は次のカラーモードをサポートする必要があります。
SGR 39
+ SGR 49
fg / bgのデフォルトの色(カスタマイズ可能)SGR 30-37
+ SGR 40-47
8 fg / bg用の低カラーパレット(カスタマイズ可能)SGR 90-97
+ SGR 100-107
fg / bg用の8つのハイカラーパレット(カスタマイズ可能)SGR 38;5;n
+ SGR 48;5;n
256 fg / bg用のインデックス付きパレット(カスタマイズ可能)SGR 38;2;r;g;b
+ SGR 48;2;r;g;b
fg / bgのRGB(カスタマイズ不可)オプション2.)と3.)は1バイトにマージできます(単一の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
したがって、32ビット数の30ビットが必要であり、他の目的のために2ビットを空けておきます。 32番目のビットは、非RGBセルのストレージを省略して、ポインター対直接属性フラグを保持できます。
また、attrアクセスを便利なクラスにまとめて、実装の詳細を外部に公開しないことをお勧めします(上記のテストファイルを参照してください。これを実現するための初期バージョンのTextAttributes
クラスがあります)。
申し訳ありませんが、数日はオンラインではありませんでした。メールがスパムフィルターに食われてしまうのではないかと心配しています。 再送していただけませんか?
憤る
ところで、配列とllrbの検索に関する上記の数値はくだらないものです。オプティマイザーが、forループで奇妙なことをしているために台無しになっていると思います。 わずかに異なるテスト設定では、O(n)とO(log n)がはるかに早く成長していることを明確に示しています(1000個の要素が事前に入力されていると、ツリーですでに高速になります)。
現在の状態:
後:
非常に単純な最適化の1つは、配列の配列を1つの配列にフラット化することです。 つまり、_N_ CharData
セルの_data
配列を持つ_N_列のBufferLine
の代わりに、各CharData
は4の配列であり、1つだけです。 _4 * N_要素の配列。これにより、_N_配列のオブジェクトオーバーヘッドが排除されます。 また、キャッシュの局所性も向上するため、より高速になるはずです。 欠点は少し複雑で醜いコードですが、それだけの価値があるようです。
以前のコメントのフォローアップとして、各セルの_data
配列で可変数の要素を使用することを検討する価値があるようです。 言い換えれば、ステートフルな表現です。 位置のランダムな変更はよりコストがかかりますが、特に単純な配列がキャッシュの局所性に最適化されているため、行の先頭からの線形スキャンは非常に高速になります。 通常のシーケンシャル出力は、レンダリングと同様に高速です。
スペースの削減に加えて、セルごとの要素数が可変であるという利点は、柔軟性が向上することです。追加の属性(24ビットカラーなど)、特定のセルまたは範囲の注釈、グリフ、または
ネストされたDOM要素。
@PerBothner Thxあなたのアイデアのために! ええ、私はすでにポインタ演算を使用して単一の密な配列レイアウトをテストしました、それは最高のメモリ使用率を示しています。 サイズ変更に関しては問題が発生します。これは基本的に、メモリチャンク全体を再構築する(コピーオーバー)か、より大きなチャンクに高速コピーしてパーツを再調整することを意味します。 これはかなり経験的です。 そして、私はmemの保存によって正当化されませんでした(上記のいくつかの遊び場のPRでテストされ、節約は新しいバッファラインの実装と比較して約10%でした)。
2番目のコメントについて-折り返し行の処理が簡単になるため、すでに説明しました。 今のところ、新しいバッファレイアウトに行X列のアプローチを採用し、それを最初に実行することにしました。 リフローサイズ変更の実装を行ったら、これに再度対処する必要があると思います。
バッファに追加のものを追加することについて:現在、他のほとんどの端末が行うことをここで行います-カーソルの進みはwcwidth
によって決定され、データのレイアウト方法に関するpty / termiosのアイデアとの互換性を維持します。 これは基本的に、サロゲートペアや結合文字などのみをバッファレベルで処理することを意味します。 その他の「より高いレベル」の結合ルールは、レンダラーの文字ジョイナーによって適用できます(現在、合字のhttps://github.com/xtermjs/xterm-addon-ligaturesで使用されています)。 私は初期のバッファレベルでUnicode書記素もサポートするためにPRを開いていましたが、ほとんどのptyバックエンドにはこれの概念がなく(まったくありませんか?)、その段階ではこれを行うことができないと思います。 。 実際のBIDIサポートについても同じことが言えます。カーソル/セルの動きを損なわないようにするには、レンダラーの段階で書記素とBIDIを実行する方がよいと思います。
セルに接続されたDOMノードのサポートは非常に興味深いと思います。私はそのアイデアが好きです。 現在、レンダラーバックエンド(DOM、キャンバス2D、新しい光沢のあるwebglレンダラー)が異なるため、直接的なアプローチでは不可能ですが、ネイティブでサポートされていない場所にオーバーレイを配置することで、すべてのレンダラーでこれを実現できると思います(DOMレンダラーのみ直接行うことができます)。 ものとそのサイズ、およびレンダラーがダーティな作業を行う可能性があることを通知するには、バッファーレベルで何らかのAPIが必要になります。 これについては別の問題で話し合い/追跡する必要があると思います。
詳細な回答ありがとうございます。
_ "サイズ変更に関しては問題が発生します。基本的には、メモリチャンク全体を再構築(コピーオーバー)するか、より大きなチャンクに高速コピーしてパーツを再調整することを意味します。" _
つまり、サイズ変更時に、_N_要素だけでなく_4 * N_要素をコピーする必要がありますか?
配列に論理(ラップされていない)行のすべてのセルを含めることは理にかなっている場合があります。 たとえば、180文字の行と80列の幅の端末を想定します。 その場合、3つのBufferLine
インスタンスがすべて同じ_4 * 180_要素_data
バッファーを共有する可能性がありますが、各BufferLine
は開始オフセットも含まれます。
さて、私は[cols] x [rows] x [needed single cell space]
によって構築された1つの大きな配列にすべてを持っていました。 したがって、基本的には、指定された高さと幅の「キャンバス」として機能します。 これは実際にメモリ効率が高く、通常の入力フローでは高速ですが、 insertCell
/ deleteCell
が呼び出されるとすぐに(サイズ変更によって実行されます)、アクションが実行される位置の背後にあるメモリ全体が呼び出されます。シフトする必要があります。 小さなスクロールバック(<10k)の場合、これも問題ではありません。実際には、> 100k行のショートッパーです。
現在の型付き配列implは、これらのシフトを実行する必要がありますが、メモリコンテンツを行末まで移動するだけでよいため、毒性が少ないことに注意してください。
コストのかかるシフトを回避するためにさまざまなレイアウトを考えました。ナンセンスなメモリシフトを節約するためのメインフィールドは、スクロールバックを「ホットターミナル行」(最新のterminal.rows
)から実際に分離することです。カーソルジャンプと挿入/削除によって変更されます。
基になるメモリを複数のバッファラインオブジェクトで共有することは、ラッピングの問題を解決するための興味深いアイデアです。 明示的な参照処理などを導入せずに、これがどのように確実に機能するかはまだわかりません。 別のバージョンでは、明示的なメモリ処理を使用してすべてを実行しようとしましたが、refカウンターは実際のショートッパーであり、GCランドでは間違っていると感じました。 (プリミティブについては#1633を参照してください)
編集:ところで、明示的なメモリ処理は現在の「1行あたりのメモリ」アプローチと同等でした。キャッシュの局所性が向上したため、パフォーマンスが向上することを期待していました。 JS抽象化でのmem処理。
セルに接続されたDOMノードのサポートは非常に興味深いと思います。私はそのアイデアが好きです。 現在、レンダラーバックエンド(DOM、キャンバス2D、新しい光沢のあるwebglレンダラー)が異なるため、直接的なアプローチでは不可能ですが、ネイティブでサポートされていない場所にオーバーレイを配置することで、すべてのレンダラーでこれを実現できると思います(DOMレンダラーのみ直接行うことができます)。
少し外れたトピックですが、最終的には、ビューポート内のセルに関連付けられたDOMノードがあり、キャンバスのレンダリングレイヤーと同様に機能します。 そうすれば、消費者はHTMLとCSSを使用してセルを「装飾」でき、canvasAPIにアクセスする必要がなくなります。
配列に論理(ラップされていない)行のすべてのセルを含めることは理にかなっている場合があります。 たとえば、180文字の行と80列の幅の端末を想定します。 その場合、3つのBufferLineインスタンスがすべて同じ4 * 180要素の_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
ます。 ここから、いくつかの方向性が考えられます。
wcwidth
UTF16と互換性を持たせるUTF8について:パーサーは現在、ネイティブUTF8シーケンスを処理できません。これは主に、中間文字がC1制御文字と衝突するためです。 また、UTF8には、追加の中間状態を使用した適切なストリーム処理が必要です。これは、厄介でimhoをパーサーに追加しないでください。 UTF8は事前に処理する方が適切であり、コードポイントをあらゆる場所で処理しやすくするために、UTF32への変換権を使用することもできます。
可能なUTF8入力エンコーディングと内部バッファレイアウトに関して、私は大まかなテストを行いました。 総実行時間に対するキャンバスレンダラーのはるかに高い影響を除外するために、私は次のwebglレンダラーでそれを行いました。 ls -lR /usr/lib
ベンチマークを使用すると、次の結果が得られます。
現在のマスター+ webglレンダラー:
遊び場ブランチ、#1796、#1811の一部、およびwebglレンダラーを適用します。
プレイグラウンドブランチは、解析と保存を行う前に、UTF8からUTF32への早期変換を行います(変換には約30ミリ秒が追加されます)。 スピードアップは主に、入力フロー中の2つのホット関数EscapeSequenceParser.parse
(120ミリ秒対35ミリ秒)とInputHandler.print
(350ミリ秒対75ミリ秒)によって得られます。 どちらも、 .charCodeAt
呼び出しを節約することで、型付き配列スイッチから多くのメリットを得ることができます。
また、これらの結果をUTF16中間型配列と比較しました- EscapeSequenceParser.parse
はわずかに高速です(〜25 ms)が、 InputHandler.print
は、 wcwidth
必要なサロゲートペアリングとコードポイントルックアップのために遅れています
また、システムがls
データ(SSDを搭載したi7)を提供できる限界にすでに達していることにも注意してください。高速化により、実行が速くなる代わりにアイドル時間が追加されます。
概要:
私たちが得ることができる最速の入力処理は、バッファ表現のためのUTF8トランスポート+ UTF32の混合です。 UTF8トランスポートは、一般的な端末入力に最適なバイトパックレートを備えており、最大Terminal.write
までのバッファーのいくつかのレイヤーを介して、ptyからナンセンスな変換を削除しますが、UTF32ベースのバッファーはデータをかなり高速に格納できます。 後者はUTF16よりもわずかに高いメモリフットプリントを備えていますが、UTF16は、より多くの間接参照を伴うより複雑な文字処理のためにわずかに遅くなります。
結論:
今のところ、UTF32ベースのバッファレイアウトを使用する必要があります。 UTF8入力エンコーディングへの切り替えも検討する必要がありますが、APIの変更とインテグレーターへの影響についてさらに検討する必要があります(electronのipcメカニズムはBASE64エンコーディングとJSONラッピングなしではバイナリデータを処理できないため、パフォーマンスの努力が妨げられるようです)。
今後のトゥルーカラーサポートのバッファレイアウト:
現在、型付き配列ベースのバッファレイアウトは次のとおりです(1つのセル)。
| uint32_t | uint32_t | uint32_t |
| attrs | codepoint | wcwidth |
ここで、 attrs
は、必要なすべてのフラグ+9ビットベースのFGおよびBGカラーが含まれています。 codepoint
は、21ビット(UTF32の場合は最大0x10FFFF)+ 1ビットを使用して、文字とwcwidth
2ビット(0〜2の範囲)の組み合わせを示します。
アイデアは、追加のRGB値のためのスペースを作るために、ビットをより良いパッケージレートに再配置することです。
wcwidth
を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) |
このアプローチの利点は、1つのインデックスアクセスと最大ですべての値に比較的安価にアクセスできることです。 2ビット演算(および/または+シフト)。
メモリフットプリントは現在のバリアントに対して安定していますが、セルあたり12バイトと非常に高くなっています。 これは、UTF16とattr
間接
| uint16_t | uint16_t |
| BMP codepoint(16) | comb(1) wcwidth(2) attr pointer(13) |
これで、セルあたり4バイト+属性用のスペースができました。 これで、属性を他のセルにリサイクルすることもできます。 イェーイミッション達成! -えーと、1秒...
メモリフットプリントを比較すると、2番目のアプローチが明らかに勝ちます。 ランタイムについてはそうではありませんが、ランタイムを大幅に増加させる3つの主要な要因があります。
2番目のアプローチのセクシーさは、追加のメモリ節約です。 したがって、 BufferLine
実装を変更して、プレイグラウンドブランチ(上記のコメントを参照)でテストしました。
ええ、パーサーでUTF8 +型付き配列に変更する前のところから少し戻っています。 ただし、メモリ使用量は約1.5MBから約0.7MBに減少しました(87セルと1000行のスクロールバックを備えたデモアプリ)。
ここからは、メモリと速度の節約の問題です。 js配列から型付き配列に切り替えることですでに多くのメモリを節約しているので(C ++ヒープでは約5.6MBから約1.5MBに低下し、有毒なJSヒープの動作とGCを遮断します)、より高速なバリアントを使用する必要があると思います。 メモリ使用量が再び差し迫った問題になったら、ここで2番目のアプローチで説明されているように、よりコンパクトなバッファレイアウトに切り替えることができます。
同意します。メモリ消費が問題にならない限り、速度を最適化しましょう。 また、コードの読み取りと保守が難しくなるため、間接参照はできるだけ避けたいと思います。 私たちのコードベースにはすでにかなりの数の概念と微調整があり、人々(私を含む😅)がコードフローに従うのを難しくしています-そしてこれらの多くを持ち込むことは常に非常に正当な理由によって正当化されるべきです。 IMOがさらに1メガバイトのメモリを節約しても、それは正当化されません。
それにもかかわらず、私はあなたの演習を読んで学ぶことを本当に楽しんでいます、それを非常に詳細に共有してくれてありがとう!
@mofuxそうですね-コードの複雑さははるかに高くなっています(UTF16サロゲート先読み、中間コードポイント計算、attrエントリをカウントする参照付きツリーコンテナ)。
また、32ビットレイアウトはほとんどフラットメモリであるため(文字を組み合わせるだけで間接参照が必要)、より多くの最適化が可能です(#1811の一部でもあり、レンダラーではまだテストされていません)。
attrオブジェクトに間接化することの大きな利点が1つあります。それは、はるかに拡張性が高いことです。 注釈、グリフ、またはカスタムペイントルールを追加できます。 リンク情報は、よりクリーンで効率的な方法で保存できます。 おそらく、セルをレンダリングする方法を知っているICellPainter
インターフェイスを定義し、カスタムプロパティをハングアップすることもできます。
1つのアイデアは、BufferLineごとに2つの配列(Uint32Array配列とICellPainter配列)を使用し、セルごとに1つの要素を使用することです。 現在のICellPainterはパーサー状態のプロパティであるため、色/属性の状態が変化しない限り、同じICellPainterを再利用するだけです。 セルに特別なプロパティを追加する必要がある場合は、最初にICellPainterのクローンを作成します(共有されている可能性がある場合)。
最も一般的な色/属性の組み合わせにICellPainterを事前に割り当てることができます。少なくとも、デフォルトの色/属性に対応する一意のオブジェクトがあります。
スタイルの変更(デフォルトの前景色/背景色の変更など)は、各セルを更新することなく、対応するICellPainterインスタンスを更新するだけで実装できます。
可能な最適化があります。たとえば、シングル幅文字とダブル幅文字(またはゼロ幅文字)に異なるICellPainterインスタンスを使用します。 (これにより、各Uint32Array要素で2ビットが節約されます。)Uint32Arrayには11個の使用可能な属性ビットがあります(BMP文字用に最適化するとさらに多くなります)。 これらは、最も一般的な/有用な色/属性の組み合わせをエンコードするために使用でき、最も一般的なICellPainterインスタンスにインデックスを付けるために使用できます。 その場合、ICellPainter配列を遅延して割り当てることができます。つまり、行の一部のセルに「あまり一般的でない」ICellPainterが必要な場合に限ります。
非BMP文字の_combined配列を削除し、それらをICellPainterに格納することもできます。 (これには、BMP以外の文字ごとに一意のICellPainterが必要なため、ここではトレードオフがあります。)
@PerBothnerええ、間接参照はより用途が広いので、珍しいエキストラに適しています。 しかし、それらは珍しいので、そもそもそれらのために最適化したくないのです。
私がいくつかのテストベッドで試したことに関するいくつかのメモ:
codepoint
に結合ビットが設定されているときにインデックスを作成したメモリのチャンク)で明らかになりました(valgrindでテストしたキャッシュの局所性が優れているため、ここではアクセスが高速でした)。 JSに引き継がれると、文字列から数値への変換が必要なため、速度の向上はそれほど大きくありませんでしたが(ただし、さらに高速です)、メモリの節約はさらに大きくなりました(JSタイプの管理スペースが追加されたためだと思います)。 問題は、JSの大きなアンチパターンである明示的なメモリ管理と組み合わせたもののグローバルStringStorage
でした。 そのためのクイックフィックスは、クリーンアップをGCに委任する_combined
オブジェクトAttributeStorage
を使用しました(https://github.com/jerch/xterm.js/tree/AttributeStorageを参照)。 メモリに関しては、これはかなりうまくいきました。これは主に、pplがトゥルーカラーをサポートしていても、少数の属性セットしか使用しないためです。 パフォーマンスはそれほど良くありませんでした-主に参照カウント(すべてのセルがこの外部メモリを2回覗き見する必要がありました)とattrマッチングが原因でした。 そして、私がJSにrefを採用しようとしたとき、それはちょうど間違っていると感じました-私が「STOP」ボタンを押したポイント。 その間に、型付き配列に切り替えることで、すでに大量のメモリとGC呼び出しを節約できたことが判明しました。したがって、少しコストのかかるフラットメモリレイアウトは、ここで速度の利点を生かすことができます。現在、このフラットな32ビットレイアウトは、一般的なものに最適化されており、一般的でない追加機能は使用できません。 NS。 まだマーカーがあり(慣れていないので、今は何ができるのかわかりません)、はい、バッファーにはまだ空きビットがあります(これは将来のニーズに適しています。たとえば、次のように使用できます。特別扱いなどのフラグ)。
私にとっては、attrsストレージを備えた16ビットレイアウトのパフォーマンスが悪いのは残念ですが、メモリ使用量を半分にすることは依然として大きな問題です(特に、pplが10kを超えるスクロールラインを使用し始めた場合)が、実行時のペナルティとコードの複雑さが重要ですより高いmemはatmimhoを必要とします。
ICellPainterのアイデアについて詳しく説明していただけますか? たぶん私はこれまでのところいくつかの重要な機能を見逃していました。
DomTermの私の目標は、従来のターミナルエミュレーターで可能になったものと同じように、より豊富な対話を可能にし、促進することでした。 Webテクノロジーを使用すると、多くの興味深いことが可能になるため、従来の高速な端末エミュレーターであることに集中するのは恥ずべきことです。 特に、xterm.jsの多くのユースケース(IDEのREPLなど)は、単純なテキストを超えることで実際に恩恵を受けることができるためです。 (人々は例えば、トゥルーカラーおよび組み込みグラフィックスが不足して文句を言っている)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バイトのポインターが必要です。 1つの可能性は、_combined配列をICellPainter配列と結合することです。IS_COMBINED_BIT_MASKが設定されている場合、ICellPainterには結合された文字列も含まれます。 もう1つの可能な最適化は、Uint32Arrayの使用可能なビットを配列へのインデックスとして使用することです。これにより、複雑さと間接参照が追加されますが、スペースが節約されます。
monaco-editorと同じようにできるかどうかを確認することをお勧めします(彼らは本当にスマートでパフォーマンスの高い方法を見つけたと思います)。 このような情報をバッファに保存する代わりに、 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 }
});
後でレンダラーはそれらの装飾を拾い上げて描くことができます。
monaco-editorapiがどのように見えるかを示すこの小さな例をチェックしてください。
https://microsoft.github.io/monaco-editor/playground.html#interacting -with-the-editor-line-and-inline-decorations
ターミナル内での画像のレンダリングなどの場合、monacoは、次の例で(他の概念の中でも)表示できるビューゾーンの概念を使用します。
https://microsoft.github.io/monaco-editor/playground.html#interacting -with-the-editor-listening-to-mouse-events
明確化とスケッチアップのための
将来的には、入力チェーンとバッファーをWebworkerに移動する予定です。 したがって、バッファは抽象レベルで動作するように意図されており、ピクセルメトリックやDOMノードなどのレンダリング/表現関連のものをまだ使用することはできません。 DomTermは高度にカスタマイズ可能であるため、これに対するニーズはわかりますが、拡張された内部マーカーAPIを使用してこれを行う必要があり、monaco / vscode(thx for thポインタ@mofux)からここで学ぶことができます。
私は本当にコアバッファを珍しいものからきれいに保ちたいのですが、新しい問題で可能なマーカー戦略について話し合うべきでしょうか?
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を約束します。
monaco-editorと同じようにできるかどうかを確認することをお勧めします(彼らは本当にスマートでパフォーマンスの高い方法を見つけたと思います)。 このような情報をバッファに保存する代わりに、装飾を作成できます。 行/列の範囲の装飾を作成すると、その範囲に固執します。
このようなものが私が物事が行くのを見たいところです。 これらの方針に沿って私が考えた1つのアイデアは、埋め込み者がDOM要素を範囲にアタッチして、カスタムのものを描画できるようにすることでした。 私がこれで達成したい現時点で私が考えることができる3つのことがあります:
これらはすべてオーバーレイで実現でき、非常に親しみやすいタイプのAPI(DOMノードを公開)であり、レンダラーのタイプに関係なく機能します。
埋め込み者が背景色と前景色の描画方法を変更できるようにするビジネスに参入したいかどうかはわかりません。
@jerchこの問題は、その時点で計画されているJSアレイの実装を削除したときに終了したと思うので、これを3.11.0マイルストーンに置きます。 https://github.com/xtermjs/xterm.js/pull/1796もマージされる予定ですが、この問題は常にバッファのメモリレイアウトの改善に関するものでした。
また、この後の議論の多くは、 https://github.com/xtermjs/xterm.js/issues/484およびhttps://github.com/xtermjs/xterm.js/issues/1852で行った方がよいでしょう
@Tyriar Woot-ついにクローズ:sweat_smile:
🎉🕺🍾
最も参考になるコメント
現在の状態:
後: