Skip to content

Commit 6f75955

Browse files
authored
cleanup and wasm movemask (#81)
* add SSE2 movemask * add wasm_movemask and update js benchmarks * update npm message for EAGAIN readsync error using sync read + stdin on wasm
1 parent 1b4c4f4 commit 6f75955

13 files changed

+312
-74
lines changed

README.md

+2-6
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ that implements the expected
7575
[app/benchmark/README.md](app/benchmark/README.md)
7676
* Low memory usage (regardless of how big your data is) and size footprint for
7777
both lib (~20k) and CLI executable (< 1MB)
78-
* Easy to use as a library in a few lines of code
78+
* Easy to use as a library in a few lines of code, via either pull or push parsing
7979
* Includes the `zsv` CLI with the following built-in commands:
8080
* `select`, `count`, `sql` query, `desc`ribe, `flatten`, `serialize`, `2json`,
8181
`2db`, `stack`, `pretty`, `2tsv`, `jq`, `prop`, `rm`
@@ -157,10 +157,6 @@ choco.exe install zsv -source .\zsv-amd64-windows-mingw.nupkg
157157
choco.exe uninstall zsv
158158
```
159159

160-
**NOTE**: Windows build has a runtime dependency on `libwinpthread-1.dll`.
161-
Please download it from here (https://wikidll.com/mingw-w64/libwinpthread-1-dll)
162-
according to your Windows version and place it with `zsv` executable.
163-
164160
#### Node
165161

166162
The zsv parser library is available for node:
@@ -256,7 +252,7 @@ zsv sql my_population_data.csv "select * from data where population > 100000"
256252

257253
### Using the API
258254

259-
Basic examples of using the API are in [examples/lib/README.md](examples/lib/README.md).
255+
Full application code examples can be found at [examples/lib/README.md](examples/lib/README.md).
260256

261257
An example of using the API, compiled to wasm and called via Javascript,
262258
is in [examples/js/README.md](examples/js/README.md).

app/Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ ifneq ($(findstring emcc,$(CC)),) # emcc
128128
LDFLAGS+=-pthread
129129
endif
130130
else # not emcc
131-
CFLAGS+= ${CFLAGS_AVX}
131+
CFLAGS+= ${CFLAGS_AVX} ${CFLAGS_SSE}
132132
LDFLAGS+=-lpthread # Linux explicitly requires
133133
endif
134134
UTILS=$(addprefix ${BUILD_DIR}/objs/utils/,$(addsuffix .o,${UTILS1}))

configure

+46-9
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Configuration:
1616
Optional configuration:
1717
--minimal=yes do not include extra features (default=no)
1818
--arch=ARCH use -march=ARCH. Set to 'none' for none, else defaults to 'native'
19+
--jq-prefix=JQ_PREFIX specify directory containing lib/libjq and include/jq.h
1920
2021
Installation directories:
2122
--prefix=PREFIX main installation prefix [\$PREFIX or /usr/local]
@@ -30,6 +31,7 @@ Optional features:
3031
--try-avx512 use avx512 instructions, if available [no]
3132
--force-avx2 force compile with (no CPU check) or without -mavx2 [auto]
3233
--force-avx force compile with (no CPU check) or without -mavx [auto]
34+
--force-sse2 force compile with (no CPU check) or without -msse2 [auto]
3335
--enable-lto compile with LTO (works with some but not all platforms/compilers) [no]
3436
--enable-whole-program compile without -fwhole-program even if no -flto [yes]
3537
--enable-pie build with position independent executables [auto]
@@ -45,6 +47,8 @@ Some influential environment variables:
4547
CFLAGS C compiler flags [-Os -pipe ...]
4648
LDFLAGS Linker flags
4749
50+
CROSS_COMPILING=no Set to yes to disable auto-detect compilation flags
51+
4852
Use these variables to override the choices made by configure.
4953
5054
EOF
@@ -244,7 +248,7 @@ trysharedldflag () {
244248
}
245249

246250
# Beginning of actual script
247-
251+
CROSS_COMPILING=no
248252
CFLAGS_AUTO=
249253
CFLAGS_TRY=
250254
LDFLAGS_AUTO=
@@ -254,7 +258,7 @@ if [ "$CONFIGFILE" = "" ]; then
254258
CONFIGFILE=config.mk
255259
fi
256260

257-
if [ "$ARCH" = "" ]; then
261+
if [ "$ARCH" = "" ] && [ "$CROSS_COMPILING" = "no" ]; then
258262
ARCH=native
259263
fi
260264

@@ -290,6 +294,9 @@ MINIMAL=no
290294

291295
TRY_LTO=no
292296
TRY_WHOLE_PROGRAM=auto
297+
FORCE_AVX2=auto
298+
FORCE_AVX=auto
299+
FORCE_SSE2=auto
293300

294301
help=yes
295302
usepie=auto
@@ -323,6 +330,9 @@ for arg ; do
323330
--force-avx|--force-avx=yes) FORCE_AVX=yes;;
324331
--force-avx=no) FORCE_AVX=no;;
325332

333+
--force-sse2|--force-sse2=yes) FORCE_SSE2=yes;;
334+
--force-sse2=no) FORCE_SSE2=no;;
335+
326336
--enable-lto|--enable-lto=yes) TRY_LTO=yes;;
327337
--enable-lto|--enable-lto=auto) TRY_LTO=auto;;
328338
--disable-lto|--enable-lto=no) TRY_LTO=no;;
@@ -517,18 +527,40 @@ tryflag CFLAGS -ffunction-sections
517527
tryflag CFLAGS -fdata-sections
518528

519529
CFLAGS_AVX=
520-
if [ "$FORCE_AVX2" = "yes" ] ; then
530+
531+
HAVE_AVX=
532+
if [ "$FORCE_AVX2" = "no" ]; then
533+
tryflag CFLAGS -mno-avx2
534+
elif [ "$FORCE_AVX2" = "yes" ] ; then
521535
CFLAGS_AVX=-mavx2
522-
trycpusupport avx2 || echo "warning: avx2 forced but not supported on native CPU"
523-
elif [ "$FORCE_AVX2" != "no" ] ; then
536+
if [ "$CROSS_COMPILING" = "no" ] ; then
537+
trycpusupport avx2 || echo "warning: avx2 forced but not supported on native CPU"
538+
fi
539+
elif [ "$FORCE_AVX2" = "auto" ] && [ "$CROSS_COMPILING" = "no" ] ; then
524540
trycpusupport avx2 && CFLAGS_AVX=-mavx2
525541
fi
526542

527543
if [ "$FORCE_AVX" = "yes" ] ; then
528544
CFLAGS_AVX=-mavx || echo "warning: avx forced but not supported on native CPU"
529-
elif [ "$FORCE_AVX" != "no" ] && [ "$CFLAGS_AVX" = "" ] ; then
545+
elif [ "$FORCE_AVX" = "auto" ] && [ "$CFLAGS_AVX" = "" ] && [ "$CROSS_COMPILING" = "no" ] ; then
530546
trycpusupport avx && CFLAGS_AVX=-mavx
531547
fi
548+
if [ "$FORCE_AVX" = "no" ]; then
549+
tryflag CFLAGS -mno-avx
550+
fi
551+
552+
if [ "$FORCE_SSE2" = "no" ]; then
553+
tryflag CFLAGS -mno-sse2
554+
elif [ "$FORCE_SSE2" = "yes" ] ; then
555+
CFLAGS_SSE=-msse2
556+
if [ "$CROSS_COMPILING" = "no" ] ; then
557+
trycpusupport sse2 || echo "warning: sse2 forced but not supported on native CPU"
558+
fi
559+
elif [ "$FORCE_SSE2" = "auto" ] && [ "$CROSS_COMPILING" = "no" ] ; then
560+
if [ "$CFLAGS_SSE" = "" ] && [ "$CROSS_COMPILING" = "no" ] ; then
561+
trycpusupport sse2 && tryflag CFLAGS_SSE -msse2
562+
fi
563+
fi
532564

533565
HAVE_LTO=0
534566
if [ "$TRY_LTO" = "yes" ]; then
@@ -557,7 +589,10 @@ tryflag CFLAGS_OPT -fvisibility=hidden
557589
tryldflag LDFLAGS_AUTO -Wl,--gc-sections
558590

559591
if [ "$ARCH" != "none" ] ; then
560-
tryldflag LDFLAGS_OPT -march=$ARCH
592+
if ! tryflag CFLAGS -march=$ARCH ; then
593+
echo "Flag -march=$ARCH failed!"
594+
exit 1
595+
fi
561596
fi
562597
tryldflag LDFLAGS_OPT -ldl
563598

@@ -602,7 +637,7 @@ if [ "$usetermcap" = "yes" ] || [ "$usetermcap" = "auto" ] ; then
602637
fi
603638
fi
604639

605-
if [ "$JQ_PREFIX" != "" ] && [ "$ARCH" = "native" ]; then
640+
if [ "$JQ_PREFIX" != "" ] && [ "$CROSS_COMPILING" = "no" ] ; then
606641
echo "checking --prefix-jq ${JQ_PREFIX}"
607642
if ! tryldflag LDFLAGS_JQ -ljq -L${JQ_PREFIX}/lib ; then
608643
echo "Error: Failed to compile with -ljq and -L${JQ_PREFIX}/lib"
@@ -676,9 +711,11 @@ CFLAGS_LTO = $CFLAGS_LTO
676711
LDFLAGS_AUTO = $LDFLAGS_AUTO
677712
678713
HAVE_AVX512=$HAVE_AVX512
679-
CFLAGS_AVX_512=$CFLAGS_AVX_512
680714
715+
CFLAGS_AVX_512=$CFLAGS_AVX_512
681716
CFLAGS_AVX=$CFLAGS_AVX
717+
CFLAGS_SSE=$CFLAGS_SSE
718+
682719
CFLAGS_DEBUG = -U_FORTIFY_SOURCE -UNDEBUG -O0 -g -Wall -Wextra -Wno-missing-field-initializers -Wno-unused-parameter # -g3 -ggdb
683720
LDFLAGS_DEBUG = -U_FORTIFY_SOURCE -UNDEBUG -O0 -g # -g3 -ggdb
684721
CFLAGS_PIC = $CFLAGS_PIC

examples/js/Makefile

+64-29
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ INDEX=${BUILD_DIR}/index.html
4141
EMJS=${BUILD_DIR}/zsv.em.js
4242
WASM=${BUILD_DIR}/zsv.em.wasm
4343

44-
CFLAGS+= ${CFLAGS_PIC} -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_RUNTIME_METHODS="['setValue','addFunction','removeFunction','writeArrayToMemory']" -s RESERVED_FUNCTION_POINTERS=4 -s EXPORTED_FUNCTIONS="['_free','_malloc']"
44+
CFLAGS+= ${CFLAGS_PIC} -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_RUNTIME_METHODS="['setValue','addFunction','removeFunction','writeArrayToMemory']" -s RESERVED_FUNCTION_POINTERS=4 -s EXPORTED_FUNCTIONS="['_free','_malloc']" -sASSERTIONS
4545

4646
ifeq ($(DEBUG),1)
4747
CFLAGS += ${CFLAGS_DEBUG}
@@ -64,14 +64,14 @@ TEST_PASS=echo "${COLOR_BLUE}$@: ${COLOR_GREEN}Passed${COLOR_NONE}"
6464
TEST_FAIL=(echo "${COLOR_BLUE}$@: ${COLOR_RED}Failed!${COLOR_NONE}" && exit 1)
6565
#####
6666

67-
.PHONY: help all run clean prep node setup benchmark count_compare
67+
.PHONY: help all run clean prep node setup benchmark count_compare select_compare
6868

6969
help:
7070
@echo "make [build|run|node|test|clean]"
7171
@echo "by default, minified code is generated, which requires running the below once:"
7272
@echo " make setup"
7373
@echo "alternatively, to generate non-minified code, use NO_MINIFY=1:"
74-
@echo " make NO_MINIFY=1 [build|run|node|test]"
74+
@echo " make NO_MINIFY=1 [build|run|node|test|benchmark]"
7575

7676
build: ${BROWSER_JS} ${STATIC}
7777
@echo Built ${BROWSER_JS}
@@ -91,7 +91,7 @@ test: npm/test/select_all.js node
9191
@mkdir -p build/test
9292
@cp -p $< node/
9393
@echo "Running test (example) program \`node node/select_all.js ../../data/test/desc.csv\`"
94-
@(cd node && node select_all.js ../../../data/test/desc.csv > ../build/test/out.json 2> ../build/test/out.err1)
94+
@(cd node && ${NODE} select_all.js ../../../data/test/desc.csv > ../build/test/out.json 2> ../build/test/out.err1)
9595
@sed 's/[0-9.]*ms//g' < build/test/out.err1 > build/test/out.err
9696
@cmp build/test/out.err npm/test/out.err
9797
@cmp build/test/out.json npm/test/out.json && ${TEST_PASS} || ${TEST_FAIL}
@@ -121,6 +121,7 @@ ifeq ($(NO_MINIFY),1)
121121
122122
else
123123
@uglifyjs [email protected] -c -m > $@
124+
124125
endif
125126

126127
### node package build
@@ -142,6 +143,7 @@ ifeq ($(NO_MINIFY),1)
142143
143144
else
144145
@uglifyjs [email protected] -c -m > $@
146+
145147
endif
146148

147149
setup:
@@ -152,50 +154,83 @@ node: ${NODE_WASM} ${NODE_INDEX} ${NODE_PKG_FILES}
152154

153155

154156
#### node benchmark
155-
BENCHMARK_INPUT=${THIS_MAKEFILE_DIR}/../../app/benchmark/worldcitiespop_mil-sc.csv
157+
BENCHMARK_INPUT=${THIS_MAKEFILE_DIR}/../../app/benchmark/worldcitiespop_mil.csv
158+
159+
NODE=node --experimental-wasm-modules
156160

157161
benchmark: node count_compare select_compare
158162

159163
count_compare:
160164
@cp -p npm/test/count*.js node/
161-
@cd node && (npm list | grep csv-parser) && echo "csv-parser already installed" || npm install csv-parser
165+
@cd node && (npm list | grep csv-parser) && echo "csv-parser already installed" || npm install csv-parser papaparse
162166

163167
@echo "zsv count"
164-
head -5000 ${BENCHMARK_INPUT} | node node/count.js 2>&1 | head -1
165-
head -5000 ${BENCHMARK_INPUT} | node node/count.js 2>&1 | head -1
166-
head -5000 ${BENCHMARK_INPUT} | node node/count.js 2>&1 | head -1
168+
head -5000 ${BENCHMARK_INPUT} | ${NODE} node/count.js 2>&1 | head -1
169+
head -5000 ${BENCHMARK_INPUT} | ${NODE} node/count.js 2>&1 | head -1
170+
head -5000 ${BENCHMARK_INPUT} | ${NODE} node/count.js 2>&1 | head -1
167171

168-
head -500000 ${BENCHMARK_INPUT} | node node/count.js 2>&1 | head -1
169-
head -500000 ${BENCHMARK_INPUT} | node node/count.js 2>&1 | head -1
170-
head -500000 ${BENCHMARK_INPUT} | node node/count.js 2>&1 | head -1
172+
head -500000 ${BENCHMARK_INPUT} | ${NODE} node/count.js 2>&1 | head -1
173+
head -500000 ${BENCHMARK_INPUT} | ${NODE} node/count.js 2>&1 | head -1
174+
head -500000 ${BENCHMARK_INPUT} | ${NODE} node/count.js 2>&1 | head -1
171175

172176

173177
@echo "csv-parser count"
174-
head -5000 ${BENCHMARK_INPUT} | node node/count-csv-parser.js 2>&1 | head -1
175-
head -5000 ${BENCHMARK_INPUT} | node node/count-csv-parser.js 2>&1 | head -1
176-
head -5000 ${BENCHMARK_INPUT} | node node/count-csv-parser.js 2>&1 | head -1
178+
head -5000 ${BENCHMARK_INPUT} | ${NODE} node/count-csv-parser.js 2>&1 | head -1
179+
head -5000 ${BENCHMARK_INPUT} | ${NODE} node/count-csv-parser.js 2>&1 | head -1
180+
head -5000 ${BENCHMARK_INPUT} | ${NODE} node/count-csv-parser.js 2>&1 | head -1
181+
182+
head -500000 ${BENCHMARK_INPUT} | ${NODE} node/count-csv-parser.js 2>&1 | head -1
183+
head -500000 ${BENCHMARK_INPUT} | ${NODE} node/count-csv-parser.js 2>&1 | head -1
184+
head -500000 ${BENCHMARK_INPUT} | ${NODE} node/count-csv-parser.js 2>&1 | head -1
185+
186+
@echo "papaparse count"
187+
head -5000 ${BENCHMARK_INPUT} | ${NODE} node/count-papaparse.js 2>&1 | head -1
188+
head -5000 ${BENCHMARK_INPUT} | ${NODE} node/count-papaparse.js 2>&1 | head -1
189+
head -5000 ${BENCHMARK_INPUT} | ${NODE} node/count-papaparse.js 2>&1 | head -1
177190

178-
head -500000 ${BENCHMARK_INPUT} | node node/count-csv-parser.js 2>&1 | head -1
179-
head -500000 ${BENCHMARK_INPUT} | node node/count-csv-parser.js 2>&1 | head -1
180-
head -500000 ${BENCHMARK_INPUT} | node node/count-csv-parser.js 2>&1 | head -1
191+
head -500000 ${BENCHMARK_INPUT} | ${NODE} node/count-papaparse.js 2>&1 | head -1
192+
head -500000 ${BENCHMARK_INPUT} | ${NODE} node/count-papaparse.js 2>&1 | head -1
193+
head -500000 ${BENCHMARK_INPUT} | ${NODE} node/count-papaparse.js 2>&1 | head -1
181194

182195
select_compare:
183196
@cp -p npm/test/select_all*.js node/
184-
@cd node && (npm list | grep csv-parser) && echo "csv-parser already installed" || npm install csv-parser
197+
@cd node && (npm list | grep csv-parser) && echo "csv-parser already installed" || npm install csv-parser papaparse
185198

186199
@echo "zsv select_all"
187-
head -5000 ${BENCHMARK_INPUT} | node node/select_all.js 2>&1 | head -1
188-
head -5000 ${BENCHMARK_INPUT} | node node/select_all.js 2>&1 | head -1
189-
head -5000 ${BENCHMARK_INPUT} | node node/select_all.js 2>&1 | head -1
200+
head -5000 ${BENCHMARK_INPUT} | ${NODE} node/select_all.js '' '[0,2]' 2>&1 | head -1
201+
head -5000 ${BENCHMARK_INPUT} | ${NODE} node/select_all.js '' '[0,2]' 2>&1 | head -1
190202

191-
head -500000 ${BENCHMARK_INPUT} | node node/select_all.js 2>&1 | head -1
192-
head -500000 ${BENCHMARK_INPUT} | node node/select_all.js 2>&1 | head -1
203+
head -5000 ${BENCHMARK_INPUT} | ${NODE} node/select_all.js 2>&1 | head -1
204+
head -5000 ${BENCHMARK_INPUT} | ${NODE} node/select_all.js 2>&1 | head -1
193205

206+
head -500000 ${BENCHMARK_INPUT} | ${NODE} node/select_all.js '' '[0,2]' 2>&1 | head -1
207+
head -500000 ${BENCHMARK_INPUT} | ${NODE} node/select_all.js '' '[0,2]' 2>&1 | head -1
208+
209+
head -500000 ${BENCHMARK_INPUT} | ${NODE} node/select_all.js 2>&1 | head -1
210+
head -500000 ${BENCHMARK_INPUT} | ${NODE} node/select_all.js 2>&1 | head -1
194211

195212
@echo "csv-parser select_all"
196-
head -5000 ${BENCHMARK_INPUT} | node node/select_all-csv-parser.js 2>&1 | head -1
197-
head -5000 ${BENCHMARK_INPUT} | node node/select_all-csv-parser.js 2>&1 | head -1
198-
head -5000 ${BENCHMARK_INPUT} | node node/select_all-csv-parser.js 2>&1 | head -1
213+
head -5000 ${BENCHMARK_INPUT} | ${NODE} node/select_all-csv-parser.js '' '[0,2]' 2>&1 | head -1
214+
head -5000 ${BENCHMARK_INPUT} | ${NODE} node/select_all-csv-parser.js '' '[0,2]' 2>&1 | head -1
215+
216+
head -5000 ${BENCHMARK_INPUT} | ${NODE} node/select_all-csv-parser.js 2>&1 | head -1
217+
head -5000 ${BENCHMARK_INPUT} | ${NODE} node/select_all-csv-parser.js 2>&1 | head -1
218+
219+
head -500000 ${BENCHMARK_INPUT} | ${NODE} node/select_all-csv-parser.js '' '[0,2]' 2>&1 | head -1
220+
head -500000 ${BENCHMARK_INPUT} | ${NODE} node/select_all-csv-parser.js '' '[0,2]' 2>&1 | head -1
221+
222+
head -500000 ${BENCHMARK_INPUT} | ${NODE} node/select_all-csv-parser.js 2>&1 | head -1
223+
head -500000 ${BENCHMARK_INPUT} | ${NODE} node/select_all-csv-parser.js 2>&1 | head -1
224+
225+
@echo "papaparse select_all"
226+
head -5000 ${BENCHMARK_INPUT} | ${NODE} node/select_all-papaparse.js '' '[0,2]' 2>&1 | head -1
227+
head -5000 ${BENCHMARK_INPUT} | ${NODE} node/select_all-papaparse.js '' '[0,2]' 2>&1 | head -1
228+
229+
head -5000 ${BENCHMARK_INPUT} | ${NODE} node/select_all-papaparse.js 2>&1 | head -1
230+
head -5000 ${BENCHMARK_INPUT} | ${NODE} node/select_all-papaparse.js 2>&1 | head -1
231+
232+
head -500000 ${BENCHMARK_INPUT} | ${NODE} node/select_all-papaparse.js '' '[0,2]' 2>&1 | head -1
233+
head -500000 ${BENCHMARK_INPUT} | ${NODE} node/select_all-papaparse.js '' '[0,2]' 2>&1 | head -1
199234

200-
head -500000 ${BENCHMARK_INPUT} | node node/select_all-csv-parser.js 2>&1 | head -1
201-
head -500000 ${BENCHMARK_INPUT} | node node/select_all-csv-parser.js 2>&1 | head -1
235+
head -500000 ${BENCHMARK_INPUT} | ${NODE} node/select_all-papaparse.js 2>&1 | head -1
236+
head -500000 ${BENCHMARK_INPUT} | ${NODE} node/select_all-papaparse.js 2>&1 | head -1

examples/js/README.md

+14-8
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,18 @@ this example does not require that libzsv is already installed
4444

4545
## Performance
4646

47-
Running ZSV lib from Javascript is still experimental and is not yet fully optimized. Some performance challenges are
48-
unique to web assembly + Javascript, especially where a lot of string data
47+
Running ZSV lib from Javascript is still experimental and is not yet fully optimized.
48+
Some performance challenges rae particular to web assembly + Javascript, e.g. where a lot of string data
4949
is being passed between Javascript and the library (see e.g. https://hacks.mozilla.org/2019/08/webassembly-interface-types/).
5050

51-
Furthermore, it is unlikely that zsv-lib can approach its full performance potential
52-
until emscripten (or gcc) [can provide a SIMD-powered movemask function](https://github.com/WebAssembly/simd/pull/201). Until then, libzsv in emscripten resorts to the "slow"
53-
movemask, which does have a significant impact.
51+
However, initial results are promising:
5452

55-
Current testing suggests that on small files (under 1 MB), zsv-lib is 30-75% faster than, for example, the `csv-parser` library. However, on larger files,
56-
due to the aforementioned Javascript/wasm memory overhead and lack of
57-
SIMD movemask, it can be more than 50% slower than `csv-parser`.
53+
* Running only "count", zsv-lib is ~90%+ faster than `csv-parser` and `papaparse`
54+
* The more cell data that is fetched, the more this advantage diminishes due to the aforementioned Javascript/wasm memory overhead.
55+
Our benchmarking suggests that if the entire row's data is fetched, performance is about on par with both csv-parser and papaparse.
56+
If only a portion is fetched, performance is about the same for papaparse, and faster than csv-parser (how much faster
57+
being roughly proportional to the difference between count (~90% faster) and the
58+
amount of total data fetched)
5859

5960
## All the build commands
6061

@@ -68,6 +69,11 @@ make clean
6869

6970
Add MINIFY=1 to any of the above to generate minified code
7071

72+
To run benchmark tests:
73+
```
74+
make benchmark
75+
```
76+
7177
To see all make options:
7278
```
7379
make

0 commit comments

Comments
 (0)