From 173d1ea1e5b5c9360d0a9f953985b8131b5fcaf7 Mon Sep 17 00:00:00 2001 From: Surma Date: Tue, 11 Aug 2020 13:17:42 +0100 Subject: [PATCH] Experiment with autovectorization --- codecs/mozjpeg_enc/Makefile | 7 ++-- codecs/mozjpeg_enc/bench.d8.js | 62 ++++++++++++++++++++++++++++ codecs/mozjpeg_enc/mozjpeg_enc.cpp | 2 - codecs/mozjpeg_enc/mozjpeg_enc.js | 12 ++---- codecs/mozjpeg_enc/mozjpeg_enc.wasm | Bin 221976 -> 217797 bytes 5 files changed, 69 insertions(+), 14 deletions(-) create mode 100644 codecs/mozjpeg_enc/bench.d8.js diff --git a/codecs/mozjpeg_enc/Makefile b/codecs/mozjpeg_enc/Makefile index 542e0b78..bf62ffb4 100644 --- a/codecs/mozjpeg_enc/Makefile +++ b/codecs/mozjpeg_enc/Makefile @@ -2,14 +2,14 @@ CODEC_URL := https://github.com/mozilla/mozjpeg/archive/v3.3.1.tar.gz CODEC_DIR := node_modules/mozjpeg CODEC_OUT_RELATIVE := .libs/libjpeg.a rdswitch.o CODEC_OUT := $(addprefix $(CODEC_DIR)/, $(CODEC_OUT_RELATIVE)) -OUT_JS := mozjpeg_enc.js +OUT_JS := mozjpeg_enc_simd.js OUT_WASM := $(OUT_JS:.js=.wasm) CFLAGS += -msimd128 -msse --em-config /src/.emscripten CXXFLAGS += -msimd128 -msse --em-config /src/.emscripten -#override CFLAGS += "-msimd128 -msse" -#override CXXFLAGS += "-msimd128 -msse" +#CFLAGS += --em-config /src/.emscripten +#CXXFLAGS += --em-config /src/.emscripten .PHONY: all clean @@ -28,6 +28,7 @@ all: $(OUT_JS) --closure 1 \ -s ALLOW_MEMORY_GROWTH=1 \ -s MODULARIZE=1 \ + -s EXPORT_ES6=1 \ -s 'EXPORT_NAME="$(basename $(@F))"' \ -o $@ \ $+ diff --git a/codecs/mozjpeg_enc/bench.d8.js b/codecs/mozjpeg_enc/bench.d8.js new file mode 100644 index 00000000..e7d016b7 --- /dev/null +++ b/codecs/mozjpeg_enc/bench.d8.js @@ -0,0 +1,62 @@ +import mozjpeg from "./mozjpeg_enc.js"; +import mozjpegSimd from "./mozjpeg_enc_simd.js"; + +const width = 2048; +const height = 2048; +const size = width * height * 4; +const data = new Uint8ClampedArray(size); +for(let i = 0; i < size; i++) { + data[i] = Math.random() * 256; +} + +mozjpeg({ + onRuntimeInitialized() { + const start = performance.now(); + const result = this.encode(data, width, height, { + quality: 75, + baseline: false, + arithmetic: false, + progressive: true, + optimize_coding: true, + smoothing: 0, + color_space: 3, + quant_table: 3, + trellis_multipass: false, + trellis_opt_zero: false, + trellis_opt_table: false, + trellis_loops: 1, + auto_subsample: true, + chroma_subsample: 2, + separate_chroma_quality: false, + chroma_quality: 75, + }); + const end = performance.now(); + console.log("No SIMD", end - start); + } +}) + +mozjpegSimd({ + onRuntimeInitialized() { + const start = performance.now(); + const result = this.encode(data, width, height, { + quality: 75, + baseline: false, + arithmetic: false, + progressive: true, + optimize_coding: true, + smoothing: 0, + color_space: 3, + quant_table: 3, + trellis_multipass: false, + trellis_opt_zero: false, + trellis_opt_table: false, + trellis_loops: 1, + auto_subsample: true, + chroma_subsample: 2, + separate_chroma_quality: false, + chroma_quality: 75, + }); + const end = performance.now(); + console.log("SIMD", end - start); + } +}) diff --git a/codecs/mozjpeg_enc/mozjpeg_enc.cpp b/codecs/mozjpeg_enc/mozjpeg_enc.cpp index 3673db5c..27388587 100644 --- a/codecs/mozjpeg_enc/mozjpeg_enc.cpp +++ b/codecs/mozjpeg_enc/mozjpeg_enc.cpp @@ -1,7 +1,5 @@ #include #include -#include -#include #include #include #include diff --git a/codecs/mozjpeg_enc/mozjpeg_enc.js b/codecs/mozjpeg_enc/mozjpeg_enc.js index 7c6d20f6..6b69a491 100644 --- a/codecs/mozjpeg_enc/mozjpeg_enc.js +++ b/codecs/mozjpeg_enc/mozjpeg_enc.js @@ -1,7 +1,7 @@ var mozjpeg_enc = (function() { - var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined; - if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename; + var _scriptDir = import.meta.url; + return ( function(mozjpeg_enc) { mozjpeg_enc = mozjpeg_enc || {}; @@ -63,10 +63,4 @@ d.run=Cb;if(d.preInit)for("function"==typeof d.preInit&&(d.preInit=[d.preInit]); } ); })(); -if (typeof exports === 'object' && typeof module === 'object') - module.exports = mozjpeg_enc; - else if (typeof define === 'function' && define['amd']) - define([], function() { return mozjpeg_enc; }); - else if (typeof exports === 'object') - exports["mozjpeg_enc"] = mozjpeg_enc; - \ No newline at end of file +export default mozjpeg_enc; \ No newline at end of file diff --git a/codecs/mozjpeg_enc/mozjpeg_enc.wasm b/codecs/mozjpeg_enc/mozjpeg_enc.wasm index e6e508c22c6321cf93ad221e08a87eb74c8aa572..d68572bf9df37bd5fec6fd56e381bed7862d1b63 100644 GIT binary patch delta 6428 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 Wu|wrc{=v_^dC(FggO>5rHU9 delta 10737 zcmb`N50G6|eaG)P_r7=E-+lM(z74zC4LR=;WCbD-G|6NWvX_J;WHLdmW5)^>|IF0B z$e>KHbON~xI0Z{-(I-Bze+;di>WBedB&F2p&H#4GA4fZ=9Z`sYXtf~nr$bvW>F0OO zeJ?Lzp?2D3c<0>nJAZ!X_wRhq@8tf!uHXEP`W@d3{GVS{TKIktG}My|kB_Y!E#LV- zA@j7CRTr9XxVaU6zNmGo_H_?#_|0xO`^L#ct<|3K5|uR*FPql#<7ZV}>TaK1b9(a6 zo+DE%D!NgA`1sy&K>s^b_fKZeID0T%-k)i6vW&8WjdZ_&PksE7jqdkm*KbPhJ-X(n zwOjM`*Zw%Y?5r@cW#hWH_nBZGI|1w334U#6EX#vgOO5XB8!w(TmMy)6q|VxO+DLy_ zwk$1tYtxB-xrShkTJ3oYFV2NJe5s(r)Zh21Exl!`9r$ymU;FxyYJgN-3X;?C=GiV5p@4(>y#>Vzp!+zQAwH|;~O2Fyxe{o-JvD%7-)2N-HyKs z?~%;~`?`GCsd$cBaH#_ae{aheSeN822S}Th~l$$)| zUCidGDqBSW)U~UlnrzBCHJZ5G}LBY zs9j)l1!lkqP@6a>oKsk}QChW8_SIIPr+87WHjlylx3uDNk|-`kUxPpA0?aYiiB^e` zMJp23R&e4OWqgSM(F&u)ewA|%qk}nE9`4c{bTL{iNWuVt?bEti9YqjEb+C)r)-Kso zU0B@ttw{ZQ_ml70P_+@OWW)NxWAFK@3hxdGL~7r{O&5-+Q8e0$ON8XV*QSKf+y27C z?=2`ilyI>bW9H;|;klwALG!aGdTBZd!FgqZ3Da*6Ikkl}*%<0HBDHdJl0A)jjZ;)hSvMlfo{OMb>} z%K;U`Dt+eSNomDt`B+r(l&zR}T3T1g&xzSrPzg)G%eYBTq>WDzL(4Rwu~^frFw3Re zL4`1I-ySxPJL?H%EV*7hI$y?(ZkkauPSqS;Ue>VtX4M12L(7cO#Mec9jIse1Xv%WQH-QJk z^T|;99hOn9BVS0MAnsX6O4cX7S%k?=53#Swe-vAeBT&;%Xqll;~Pst2fGNMTBf?6dfe~-sHyB zovh+>6w|0^EHhLrNK9a>XaAntoV75x0{$RzG(1aTnH{UC!|F+ar z5|cfw2F>@|X6A~RZOyq^EBmF&o>STLY`@Qe7kGT<>zO(FJg>4Bm^^EHcV%M))wa4T z!+=(J;%uDx5?`#Kj7~~&YUW3BsXP7I_2C0r`w|x3{n@vu>1@=3AaNkUYa6S#E)Pyl z!z=X_{=V+*e|l%z)MoVbxn?|3J1!OB735TntQ)j}lI>de5B}_iG;77PI9yGHkOj(% zH<%4*HQs1ubHKs=`X?PCcu?{4kcv0J^xa61Qr(@!c3&IveNozdnOS3}5ilY$?*o#d zX;#1XJj8{3BH(V|!D6tm`p?%*hd1PB$?x86QbORdXvxA6EwV(Z9wGSpTb$h^BZ+78 z>p6Cl+uvnvx8Jr#+P?k8{l_%UNMl1zydy`h3Lxcv%p68Y3*X(Jy-z%#Kg)!)KKw;^ z8fuKB@%9b2Et#JepE)`Y7}8I^ahr1_JB^>XmD)aAz;D#T%D=m+TEATR=}BG^?THJk zyzb}kU-y=RMDzw>EdYlmjZQRIQ&ssyY(BVj4@W46s8P`;XfGsDhk@Ds->-jkFqKcs zqu-d^7Pyf*dcPcSg=ljeL^~v4M(^WSN9ThK4gM2tZ;L?lbva2859?CMPckSG)zkUd(?{9GLQo)^yj!D=;fryA@!VAsF* z!%JGk3lX5i1-#4&u=~p&P8R`yvYpyv*C*kBDIMT(y}wwg9+)KHPk<7UDzzDGGNN9j zJamgu^L6z|Z~2?lNd-A6gG1Ziq~=2P^r36tsxDU*v@Yg{b`KTTso$SG>zcBzFflrv z;sT#w$6DM`1A@rNwxZ zeT)ZUw$6}ayo%g$mf}$kr`P2$MgRm_JS2&l*bg2vqZjqLb*#Pj+}qVmTCh^#Gnb0@ zf0qi%wO)FxNQfbb1SK3535QYYL=rfWP*{;rT9Ht`hDeJ4A0j~_A3!8>3j_BfnHSX# zcNIipL_vRNU1E&I>)4(jtx8l|xsIGNbV1vc8wQ%?=89D=4e8%2d(AO@zN}S@EK5K+ zx~yCRhL*Kg_M-^j%HepqakD+te5bm<#34=$`IigSbX)Q>u$VmHQYhBxG%538+8eYyAb9}RWs9r=;^;6{3Y z<;BJk-zu;AveG3NXABscID>TlP0JnIE?p+hNPLmf>wWnNwcJLLc0o{mOJa+2X=kW5 zkQmkb*C&)Yg)1a+W>>Ti>55meTeKh}I71L$zw??WK}4$e_9xXzN*}uHNp-DKSM*+a zO08@pXNouWFBhjDTJvLdo}Yfqf*fl`zJ+G?NRych?sh9SVE6g7+TyACZP{4%l*(QP z7|wG$mKadX@21=pxaP{;)Yg>TO>NEIO^L>Ge_r$~GCiP_PFc10*iY4G)8~tYa;o<0 zE7cVNJLK3AEJkQ;S42}YI&xc`(L2cFAk?4gn06$xv0anzUF|@M_3gS8Z)umMSkoSn z;`DZdg(|pvU`Cn+?(57S;KAjlLXLrHq@)Q<7|e;1&QSo;_A;!~^XK&b98MkvzV@sC z01jg{VegHof0RqaO79*TDb+oIGqdcRkYi~#1d;(q@i8690=VzfxCA;FH=eaq?=CkU zAeT>YgujMNLQ?8sxu^VkGkJG$R~cg=X5y}8P#J%yY#TS0x=m#9-9JzI$S{3~6oVNR z9>!#SkTol|xtiS4-}IIbAhgyU9EW{OK0lvU+hjRRDG5nFx%`OI`(<(Oe;f(;Zwm4s z2heUJ$wsK5>TOaU($j4Gn#m= zqqw-fv?CEI#QC0u%%B1^(jSuRXUMe zJ5`r#r_@$#FB*K(Yto@59a{M;l$K8EgWH&8M1Htw(v!pSR6NnGM`?Rf`Z0(T`n1re zb2_D^Q~H<+lQdJ?t0ui_c`r)ai_*8ri!wmLpJ%VFXAONOUO=79cX?$>oQLY2j~yb!!~ zb2b*&v!`>B(4X0mZa!OSFk5Lbn`D2=b_&aEx?WV4_n#Q_pBVI)Yj49uZuJt`Z(*B| zT9rx(HjG{0w%5r8jgrQ&?8T)mk6d4YLD-|c%o+%&1aZswFYZ0tw#SuJ8m&?t^JqS2 zMj|b3h^{qYM)FT}x3uey;_~hi7F_q*pt~fg%CO85WmDR6*`~kA*{L{IXiwcdE~hFQ zlM~F%6Ab2&OBoZ`ty>XeRr#{hgYMIV?$daaAWoNHu(Rq>tD`hD>klEw& zq8N55=kXF@&+-T!r^&=4wp>(kLr7Qzt#f%Y?SLeyXw6L#;paIqvM#hckV3GoSpKO* zeHCFrsWqXBn2>0c&WnTqI^{#>hPFoC^T>3izfZ14 zaos)&Nz~%-MtUAHt!D=>P~e7|8A)D|G~O&XFHl9E=U%jU1uFl&`kE&2Cnxm}RWJW@ zm$X>-E`LT%zgs1Xo3UDXIa>0y=yfOAWAw%3Se4N`rjjFGr(KkD51Z0$)w|?rHMJV> z?ZN%7y0_e))98yN-)3Hi2>#p(H@EYogUxTL-Yd_jsV4Y}*B|-}V#2Mg6@4X=|UoQye{0ljP%U_Vo zxMT@0uX04mvyj?3C72c`W#>}J|2wAugB6}_R?q1aYb+3`%Adku?S$2%6IO>NM35i( zg_=x{nV0F@(?VFa=cruIs@0nUKS+c9Wp`%6E~zgG4!&SilHu~cJg+(Bc+ty^Ow6Q# zH@ymhos;7{{BAdwU@t|EwWT`Jwwu%HJCbx>A@^n0D1zTC03UtL5o2_Y!|N>CT0vGD zv=x`G19%b6^Q;5n9i$3PDtuT5j?W@m1{%UZO&G9CF4;%;i_5Yk9XYgQ$%-uLb}wV1 z=IGeOXEE&cSMad#csxqS&AEC6=SM0#9wCzf0;g01xqanr6OEIJ1E6nhDRj( z?Vyxx7=}`|p%0}v+hzbIj35I~3Fxq;PxQm698%&WBKe%=y{7YClt3j;2@8<$(40M<1OAs}l<-a5VY0BQ;<+z=)DJ40br`&V5)O-m7D#e84J!g23 z164A}fhrl~K$Q#|3@HWAMIQkcJ9ITHyvs2ehnlUaJ(4c_{od$6+my65TvYe0)*`Ewvg z_;Vmf_?srOIjK*>ANOb(lRcWcgudemY?v%ulfMPPlZfEsztfi=9(n9C4)Mt{4)Mt{ zt-&&a_=w@jBljJh@OVaRChgmYLxD1dLxCbmL4hOOeXc+3HqQEp^H?VOZ|ZAv{3kQW znd0_JCgb%0-MmT%7|v?Np|-;)%mOuHBFp9eW?K$8pYyFrqSU_2Bu~qERKE+C~>HC&5#wJI=uxI3>7iDx4 z6B=^4oCLP9*9d0H9?dPz+I1<#LWWHa-iJ-jT4_GX0hymC>tifxnEi{O6B&*%m*EMH zLz#Dnyf5g|zTW3vP-{1!aAGLQCrMn9!J??Oxrq`u+YzWuopVqG@ql^(fn-EKya6Fvo(7**_u6@@+`hZur*SKo;>Re zPqWNExWz;n$((3;7S4P>^@zeiV3I++_I*jUNu1bodNSL@OFfAwn{H@P!|tZ!-@3F% zs|HG*D;edUIwdwar7kAM=fpf)Vu?Id!#6~o6G2gu!mvdA7^)A|7wQD4<)Co0%uHsp z4tTD1!;|N059_weZS~iPCw$IA# z43b!*gCtgyZg~=m##}MrYn#M^XJXch?YpDR VVwEL}RlaueC3R@>C4Jk4{|y?r#|{7h