Skip to content

WIP: Typed array encoding in supplyDefaults with Caching #5308

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Nov 29, 2020
108 changes: 108 additions & 0 deletions src/lib/array.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
*/

'use strict';
var b64 = require('base64-arraybuffer');
var isPlainObject = require('./is_plain_object');

var isArray = Array.isArray;

Expand Down Expand Up @@ -63,6 +65,112 @@ exports.ensureArray = function(out, n) {
return out;
};

var typedArrays = {
int8: typeof Int8Array !== 'undefined' ? Int8Array : null,
uint8: typeof Uint8Array !== 'undefined' ? Uint8Array : null,
uint8clamped: typeof Uint8ClampedArray !== 'undefined' ? Uint8ClampedArray : null,
int16: typeof Int16Array !== 'undefined' ? Int16Array : null,
uint16: typeof Uint16Array !== 'undefined' ? Uint16Array : null,
int32: typeof Int32Array !== 'undefined' ? Int32Array : null,
uint32: typeof Uint32Array !== 'undefined' ? Uint32Array : null,
float32: typeof Float32Array !== 'undefined' ? Float32Array : null,
float64: typeof Float64Array !== 'undefined' ? Float64Array : null,
};
exports.typedArrays = typedArrays;


exports.decodeTypedArraySpec = function(v) {
// Assume processed by coerceTypedArraySpec
v = coerceTypedArraySpec(v);
var T = typedArrays[v.dtype];
var buffer;
if(v.bvals.constructor === ArrayBuffer) {
// Already an ArrayBuffer
buffer = v.bvals;
} else {
// Decode, assuming a string
buffer = b64.decode(v.bvals);
}

// Check if 1d shape. If so, we're done
if(v.ndims === 1) {
// Construct single Typed array over entire buffer
return new T(buffer);
} else {
// Reshape into nested plain arrays with innermost
// level containing typed arrays
// We could eventually adopt an ndarray library

// Build cumulative product of dimensions
var cumulativeShape = v.shape.map(function(a, i) {
return a * (v.shape[i - 1] || 1);
});

// Loop of dimensions in reverse order
var nestedArray = [];
for(var dimInd = v.ndims - 1; dimInd > 0; dimInd--) {
var subArrayLength = v.shape[dimInd];
var numSubArrays = cumulativeShape[dimInd - 1];
var nextArray = [];

if(dimInd === v.ndims - 1) {
// First time through, we build the
// inner most typed arrays
for(var typedInd = 0; typedInd < numSubArrays; typedInd++) {
var typedOffset = typedInd * subArrayLength;
nextArray.push(
new T(buffer, typedOffset * T.BYTES_PER_ELEMENT, subArrayLength)
);
}
} else {
// Following times through, build
// next layer of nested arrays
for(var i = 0; i < numSubArrays; i++) {
var offset = i * subArrayLength;
nextArray.push(nextArray.slice(offset, offset + subArrayLength - 1));
}
}

// Update nested array with next nesting level
nestedArray = nextArray;
}

return nestedArray;
}
};

function isTypedArraySpec(v) {
// Assume v has not passed through
return isPlainObject(v) && typedArrays[v.dtype] && v.bvals && (
Number.isInteger(v.shape) ||
(isArrayOrTypedArray(v.shape) &&
v.shape.length > 0 &&
v.shape.every(function(d) { return Number.isInteger(d); }))
);
}
exports.isTypedArraySpec = isTypedArraySpec;

function coerceTypedArraySpec(v) {
// Assume isTypedArraySpec passed
var coerced = {dtype: v.dtype, bvals: v.bvals};

// Normalize shape to a list
if(Number.isInteger(v.shape)) {
coerced.shape = [v.shape];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we possibly support numeric strings here as well?

} else {
coerced.shape = v.shape;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a good idea to drop this array support for shape attribute; while it cannot be a typedArray itself.
In that case for n dimensional arrays one could pass an string containing comma (or X) separated integers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment, I am using the shape to build a multi-dimensional array when the length is more than 1. Only the inner most layer of the multi-dimensional array is made up of typed arrays, but I pretty sure it'll be a lot better than nothing.

}

// Add length property
coerced.length = v.shape.reduce(function(a, b) { return a * b; });

// Add ndims
coerced.ndims = v.shape.length;

return coerced;
}
exports.coerceTypedArraySpec = coerceTypedArraySpec;

