ソースでノードを解析します.jsにおけるBufferの8 KBプール割当てルールと固定ビット数の読み書き
5663 ワード
Node.jsでは、Bufferは、ファイルやネットワークI/Oで取得されたデータなどの潜在的な大体積データを格納するためによく使用され、符号化を指定しない場合、Bufferの形式で提供され、その地位は一般的ではないことがわかります.Bufferの作成は、内部の8 KBプールを通過する可能性があると聞いたことがあるかもしれませんが、具体的なルールは何ですか?新しいBufferインスタンスを作成できるAPIはそんなに多く、いったいどのAPIが通過するのか、どのAPIが通過しないのか.ドキュメントを読むとき、
一緒に行こうjsプロジェクトの
統計すると、現在のバージョンのNode.js(v 6.0)で新しいBufferクラスインスタンスを作成できるAPIは、次のとおりです.
コードトレーサビリティに従って、これらのAPIは最後に2つの内部関数のうちの1つに入り、Bufferインスタンスを作成します.この2つの内部関数はそれぞれ
コードから明らかなように、最終的に作成された場合、
では、これらのAPIがどのような方法を行っているのか見てみましょう.
一目瞭然ですが、以下の状況が同時に成立すると、作成された新しいBufferクラスのインスタンスが内部8 KBプールを通過することになります.は、 受信データサイズは0ではありません. 入力データのサイズは4 KB未満でなければなりません.
Bufferのドキュメントを読むときに、顕著なパフォーマンス向上はありません. インスタンスが初期化されると、新しいプロパティが追加されます.
このPRはともかく、これらの読み書き操作は、数字の精度が32ビット以下であれば、対応方法はすべてJavaScriptによって実現され、非常に優雅であり、
これに対応して、符号なし左シフトを使用して空席を空けて
このうちの
参照先: https://github.com/nodejs/node/blob/master/lib/buffer.js
Buffer#writeUInt32BE
,Buffer#readUInt32BE
などの固定ビットの数字の読み書き操作を見たことがあるかもしれませんが、具体的にはどのように実現されていますか?一緒に行こうjsプロジェクトの
lib/buffer.js
のコードを探してみましょう.8 KBプール割当ルール
統計すると、現在のバージョンのNode.js(v 6.0)で新しいBufferクラスインスタンスを作成できるAPIは、次のとおりです.
new Buffer()
(使用は推奨されていませんが、メモリ内の潜在的な機密情報が漏洩する可能性があります.具体的な例はここを参照してください)Buffer.alloc()
Buffer.allocUnsafe()
(メモリ内の機密情報を漏らす可能性もあるが、意味的には非常に明確)Buffer.from()
Buffer.concat()
コードトレーサビリティに従って、これらのAPIは最後に2つの内部関数のうちの1つに入り、Bufferインスタンスを作成します.この2つの内部関数はそれぞれ
createBuffer()
とallocate()
です.// lib/buffer.js
// ...
Buffer.poolSize = 8 * 1024;
var poolSize, poolOffset, allocPool;
function createPool() {
poolSize = Buffer.poolSize;
allocPool = createBuffer(poolSize, true);
poolOffset = 0;
}
createPool();
function createBuffer(size, noZeroFill) {
flags[kNoZeroFill] = noZeroFill ? 1 : 0;
try {
const ui8 = new Uint8Array(size);
Object.setPrototypeOf(ui8, Buffer.prototype);
return ui8;
} finally {
flags[kNoZeroFill] = 0;
}
}
function allocate(size) {
if (size === 0) {
return createBuffer(size);
}
if (size < (Buffer.poolSize >>> 1)) {
if (size > (poolSize - poolOffset))
createPool();
var b = allocPool.slice(poolOffset, poolOffset + size);
poolOffset += size;
alignPool();
return b;
} else {
return createBuffer(size, true);
}
}
コードから明らかなように、最終的に作成された場合、
createBuffer()
関数を実行すると、8 KBプールを経由せず、allocate()
関数を実行すると、入力されたデータサイズがBuffer.poolSize
記号よりも小さく1ビット右にシフトした結果(この値を2で割って下に整列することに相当し、この例では4 KB)、8 KBプールにのみ使用されます(現在のプールの空き容量が不足している場合は、新しいプールを作成し、現在のプールを新しいプールに指定します).では、これらのAPIがどのような方法を行っているのか見てみましょう.
// lib/buffer.js
// ...
Buffer.alloc = function(size, fill, encoding) {
// ...
return createBuffer(size);
};
Buffer.allocUnsafe = function(size) {
assertSize(size);
return allocate(size);
};
Buffer.from = function(value, encodingOrOffset, length) {
// ...
if (value instanceof ArrayBuffer)
return fromArrayBuffer(value, encodingOrOffset, length);
if (typeof value === 'string')
return fromString(value, encodingOrOffset);
return fromObject(value);
};
function fromArrayBuffer(obj, byteOffset, length) {
byteOffset >>>= 0;
if (typeof length === 'undefined')
return binding.createFromArrayBuffer(obj, byteOffset);
length >>>= 0;
return binding.createFromArrayBuffer(obj, byteOffset, length);
}
function fromString(string, encoding) {
// ...
if (length >= (Buffer.poolSize >>> 1))
return binding.createFromString(string, encoding);
if (length > (poolSize - poolOffset))
createPool();
var actual = allocPool.write(string, poolOffset, encoding);
var b = allocPool.slice(poolOffset, poolOffset + actual);
poolOffset += actual;
alignPool();
return b;
}
Buffer.concat = function(list, length) {
// ...
var buffer = Buffer.allocUnsafe(length);
// ...
return buffer;
};
一目瞭然ですが、以下の状況が同時に成立すると、作成された新しいBufferクラスのインスタンスが内部8 KBプールを通過することになります.
Buffer.allocUnsafe
,Buffer.concat
,Buffer.from
(パラメータはArrayBufferインスタンスではありません)およびnew Buffer
(パラメータはArrayBufferインスタンスではありません)によって作成されます.固定ビット数読み書きAPI
Bufferのドキュメントを読むときに、
Buffer#writeUInt32BE
,Buffer#readUInt32BE
のようなAPIを見ると、ES 6仕様のDateViewクラスが提供する方法が考えられるかもしれません.似たようなことをしていますjsプロジェクトでは、これらのAPIの下位層を元のDateView
インスタンスに置き換えて動作するPRもあるが、このPRは現在stalled
とマークされている.具体的な理由は、以下の通りである.noAssert
パラメータは無効になります.このPRはともかく、これらの読み書き操作は、数字の精度が32ビット以下であれば、対応方法はすべてJavaScriptによって実現され、非常に優雅であり、
TypeArray
の下のクラス(Bufferで使用されているのはUint8Array
)のインスタンスの要素を利用して、ビットオーバーフロー時にオーバーフロービットを捨てる仕組みを利用している.writeUInt32LE
とwriteUInt32BE
(LEとBE、すなわち小端バイト順と大端バイト順、この記事を参照)を例にとると、32ビットの符号なし整数には4バイトの記憶が必要であり、大端バイト順の場合、最初の要素は、入力された32ビットの整数を直接24ビット右にシフトし、元の最も左の8ビットに取得し、現在の左のすべてのビットを捨てる.このように、2番目の要素は符号なしで右に16ビット、3番目の要素は8ビット、4番目の要素は移動する必要はありません(小端バイト順は逆です):Buffer.prototype.writeUInt32BE = function(value, offset, noAssert) {
value = +value;
offset = offset >>> 0;
if (!noAssert)
checkInt(this, value, offset, 4, 0xffffffff, 0);
this[offset] = (value >>> 24);
this[offset + 1] = (value >>> 16);
this[offset + 2] = (value >>> 8);
this[offset + 3] = value;
return offset + 4;
};
これに対応して、符号なし左シフトを使用して空席を空けて
|
操作をマージします.Buffer.prototype.readUInt32BE = function(offset, noAssert) {
offset = offset >>> 0;
if (!noAssert)
checkOffset(offset, 4, this.length);
return (this[offset] * 0x1000000) +
((this[offset + 1] << 16) |
(this[offset + 2] << 8) |
this[offset + 3]);
};
このうちの
(this[offset] * 0x1000000) +
はthis[offset] << 24 |
に相当する.最後に
参照先: