aboutsummaryrefslogtreecommitdiff
path: root/ext/wasm/api/sqlite3-api-opfs.js
diff options
context:
space:
mode:
authorstephan <stephan@noemail.net>2022-09-20 13:25:39 +0000
committerstephan <stephan@noemail.net>2022-09-20 13:25:39 +0000
commitb8c8d4e4a077d0f877b189a739fc3293bf585aec (patch)
tree22a6438357ac79bde4199c70a5501800a8b13903 /ext/wasm/api/sqlite3-api-opfs.js
parentc2ccd676890392b1850bdb17d1f22233fb9c0570 (diff)
downloadsqlite-b8c8d4e4a077d0f877b189a739fc3293bf585aec.tar.gz
sqlite-b8c8d4e4a077d0f877b189a739fc3293bf585aec.zip
Speed up de/serialization of func args and return values in the OPFS VFS proxy.
FossilOrigin-Name: 5bf235bbe035e4ace7a54851e190742528af6b4266328a1b8bbb9fb3dd7f2118
Diffstat (limited to 'ext/wasm/api/sqlite3-api-opfs.js')
-rw-r--r--ext/wasm/api/sqlite3-api-opfs.js127
1 files changed, 96 insertions, 31 deletions
diff --git a/ext/wasm/api/sqlite3-api-opfs.js b/ext/wasm/api/sqlite3-api-opfs.js
index dd61ee0a2..1f50b4631 100644
--- a/ext/wasm/api/sqlite3-api-opfs.js
+++ b/ext/wasm/api/sqlite3-api-opfs.js
@@ -152,6 +152,7 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri)
"metrics for",self.location.href,":",metrics,
"\nTotal of",n,"op(s) for",t,
"ms (incl. "+w+" ms of waiting on the async side)");
+ console.log("Serialization metrics:",JSON.stringify(metrics.s11n,0,2));
},
reset: function(){
let k;
@@ -159,6 +160,11 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri)
for(k in state.opIds){
r(metrics[k] = Object.create(null));
}
+ let s = metrics.s11n = Object.create(null);
+ s = s.serialize = Object.create(null);
+ s.count = s.time = 0;
+ s = metrics.s11n.deserialize = Object.create(null);
+ s.count = s.time = 0;
//[ // timed routines which are not in state.opIds
// 'xFileControl'
//].forEach((k)=>r(metrics[k] = Object.create(null)));
@@ -314,56 +320,115 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri)
};
const initS11n = ()=>{
- // Achtung: this code is 100% duplicated in the other half of this proxy!
+ /**
+ ACHTUNG: this code is 100% duplicated in the other half of
+ this proxy!
+
+ Historical note: this impl was initially about 5% this size by using
+ using JSON.stringify/parse(), but using fit-to-purpose serialization
+ saves considerable runtime.
+ */
if(state.s11n) return state.s11n;
- const jsonDecoder = new TextDecoder(),
- jsonEncoder = new TextEncoder('utf-8'),
- viewSz = new DataView(state.sabIO, state.sabS11nOffset, 4),
- viewJson = new Uint8Array(state.sabIO, state.sabS11nOffset+4, state.sabS11nSize-4);
+ const textDecoder = new TextDecoder(),
+ textEncoder = new TextEncoder('utf-8'),
+ viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize),
+ viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
state.s11n = Object.create(null);
+ const TypeIds = Object.create(null);
+ TypeIds.number = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' };
+ TypeIds.bigint = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' };
+ TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' };
+ TypeIds.string = { id: 4 };
+ const getTypeId = (v)=>{
+ return TypeIds[typeof v] || toss("This value type cannot be serialized.",v);
+ };
+ const getTypeIdById = (tid)=>{
+ switch(tid){
+ case TypeIds.number.id: return TypeIds.number;
+ case TypeIds.bigint.id: return TypeIds.bigint;
+ case TypeIds.boolean.id: return TypeIds.boolean;
+ case TypeIds.string.id: return TypeIds.string;
+ default: toss("Invalid type ID:",tid);
+ }
+ };
/**
Returns an array of the state serialized by the most recent
serialize() operation (here or in the counterpart thread), or
null if the serialization buffer is empty.
*/
state.s11n.deserialize = function(){
- const sz = viewSz.getInt32(0, state.littleEndian);
- const json = sz ? jsonDecoder.decode(
- viewJson.slice(0, sz)
- /* slice() (copy) needed, instead of subarray() (reference),
- because TextDecoder throws if asked to decode from an
- SAB. */
- ) : null;
- return JSON.parse(json);
- }
+ ++metrics.s11n.deserialize.count;
+ const t = performance.now();
+ let rc = null;
+ const argc = viewU8[0];
+ if(argc){
+ rc = [];
+ let offset = 1, i, n, v, typeIds = [];
+ for(i = 0; i < argc; ++i, ++offset){
+ typeIds.push(getTypeIdById(viewU8[offset]));
+ }
+ for(i = 0; i < argc; ++i){
+ const t = typeIds[i];
+ if(t.getter){
+ v = viewDV[t.getter](offset, state.littleEndian);
+ offset += t.size;
+ }else{
+ n = viewDV.getInt32(offset, state.littleEndian);
+ offset += 4;
+ v = textDecoder.decode(viewU8.slice(offset, offset+n));
+ offset += n;
+ }
+ rc.push(v);
+ }
+ }
+ //log("deserialize:",argc, rc);
+ metrics.s11n.deserialize.time += performance.now() - t;
+ return rc;
+ };
/**
Serializes all arguments to the shared buffer for consumption
- by the counterpart thread. This impl currently uses JSON for
- serialization for simplicy of implementation, but if that
- proves imperformant then a lower-level approach will be
- created.
-
- If passed "too much data" (more that the shared buffer size
- it will either throw or truncate the data (not certain
- which)). This routine is only intended for serializing OPFS
- VFS arguments and (in at least one special case) result
- values, and the buffer is sized to be able to comfortably
- handle those.
+ by the counterpart thread.
+
+ This routine is only intended for serializing OPFS VFS
+ arguments and (in at least one special case) result values,
+ and the buffer is sized to be able to comfortably handle
+ those.
If passed no arguments then it zeroes out the serialization
state.
*/
state.s11n.serialize = function(...args){
+ ++metrics.s11n.serialize.count;
+ const t = performance.now();
if(args.length){
- const json = jsonEncoder.encode(JSON.stringify(args));
- viewSz.setInt32(0, json.byteLength, state.littleEndian);
- viewJson.set(json);
+ //log("serialize():",args);
+ let i = 0, offset = 1, typeIds = [];
+ viewU8[0] = args.length & 0xff;
+ for(; i < args.length; ++i, ++offset){
+ typeIds.push(getTypeId(args[i]));
+ viewU8[offset] = typeIds[i].id;
+ }
+ for(i = 0; i < args.length; ++i) {
+ const t = typeIds[i];
+ if(t.setter){
+ viewDV[t.setter](offset, args[i], state.littleEndian);
+ offset += t.size;
+ }else{
+ const s = textEncoder.encode(args[i]);
+ viewDV.setInt32(offset, s.byteLength, state.littleEndian);
+ offset += 4;
+ viewU8.set(s, offset);
+ offset += s.byteLength;
+ }
+ }
+ //log("serialize() result:",viewU8.slice(0,offset));
}else{
- viewSz.setInt32(0, 0, state.littleEndian);
+ viewU8[0] = 0;
}
+ metrics.s11n.serialize.time += performance.now() - t;
};
return state.s11n;
- };
+ }/*initS11n()*/;
/**
Generates a random ASCII string len characters long, intended for
@@ -762,7 +827,7 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri)
//| capi.SQLITE_OPEN_DELETEONCLOSE
| capi.SQLITE_OPEN_MAIN_DB;
const pOut = wasm.scopedAlloc(8);
- const dbFile = "/sanity/check/file";
+ const dbFile = "/sanity/check/file"+randomFilename(8);
const zDbFile = wasm.scopedAllocCString(dbFile);
let rc;
vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);