Skip to content

Commit

Permalink
Transfer notebook output as VSBuffer
Browse files Browse the repository at this point in the history
Fixes #125819

This PR lets us transfer the notebook output as a VSBuffer instead of as an array of numbers

To implement this, I reworked our rpc logic to support serializing VSBuffers inside of objects. Previously rpc calls could only efficiently serialize VSBuffers that appear as top level arguments

To support transferring VS Buffers, during serialization we use the `replacer` argument on `JSON.stringify` to extract and replace all buffers in the arguments with special reference objects. During de-serialization, when we encounter one of these reference objects we simply replace it with the corresponding restored buffer.

While implementing the new rpc logic, the separation between 'mixed' and 'simple' rpc arguments became less clear. Therefore I decided to try to combine roc calls to use a single arguments format. This simplified the code but I may revisit this to see if I can improve the performance. Right now serialization up 2x slower for large objects (usually it's less than that), while deserialization is about the same as it was previously. I believe the root cause is that we now potentially make two function calls per argument value: one for the outer replacer and one for the inner `JSONStringifyReplacer`.
  • Loading branch information
mjbvz committed Aug 11, 2021
1 parent 08b1c2e commit 78152ae
Show file tree
Hide file tree
Showing 17 changed files with 108 additions and 208 deletions.
1 change: 1 addition & 0 deletions src/vs/base/common/marshalling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ function replacer(key: string, value: any): any {


type Deserialize<T> = T extends UriComponents ? URI
: T extends VSBuffer ? VSBuffer
: T extends object
? Revived<T>
: T;
Expand Down
4 changes: 2 additions & 2 deletions src/vs/workbench/api/browser/mainThreadNotebookDto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export namespace NotebookDto {
export function toNotebookOutputItemDto(item: notebookCommon.IOutputItemDto): extHostProtocol.NotebookOutputItemDto {
return {
mime: item.mime,
valueBytes: Array.from(item.data)
valueBytes: item.data
};
}

Expand Down Expand Up @@ -46,7 +46,7 @@ export namespace NotebookDto {
export function fromNotebookOutputItemDto(item: extHostProtocol.NotebookOutputItemDto): notebookCommon.IOutputItemDto {
return {
mime: item.mime,
data: new Uint8Array(item.valueBytes)
data: item.valueBytes
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1938,7 +1938,7 @@ export interface INotebookDocumentsAndEditorsDelta {

export interface NotebookOutputItemDto {
readonly mime: string;
readonly valueBytes: number[]; // todo@jrieken ugly, should be VSBuffer
readonly valueBytes: VSBuffer;
}

export interface NotebookOutputDto {
Expand Down
5 changes: 3 additions & 2 deletions src/vs/workbench/api/common/extHostTypeConverters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays';
import { VSBuffer } from 'vs/base/common/buffer';
import * as htmlContent from 'vs/base/common/htmlContent';
import { DisposableStore } from 'vs/base/common/lifecycle';
import * as marked from 'vs/base/common/marked/marked';
Expand Down Expand Up @@ -1494,12 +1495,12 @@ export namespace NotebookCellOutputItem {
export function from(item: types.NotebookCellOutputItem): extHostProtocol.NotebookOutputItemDto {
return {
mime: item.mime,
valueBytes: Array.from(item.data), //todo@jrieken this HACKY and SLOW... hoist VSBuffer instead
valueBytes: VSBuffer.wrap(item.data),
};
}

export function to(item: extHostProtocol.NotebookOutputItemDto): types.NotebookCellOutputItem {
return new types.NotebookCellOutputItem(new Uint8Array(item.valueBytes), item.mime);
return new types.NotebookCellOutputItem(item.valueBytes.buffer, item.mime);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export class InteractiveDocumentContribution extends Disposable implements IWork
outputId: output.outputId,
outputs: output.outputs.map(ot => ({
mime: ot.mime,
data: Uint8Array.from(ot.data)
data: ot.data
}))
}))
: [],
Expand Down Expand Up @@ -145,7 +145,7 @@ export class InteractiveDocumentContribution extends Disposable implements IWork
outputId: output.outputId,
outputs: output.outputs.map(ot => ({
mime: ot.mime,
data: Array.from(ot.data)
data: ot.data
}))
};
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ workbenchContributionsRegistry.registerWorkbenchContribution(NotebookCodeRendere
// --- utils ---
function getStringValue(item: IOutputItemDto): string {
// todo@jrieken NOT proper, should be VSBuffer
return new TextDecoder().decode(item.data);
return new TextDecoder().decode(item.data.buffer);
}

function getOutputSimpleEditorOptions(): IEditorConstructionOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -515,12 +515,12 @@ function outputsEqual(original: ICellOutput[], modified: ICellOutput[]) {
return false;
}

if (aOutputItem.data.length !== bOutputItem.data.length) {
if (aOutputItem.data.buffer.length !== bOutputItem.data.buffer.length) {
return false;
}

for (let k = 0; k < aOutputItem.data.length; k++) {
if (aOutputItem.data[k] !== bOutputItem.data[k]) {
for (let k = 0; k < aOutputItem.data.buffer.length; k++) {
if (aOutputItem.data.buffer[k] !== bOutputItem.data.buffer[k]) {
return false;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ class CellInfoContentProvider {
const sameStream = !outputs.find(op => op.mime !== mime);

if (sameStream) {
return outputs.map(opit => new TextDecoder().decode(opit.data)).join('');
return outputs.map(opit => new TextDecoder().decode(opit.data.buffer)).join('');
} else {
return null;
}
Expand Down Expand Up @@ -411,7 +411,7 @@ class CellInfoContentProvider {
metadata: output.metadata,
outputItems: output.outputs.map(opit => ({
mimeType: opit.mime,
data: new TextDecoder().decode(opit.data)
data: new TextDecoder().decode(opit.data.buffer)
}))
})));
const edits = format(content, undefined, {});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ class ImgRendererContrib extends Disposable implements IOutputRendererContributi
render(output: ICellOutputViewModel, item: IOutputItemDto, container: HTMLElement, notebookUri: URI): IRenderOutput {
const disposable = new DisposableStore();

const blob = new Blob([item.data], { type: item.mime });
const blob = new Blob([item.data.buffer], { type: item.mime });
const src = URL.createObjectURL(blob);
disposable.add(toDisposable(() => URL.revokeObjectURL(src)));

Expand All @@ -281,6 +281,5 @@ OutputRendererRegistry.registerOutputTransform(StderrRendererContrib);

// --- utils ---
export function getStringValue(item: IOutputItemDto): string {
// todo@jrieken NOT proper, should be VSBuffer
return new TextDecoder().decode(item.data);
return item.data.buffer.toString();
}
Original file line number Diff line number Diff line change
Expand Up @@ -979,7 +979,7 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
type: RenderOutputType.Extension,
outputId: output.outputId,
mimeType: first.mime,
valueBytes: first.data,
valueBytes: first.data.buffer,
metadata: output.metadata,
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1016,7 +1016,7 @@ class OutputSequence implements ISequence {
return this.outputs.map(output => {
return hash(output.outputs.map(output => ({
mime: output.mime,
data: Array.from(output.data)
data: output.data
})));
});
}
Expand Down
3 changes: 2 additions & 1 deletion src/vs/workbench/contrib/notebook/common/notebookCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IDiffResult, ISequence } from 'vs/base/common/diff/diff';
import { Event } from 'vs/base/common/event';
Expand Down Expand Up @@ -157,7 +158,7 @@ export interface IOrderedMimeType {

export interface IOutputItemDto {
readonly mime: string;
readonly data: Uint8Array;
readonly data: VSBuffer;
}

export interface IOutputDto {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class MirrorCell {
this._hash = hash([hash(this.language), hash(this.getValue()), this.metadata, this.internalMetadata, this.outputs.map(op => ({
outputs: op.outputs.map(output => ({
mime: output.mime,
data: Array.from(output.data)
data: output.data
})),
metadata: op.metadata
}))]);
Expand Down
29 changes: 15 additions & 14 deletions src/vs/workbench/contrib/notebook/test/notebookDiff.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import * as assert from 'assert';
import { VSBuffer } from 'vs/base/common/buffer';
import { LcsDiff } from 'vs/base/common/diff/diff';
import { Mimes } from 'vs/base/common/mime';
import { NotebookDiffEditorEventDispatcher } from 'vs/workbench/contrib/notebook/browser/diff/eventDispatcher';
Expand All @@ -15,9 +16,9 @@ suite('NotebookCommon', () => {

test('diff different source', async () => {
await withTestNotebookDiffModel([
['x', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: new Uint8Array([3]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }],
['x', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([3])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }],
], [
['y', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: new Uint8Array([3]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }],
['y', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([3])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }],
], (model, accessor) => {
const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook));
const diffResult = diff.ComputeDiff(false);
Expand Down Expand Up @@ -45,10 +46,10 @@ suite('NotebookCommon', () => {

test('diff different output', async () => {
await withTestNotebookDiffModel([
['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([5]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 5 }],
['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([5])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 5 }],
['', 'javascript', CellKind.Code, [], {}]
], [
['x', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: new Uint8Array([3]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }],
['x', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([3])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }],
['', 'javascript', CellKind.Code, [], {}]
], (model, accessor) => {
const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook));
Expand Down Expand Up @@ -146,12 +147,12 @@ suite('NotebookCommon', () => {

test('diff foo/foe', async () => {
await withTestNotebookDiffModel([
[['def foe(x, y):\n', ' return x + y\n', 'foe(3, 2)'].join(''), 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([6]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 5 }],
[['def foo(x, y):\n', ' return x * y\n', 'foo(1, 2)'].join(''), 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([2]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 6 }],
[['def foe(x, y):\n', ' return x + y\n', 'foe(3, 2)'].join(''), 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([6])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 5 }],
[['def foo(x, y):\n', ' return x * y\n', 'foo(1, 2)'].join(''), 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([2])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 6 }],
['', 'javascript', CellKind.Code, [], {}]
], [
[['def foo(x, y):\n', ' return x * y\n', 'foo(1, 2)'].join(''), 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([6]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 5 }],
[['def foe(x, y):\n', ' return x + y\n', 'foe(3, 2)'].join(''), 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([2]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 6 }],
[['def foo(x, y):\n', ' return x * y\n', 'foo(1, 2)'].join(''), 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([6])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 5 }],
[['def foe(x, y):\n', ' return x + y\n', 'foe(3, 2)'].join(''), 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([2])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 6 }],
['', 'javascript', CellKind.Code, [], {}]
], (model, accessor) => {
const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook));
Expand Down Expand Up @@ -273,13 +274,13 @@ suite('NotebookCommon', () => {
await withTestNotebookDiffModel([
['# Description', 'markdown', CellKind.Markup, [], { custom: { metadata: {} } }],
['x = 3', 'javascript', CellKind.Code, [], { custom: { metadata: { collapsed: true } }, executionOrder: 1 }],
['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([3]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 1 }],
['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([3])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 1 }],
['x', 'javascript', CellKind.Code, [], { custom: { metadata: { collapsed: false } } }]
], [
['# Description', 'markdown', CellKind.Markup, [], { custom: { metadata: {} } }],
['x = 3', 'javascript', CellKind.Code, [], { custom: { metadata: { collapsed: true } }, executionOrder: 1 }],
['x', 'javascript', CellKind.Code, [], { custom: { metadata: { collapsed: false } } }],
['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([3]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 1 }]
['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([3])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 1 }]
], async (model) => {
const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook));
const diffResult = diff.ComputeDiff(false);
Expand All @@ -306,18 +307,18 @@ suite('NotebookCommon', () => {
await withTestNotebookDiffModel([
['# Description', 'markdown', CellKind.Markup, [], { custom: { metadata: {} } }],
['x = 3', 'javascript', CellKind.Code, [], { custom: { metadata: { collapsed: true } }, executionOrder: 1 }],
['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([3]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 1 }],
['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([3])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 1 }],
['x', 'javascript', CellKind.Code, [], { custom: { metadata: { collapsed: false } } }],
['x = 5', 'javascript', CellKind.Code, [], {}],
['x', 'javascript', CellKind.Code, [], {}],
['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([5]) }] }], {}],
['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([5])) }] }], {}],
], [
['# Description', 'markdown', CellKind.Markup, [], { custom: { metadata: {} } }],
['x = 3', 'javascript', CellKind.Code, [], { custom: { metadata: { collapsed: true } }, executionOrder: 1 }],
['x', 'javascript', CellKind.Code, [], { custom: { metadata: { collapsed: false } } }],
['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([3]) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 1 }],
['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([3])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 1 }],
['x = 5', 'javascript', CellKind.Code, [], {}],
['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: new Uint8Array([5]) }] }], {}],
['x', 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([5])) }] }], {}],
['x', 'javascript', CellKind.Code, [], {}],
], async (model) => {
const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import * as assert from 'assert';
import { VSBuffer } from 'vs/base/common/buffer';
import { Mimes } from 'vs/base/common/mime';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
Expand Down Expand Up @@ -426,7 +427,7 @@ suite('NotebookTextModel', () => {
const success1 = model.applyEdits(
[{
editType: CellEditType.Output, index: 0, outputs: [
{ outputId: 'out1', outputs: [{ mime: 'application/x.notebook.stream', data: new Uint8Array([1]) }] }
{ outputId: 'out1', outputs: [{ mime: 'application/x.notebook.stream', data: VSBuffer.wrap(new Uint8Array([1])) }] }
],
append: false
}], true, undefined, () => undefined, undefined, false
Expand All @@ -438,7 +439,7 @@ suite('NotebookTextModel', () => {
const success2 = model.applyEdits(
[{
editType: CellEditType.Output, index: 0, outputs: [
{ outputId: 'out2', outputs: [{ mime: 'application/x.notebook.stream', data: new Uint8Array([1]) }] }
{ outputId: 'out2', outputs: [{ mime: 'application/x.notebook.stream', data: VSBuffer.wrap(new Uint8Array([1])) }] }
],
append: true
}], true, undefined, () => undefined, undefined, false
Expand All @@ -465,7 +466,7 @@ suite('NotebookTextModel', () => {
const success = model.applyEdits(
[{
editType: CellEditType.Output, index: 0, outputs: [
{ outputId: 'out1', outputs: [{ mime: 'application/x.notebook.stream', data: new Uint8Array([1]) }] }
{ outputId: 'out1', outputs: [{ mime: 'application/x.notebook.stream', data: VSBuffer.wrap(new Uint8Array([1])) }] }
],
append: false
}], true, undefined, () => undefined, undefined, false
Expand Down
5 changes: 3 additions & 2 deletions src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOp
import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext';
import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer';
import { Mimes } from 'vs/base/common/mime';
import { VSBuffer } from 'vs/base/common/buffer';

export class TestCell extends NotebookCellTextModel {
constructor(
Expand Down Expand Up @@ -345,6 +346,6 @@ export function createNotebookCellList(instantiationService: TestInstantiationSe
return cellList;
}

export function valueBytesFromString(value: string) {
return new TextEncoder().encode(value);
export function valueBytesFromString(value: string): VSBuffer {
return VSBuffer.wrap(new TextEncoder().encode(value));
}
Loading

0 comments on commit 78152ae

Please sign in to comment.