@@ -31,7 +31,17 @@ const defaultIterationCount = 120;
31
31
const defaultWorstCaseCount = 4 ;
32
32
33
33
if ( ! JetStreamParams . prefetchResources )
34
- console . warn ( "Disabling resource prefetching!" ) ;
34
+ console . warn ( "Disabling resource prefetching! All compressed files must have been decompressed using `npm run decompress`" ) ;
35
+
36
+ if ( ! isInBrowser && JetStreamParams . prefetchResources ) {
37
+ // Use the wasm compiled zlib as a polyfill when decompression stream is
38
+ // not available in JS shells.
39
+ load ( "./wasm/zlib/shell.js" ) ;
40
+
41
+ // Load a polyfill for TextEncoder/TextDecoder in shells. Used when
42
+ // decompressing a prefetched resource and converting it to text.
43
+ load ( "./polyfills/fast-text-encoding/1.0.3/text.js" ) ;
44
+ }
35
45
36
46
// Used for the promise representing the current benchmark run.
37
47
this . currentResolve = null ;
@@ -138,6 +148,20 @@ function uiFriendlyDuration(time) {
138
148
return `${ time . toFixed ( 3 ) } ms` ;
139
149
}
140
150
151
+ // Files can be zlib compressed to reduce the size of the JetStream source code.
152
+ // We don't use http compression because we support running from the shell and
153
+ // don't want to require a complicated server setup.
154
+ //
155
+ // zlib was chosen because we already have it in tree for the wasm-zlib test.
156
+ function isCompressed ( name ) {
157
+ return name . endsWith ( ".z" ) ;
158
+ }
159
+
160
+ function uncompressedName ( name ) {
161
+ console . assert ( isCompressed ( name ) ) ;
162
+ return name . slice ( 0 , - 2 ) ;
163
+ }
164
+
141
165
// TODO: Cleanup / remove / merge. This is only used for caching loads in the
142
166
// non-browser setting. In the browser we use exclusively `loadCache`,
143
167
// `loadBlob`, `doLoadBlob`, `prefetchResourcesForBrowser` etc., see below.
@@ -150,14 +174,28 @@ class ShellFileLoader {
150
174
// share common code.
151
175
load ( url ) {
152
176
console . assert ( ! isInBrowser ) ;
177
+
178
+ let compressed = isCompressed ( url ) ;
179
+ if ( compressed && ! JetStreamParams . prefetchResources ) {
180
+ url = uncompressedName ( url ) ;
181
+ }
182
+
183
+ // If we aren't supposed to prefetch this then return code snippet that will load the url on-demand.
153
184
if ( ! JetStreamParams . prefetchResources )
154
185
return `load("${ url } ");`
155
186
156
187
if ( this . requests . has ( url ) ) {
157
188
return this . requests . get ( url ) ;
158
189
}
159
190
160
- const contents = readFile ( url ) ;
191
+ let contents ;
192
+ if ( compressed ) {
193
+ const compressedBytes = new Int8Array ( read ( url , "binary" ) ) ;
194
+ const decompressedBytes = zlib . decompress ( compressedBytes ) ;
195
+ contents = new TextDecoder ( ) . decode ( decompressedBytes ) ;
196
+ } else {
197
+ contents = readFile ( url ) ;
198
+ }
161
199
this . requests . set ( url , contents ) ;
162
200
return contents ;
163
201
}
@@ -209,10 +247,14 @@ class Driver {
209
247
performance . mark ( "update-ui" ) ;
210
248
benchmark . updateUIAfterRun ( ) ;
211
249
212
- if ( isInBrowser && JetStreamParams . prefetchResources ) {
250
+ if ( isInBrowser ) {
213
251
const cache = JetStream . blobDataCache ;
214
252
for ( const file of benchmark . files ) {
215
253
const blobData = cache [ file ] ;
254
+ // If we didn't prefetch this resource, then no need to free it
255
+ if ( ! blobData . blob ) {
256
+ continue
257
+ }
216
258
blobData . refCount -- ;
217
259
if ( ! blobData . refCount )
218
260
cache [ file ] = undefined ;
@@ -338,6 +380,9 @@ class Driver {
338
380
339
381
async prefetchResources ( ) {
340
382
if ( ! isInBrowser ) {
383
+ if ( JetStreamParams . prefetchResources ) {
384
+ await zlib . initialize ( ) ;
385
+ }
341
386
for ( const benchmark of this . benchmarks )
342
387
benchmark . prefetchResourcesForShell ( ) ;
343
388
return ;
@@ -553,6 +598,11 @@ class Scripts {
553
598
}
554
599
555
600
class ShellScripts extends Scripts {
601
+ constructor ( ) {
602
+ super ( ) ;
603
+ this . prefetchedResources = Object . create ( null ) ; ;
604
+ }
605
+
556
606
run ( ) {
557
607
let globalObject ;
558
608
let realm ;
@@ -579,13 +629,32 @@ class ShellScripts extends Scripts {
579
629
currentReject
580
630
} ;
581
631
632
+ // Pass the prefetched resources to the benchmark global.
633
+ if ( JetStreamParams . prefetchResources ) {
634
+ // Pass the 'TextDecoder' polyfill into the benchmark global. Don't
635
+ // use 'TextDecoder' as that will get picked up in the kotlin test
636
+ // without full support.
637
+ globalObject . ShellTextDecoder = TextDecoder ;
638
+ // Store shellPrefetchedResources on ShellPrefetchedResources so that
639
+ // getBinary and getString can find them.
640
+ globalObject . ShellPrefetchedResources = this . prefetchedResources ;
641
+ } else {
642
+ console . assert ( Object . values ( this . prefetchedResources ) . length === 0 , "Unexpected prefetched resources" ) ;
643
+ }
644
+
582
645
globalObject . performance ??= performance ;
583
646
for ( const script of this . scripts )
584
647
globalObject . loadString ( script ) ;
585
648
586
649
return isD8 ? realm : globalObject ;
587
650
}
588
651
652
+ addPrefetchedResources ( prefetchedResources ) {
653
+ for ( let [ file , bytes ] of Object . entries ( prefetchedResources ) ) {
654
+ this . prefetchedResources [ file ] = bytes ;
655
+ }
656
+ }
657
+
589
658
add ( text ) {
590
659
this . scripts . push ( text ) ;
591
660
}
@@ -618,7 +687,6 @@ class BrowserScripts extends Scripts {
618
687
return magicFrame ;
619
688
}
620
689
621
-
622
690
add ( text ) {
623
691
this . scripts . push ( `<script>${ text } </script>` ) ;
624
692
}
@@ -638,6 +706,7 @@ class Benchmark {
638
706
this . allowUtf16 = ! ! plan . allowUtf16 ;
639
707
this . scripts = null ;
640
708
this . preloads = null ;
709
+ this . shellPrefetchedResources = null ;
641
710
this . results = [ ] ;
642
711
this . _state = BenchmarkState . READY ;
643
712
}
@@ -771,6 +840,9 @@ class Benchmark {
771
840
if ( ! ! this . plan . exposeBrowserTest )
772
841
scripts . addBrowserTest ( ) ;
773
842
843
+ if ( this . shellPrefetchedResources ) {
844
+ scripts . addPrefetchedResources ( this . shellPrefetchedResources ) ;
845
+ }
774
846
if ( this . plan . preload ) {
775
847
let preloadCode = "" ;
776
848
for ( let [ variableName , blobURLOrPath ] of this . preloads )
@@ -789,7 +861,7 @@ class Benchmark {
789
861
} else {
790
862
const cache = JetStream . blobDataCache ;
791
863
for ( const file of this . plan . files ) {
792
- scripts . addWithURL ( JetStreamParams . prefetchResources ? cache [ file ] . blobURL : file ) ;
864
+ scripts . addWithURL ( cache [ file ] . blobURL ) ;
793
865
}
794
866
}
795
867
@@ -840,10 +912,19 @@ class Benchmark {
840
912
841
913
async doLoadBlob ( resource ) {
842
914
const blobData = JetStream . blobDataCache [ resource ] ;
915
+
916
+ const compressed = isCompressed ( resource ) ;
917
+ if ( compressed && ! JetStreamParams . prefetchResources ) {
918
+ resource = uncompressedName ( resource ) ;
919
+ }
920
+
921
+ // If we aren't supposed to prefetch this then set the blobURL to just
922
+ // be the resource URL.
843
923
if ( ! JetStreamParams . prefetchResources ) {
844
924
blobData . blobURL = resource ;
845
925
return blobData ;
846
926
}
927
+
847
928
let response ;
848
929
let tries = 3 ;
849
930
while ( tries -- ) {
@@ -859,7 +940,15 @@ class Benchmark {
859
940
continue ;
860
941
throw new Error ( "Fetch failed" ) ;
861
942
}
862
- const blob = await response . blob ( ) ;
943
+
944
+ // If we need to decompress this, then run it through a decompression
945
+ // stream.
946
+ if ( compressed ) {
947
+ const stream = response . body . pipeThrough ( new DecompressionStream ( "deflate" ) )
948
+ response = new Response ( stream ) ;
949
+ }
950
+
951
+ let blob = await response . blob ( ) ;
863
952
blobData . blob = blob ;
864
953
blobData . blobURL = URL . createObjectURL ( blob ) ;
865
954
return blobData ;
@@ -995,7 +1084,27 @@ class Benchmark {
995
1084
this . scripts = this . plan . files . map ( file => shellFileLoader . load ( file ) ) ;
996
1085
997
1086
console . assert ( this . preloads === null , "This initialization should be called only once." ) ;
998
- this . preloads = Object . entries ( this . plan . preload ?? { } ) ;
1087
+ this . preloads = [ ] ;
1088
+ this . shellPrefetchedResources = Object . create ( null ) ;
1089
+ if ( ! this . plan . preload ) {
1090
+ return ;
1091
+ }
1092
+ for ( let [ name , file ] of Object . entries ( this . plan . preload ) ) {
1093
+ const compressed = isCompressed ( file ) ;
1094
+ if ( compressed && ! JetStreamParams . prefetchResources ) {
1095
+ file = uncompressedName ( file ) ;
1096
+ }
1097
+
1098
+ if ( JetStreamParams . prefetchResources ) {
1099
+ let bytes = new Int8Array ( read ( file , "binary" ) ) ;
1100
+ if ( compressed ) {
1101
+ bytes = zlib . decompress ( bytes ) ;
1102
+ }
1103
+ this . shellPrefetchedResources [ file ] = bytes ;
1104
+ }
1105
+
1106
+ this . preloads . push ( [ name , file ] ) ;
1107
+ }
999
1108
}
1000
1109
1001
1110
scoreIdentifiers ( ) {
@@ -1268,15 +1377,23 @@ class AsyncBenchmark extends DefaultBenchmark {
1268
1377
} else {
1269
1378
str += `
1270
1379
JetStream.getBinary = async function(path) {
1380
+ if ("ShellPrefetchedResources" in globalThis) {
1381
+ return ShellPrefetchedResources[path];
1382
+ }
1271
1383
return new Int8Array(read(path, "binary"));
1272
1384
};
1273
1385
1274
1386
JetStream.getString = async function(path) {
1387
+ if ("ShellPrefetchedResources" in globalThis) {
1388
+ return new ShellTextDecoder().decode(ShellPrefetchedResources[path]);
1389
+ }
1275
1390
return read(path);
1276
1391
};
1277
1392
1278
1393
JetStream.dynamicImport = async function(path) {
1279
1394
try {
1395
+ // TODO: this skips the prefetched resources, but I'm
1396
+ // not sure of a way around that.
1280
1397
return await import(path);
1281
1398
} catch (e) {
1282
1399
// In shells, relative imports require different paths, so try with and
@@ -1493,7 +1610,11 @@ class WasmLegacyBenchmark extends Benchmark {
1493
1610
` ;
1494
1611
} else {
1495
1612
str += `
1496
- Module[key] = new Int8Array(read(path, "binary"));
1613
+ if (ShellPrefetchedResources) {
1614
+ Module[key] = ShellPrefetchedResources[path];
1615
+ } else {
1616
+ Module[key] = new Int8Array(read(path, "binary"));
1617
+ }
1497
1618
if (andThen == doRun) {
1498
1619
globalObject.read = (...args) => {
1499
1620
console.log("should not be inside read: ", ...args);
@@ -1803,7 +1924,7 @@ let BENCHMARKS = [
1803
1924
name : "FlightPlanner" ,
1804
1925
files : [
1805
1926
"./RexBench/FlightPlanner/airways.js" ,
1806
- "./RexBench/FlightPlanner/waypoints.js" ,
1927
+ "./RexBench/FlightPlanner/waypoints.js.z " ,
1807
1928
"./RexBench/FlightPlanner/flight_planner.js" ,
1808
1929
"./RexBench/FlightPlanner/expectations.js" ,
1809
1930
"./RexBench/FlightPlanner/benchmark.js" ,
@@ -1914,7 +2035,7 @@ let BENCHMARKS = [
1914
2035
new DefaultBenchmark ( {
1915
2036
name : "json-stringify-inspector" ,
1916
2037
files : [
1917
- "./SeaMonster/inspector-json-payload.js" ,
2038
+ "./SeaMonster/inspector-json-payload.js.z " ,
1918
2039
"./SeaMonster/json-stringify-inspector.js" ,
1919
2040
] ,
1920
2041
iterations : 20 ,
@@ -1924,7 +2045,7 @@ let BENCHMARKS = [
1924
2045
new DefaultBenchmark ( {
1925
2046
name : "json-parse-inspector" ,
1926
2047
files : [
1927
- "./SeaMonster/inspector-json-payload.js" ,
2048
+ "./SeaMonster/inspector-json-payload.js.z " ,
1928
2049
"./SeaMonster/json-parse-inspector.js" ,
1929
2050
] ,
1930
2051
iterations : 20 ,
@@ -2331,7 +2452,7 @@ let BENCHMARKS = [
2331
2452
"./wasm/argon2/benchmark.js" ,
2332
2453
] ,
2333
2454
preload : {
2334
- wasmBinary : "./wasm/argon2/build/argon2.wasm" ,
2455
+ wasmBinary : "./wasm/argon2/build/argon2.wasm.z " ,
2335
2456
} ,
2336
2457
iterations : 30 ,
2337
2458
worstCaseCount : 3 ,
0 commit comments