/*
* TypedArray-compatible concatenation of n arrays
* if all arrays are the same type it will preserve that type,
Expand Down
60 changes: 41 additions & 19 deletions src/lib/coerce.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,11 @@ var DESELECTDIM = require('../constants/interactions').DESELECTDIM;
var nestedProperty = require('./nested_property');
var counterRegex = require('./regex').counter;
var modHalf = require('./mod').modHalf;
var isPlainObject = require('./is_plain_object');
var isArrayOrTypedArray = require('./array').isArrayOrTypedArray;
var isTypedArraySpec = require('./array').isTypedArraySpec;
var decodeTypedArraySpec = require('./array').decodeTypedArraySpec;
var coerceTypedArraySpec = require('./array').coerceTypedArraySpec;

var typedArrays = {
int8: typeof Int8Array !== 'undefined' ? 1 : 0,
uint8: typeof Uint8Array !== 'undefined' ? 1 : 0,
uint8clamped: typeof Uint8ClampedArray !== 'undefined' ? 1 : 0,
int16: typeof Int16Array !== 'undefined' ? 1 : 0,
uint16: typeof Uint16Array !== 'undefined' ? 1 : 0,
int32: typeof Int32Array !== 'undefined' ? 1 : 0,
uint32: typeof Uint32Array !== 'undefined' ? 1 : 0,
float32: typeof Float32Array !== 'undefined' ? 1 : 0,
float64: typeof Float64Array !== 'undefined' ? 1 : 0,
bigint64: typeof BigInt64Array !== 'undefined' ? 1 : 0,
biguint64: typeof BigUint64Array !== 'undefined' ? 1 : 0
};

exports.valObjectMeta = {
data_array: {
Expand All @@ -53,12 +42,45 @@ exports.valObjectMeta = {
if(isArrayOrTypedArray(v)) {
propOut.set(v);
wasSet = true;
} else if(isPlainObject(v)) {
var T = typedArrays[v.dtype];
if(T) {
propOut.set(v);
wasSet = true;
} else if(isTypedArraySpec(v)) {
// Copy and coerce spec
v = coerceTypedArraySpec(v);

// See if caching location is available
var uid = propOut.obj.uid;
var module = propOut.obj._module;

if(v.bvals.constructor === ArrayBuffer || !uid || !module) {
// Already an ArrayBuffer
// decoding is cheap
propOut.set(decodeTypedArraySpec(v));
} else {
var prop = propOut.astr;
var cache = module._b64BufferCache || {};

// Check cache
var cachedBuffer = ((cache[uid] || {})[prop] || {})[v.bvals];
if(cachedBuffer !== undefined) {
// Use cached array buffer instead of base64 encoded
// string
v.bvals = cachedBuffer;
propOut.set(decodeTypedArraySpec(v));
} else {
// Not in so cache decode
var decoded = decodeTypedArraySpec(v);
propOut.set(decoded);

// Update cache for next time
cache[uid] = (cache[uid] || {});

// Clear any prior cache value (only store one per
// trace property
cache[uid][prop] = {};
cache[uid][prop][v.bvals] = decoded.buffer;
module._b64BufferCache = cache;
}
}
wasSet = true;
}
if(!wasSet && dflt !== undefined) propOut.set(dflt);
}
Expand Down
44 changes: 0 additions & 44 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
var d3 = require('d3');
var timeFormatLocale = require('d3-time-format').timeFormatLocale;
var isNumeric = require('fast-isnumeric');
var b64 = require('base64-arraybuffer');

var Registry = require('../registry');
var PlotSchema = require('../plot_api/plot_schema');
Expand Down Expand Up @@ -2849,50 +2848,7 @@ function _transition(gd, transitionOpts, opts) {
return transitionStarting.then(function() { return gd; });
}

var typedArrays = {
int8: typeof Int8Array !== 'undefined' ? Int8Array : null,
uint8: typeof Uint8Array !== 'undefined' ? Uint8Array : null,
uint8clamped: typeof Uint8ClampedArray !== 'undefined' ? Uint8ClampedArray : null,
int16: typeof Int16Array !== 'undefined' ? Int16Array : null,
uint16: typeof Uint16Array !== 'undefined' ? Uint16Array : null,
int32: typeof Int32Array !== 'undefined' ? Int32Array : null,
uint32: typeof Uint32Array !== 'undefined' ? Uint32Array : null,
float32: typeof Float32Array !== 'undefined' ? Float32Array : null,
float64: typeof Float64Array !== 'undefined' ? Float64Array : null,
bigint64: typeof BigInt64Array !== 'undefined' ? BigInt64Array : null,
biguint64: typeof BigUint64Array !== 'undefined' ? BigUint64Array : null
};

function _decode(cont) {
if(cont.dtype && cont.bvals) {
var T = typedArrays[cont.dtype];
if(T) {
return new T(b64.decode(cont.bvals));
}
}

for(var prop in cont) {
if(prop[0] !== '_' && cont.hasOwnProperty(prop)) {
var item = cont[prop];
if(Lib.isPlainObject(item)) {
var r = _decode(item);
if(r !== undefined) cont[prop] = r;
}
}
}
}

function decodeB64Arrays(gd) {
for(var i = 0; i < gd._fullData.length; i++) {
_decode(gd._fullData[i]);
}

_decode(gd._fullLayout);
}

plots.doCalcdata = function(gd, traces) {
decodeB64Arrays(gd);

var axList = axisIDs.list(gd);
var fullData = gd._fullData;
var fullLayout = gd._fullLayout;
Expand Down
16 changes: 5 additions & 11 deletions src/traces/heatmap/xyz_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,19 @@ module.exports = function handleXYZDefaults(traceIn, traceOut, coerce, layout, x
yName = yName || 'y';
var x, y;

var shapeX = x ? (x.shape ? x.shape[0] : x.length) || 0 : 0;
var shapeY = y ? (y.shape ? y.shape[0] : y.length) || 0 : 0;
var shapeZ = z ? (z.shape ? z.shape[0] : z.length) || 0 : 0;
if(z === undefined || !z.length) return 0;

var zlen = shapeZ || (z && z.length) || 0;

if(z === undefined || !zlen) return 0;

if(Lib.isArray1D(traceIn.z) || (z && z.shape && z.shape.length === 1)) {
if(Lib.isArray1D(z)) {
x = coerce(xName);
y = coerce(yName);

var xlen = shapeX || Lib.minRowLength(x);
var ylen = shapeY || Lib.minRowLength(y);
var xlen = Lib.minRowLength(x);
var ylen = Lib.minRowLength(y);

// column z must be accompanied by xName and yName arrays
if(xlen === 0 || ylen === 0) return 0;

traceOut._length = Math.min(xlen, ylen, zlen);
traceOut._length = Math.min(xlen, ylen, z.length);
} else {
x = coordDefaults(xName, coerce);
y = coordDefaults(yName, coerce);
Expand Down
18 changes: 6 additions & 12 deletions src/traces/isosurface/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,12 @@ function supplyIsoDefaults(traceIn, traceOut, defaultColor, layout, coerce) {
var z = coerce('z');
var value = coerce('value');

var len = 0;

if(x && y && z && value) {
len = Math.min(
(x.shape ? x.shape[0] : x.length) || 0,
(y.shape ? y.shape[0] : y.length) || 0,
(z.shape ? z.shape[0] : z.length) || 0,
(value.shape ? value.shape[0] : value.length) || 0
);
}

if(!len) {
if(
!x || !x.length ||
!y || !y.length ||
!z || !z.length ||
!value || !value.length
) {
traceOut.visible = false;
return;
}
Expand Down
11 changes: 4 additions & 7 deletions src/traces/scatter/xy_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,19 @@ module.exports = function handleXYDefaults(traceIn, traceOut, layout, coerce) {
var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout);

var shapeX = x && x.shape ? x.shape[0] : 0;
var shapeY = y && y.shape ? y.shape[0] : 0;

if(x) {
var xlen = shapeX || Lib.minRowLength(x);
var xlen = Lib.minRowLength(x);
if(y) {
len = shapeY || Math.min(xlen, Lib.minRowLength(y));
len = Math.min(xlen, Lib.minRowLength(y));
} else {
len = shapeX || xlen;
len = xlen;
coerce('y0');
coerce('dy');
}
} else {
if(!y) return 0;

len = shapeY || Lib.minRowLength(y);
len = Lib.minRowLength(y);
coerce('x0');
coerce('dx');
}
Expand Down
6 changes: 1 addition & 5 deletions src/traces/scatter3d/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,8 @@ function handleXYZDefaults(traceIn, traceOut, coerce, layout) {
handleCalendarDefaults(traceIn, traceOut, ['x', 'y', 'z'], layout);

if(x && y && z) {
var shapeX = (x.shape ? x.shape[0] : x.length) || 0;
var shapeY = (y.shape ? y.shape[0] : y.length) || 0;
var shapeZ = (z.shape ? z.shape[0] : z.length) || 0;

// TODO: what happens if one is missing?
len = Math.min(shapeX, shapeY, shapeZ);
len = Math.min(x.length, y.length, z.length);
traceOut._length = traceOut._xlength = traceOut._ylength = traceOut._zlength = len;
}

Expand Down