1"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[4461],{65158:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>l,contentTitle:()=>a,default:()=>d,frontMatter:()=>t,metadata:()=>r,toc:()=>c});var r=i(54512),s=i(13274),o=i(98703);const t={description:"Profiling MoonBit-Generated Wasm using Chrome",slug:"profile-wasm-from-js",image:"/img/blogs/2025-02-14-profile-wasm-from-js/cover.jpg",tags:["MoonBit","Wasm","JavaScript"]},a="Profiling MoonBit-Generated Wasm using Chrome",l={authorsImageUrls:[]},c=[{value:"Profiling the Cmark library",id:"profiling-the-cmark-library",level:2},{value:"Analyzing the Flame Graph",id:"analyzing-the-flame-graph",level:2},{value:"Achieving High Performance",id:"achieving-high-performance",level:2}];function h(e){const n={a:"a",blockquote:"blockquote",code:"code",em:"em",h2:"h2",img:"img",li:"li",p:"p",pre:"pre",span:"span",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,o.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"cover",src:i(7960).A+"",width:"1920",height:"1080"})}),"\n",(0,s.jsxs)(n.p,{children:["In ",(0,s.jsx)(n.a,{href:"http://moonbitlang.com/blog/call-wasm-from-js",children:"one of our previous blog posts"}),", we have introduced how\r\nto consume a MoonBit-powered Wasm library, ",(0,s.jsx)(n.a,{href:"https://github.com/moonbit-community/cmark",children:"Cmark"}),", within frontend JavaScript.\r\nIn this post, we will explore how to profile the same library directly from the\r\nChrome browser. Hopefully, this will help you gain insights and eventually\r\nachieve better overall performance with MoonBit in similar use cases."]}),"\n",(0,s.jsxs)(n.p,{children:["For this blog post in particular, we will focus on making minimal changes to our\r\nprevious ",(0,s.jsx)(n.a,{href:"https://github.com/moonbit-community/cmark-frontend-example",children:'"Cmark for frontend" example'}),", so that we may\r\napply Chrome's built-in V8 profiler to ",(0,s.jsx)(n.a,{href:"https://github.com/moonbit-community/cmark",children:"Cmark"}),"'s Wasm code."]}),"\n",(0,s.jsx)(n.h2,{id:"profiling-the-cmark-library",children:"Profiling the Cmark library"}),"\n",(0,s.jsxs)(n.p,{children:['We can easily refactor the original frontend application to include a new\r\nnavigation bar with two links, "Demo" and "Profiling". Clicking the first will\r\ntell the browser to render the HTML for the original ',(0,s.jsx)(n.em,{children:"A Tour of MoonBit for\r\nBeginners"})," example, and clicking the second will lead it to our new document for\r\nprofiling. (If you are curious about the actual implementation, you can find a\r\nlink pointing to the final code towards the end of this post.)"]}),"\n",(0,s.jsx)(n.p,{children:"Now we are ready to write some code to actually implement Wasm profiling. Are\r\nthere any particularities involved in terms of profiling Wasm compared to\r\nJavaScript?"}),"\n",(0,s.jsxs)(n.p,{children:["As it turns out, we can use the very same APIs for profiling Wasm as we do for\r\nJavaScript code. There is ",(0,s.jsx)(n.a,{href:"https://developer.chrome.com/docs/devtools/console/utilities/#profile-function",children:"an article"})," in the Chromium\r\ndocumentation that describes them in more detail, but in short:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["When we call ",(0,s.jsx)(n.code,{children:"console.profile()"}),", the V8 profiler will start recording a CPU\r\nperformance profile;"]}),"\n",(0,s.jsx)(n.li,{children:"After that, we can call the performance-critical function we would like to\r\nanalyze;"}),"\n",(0,s.jsxs)(n.li,{children:["Finally, when we call ",(0,s.jsx)(n.code,{children:"console.profileEnd()"}),", the profiler will stop the\r\nrecording and then visualizes the resulting data in Chrome's Performance Tab."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"With that in mind, let's have a look at the actual implementation of our\r\nprofiling functionality:"}),"\n",(0,s.jsx)(s.Fragment,{children:(0,s.jsx)(n.pre,{className:"shiki shiki-themes one-light one-dark-pro",style:{backgroundColor:"#FAFAFA","--shiki-dark-bg":"#282c34",color:"#383A42","--shiki-dark":"#abb2bf"},tabIndex:"0",children:(0,s.jsxs)(n.code,{children:[(0,s.jsxs)(n.span,{className:"line",children:[(0,s.jsx)(n.span,{style:{color:"#A626A4","--shiki-dark":"#C678DD"},children:"async"}),(0,s.jsx)(n.span,{style:{color:"#A626A4","--shiki-dark":"#C678DD"},children:" function"}),(0,s.jsx)(n.span,{style:{color:"#4078F2","--shiki-dark":"#61AFEF"},children:" profileView"}),(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#ABB2BF"}
1,children:"() {"})]}),"\n",(0,s.jsxs)(n.span,{className:"line",children:[(0,s.jsx)(n.span,{style:{color:"#A626A4","--shiki-dark":"#C678DD"},children:" const"}),(0,s.jsx)(n.span,{style:{color:"#986801","--shiki-dark":"#E5C07B"},children:" docText"}),(0,s.jsx)(n.span,{style:{color:"#0184BC","--shiki-dark":"#56B6C2"},children:" ="}),(0,s.jsx)(n.span,{style:{color:"#A626A4","--shiki-dark":"#C678DD"},children:" await"}),(0,s.jsx)(n.span,{style:{color:"#4078F2","--shiki-dark":"#61AFEF"},children:" fetchDocText"}),(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#ABB2BF"},children:"("}),(0,s.jsx)(n.span,{style:{color:"#50A14F","--shiki-dark":"#98C379"},children:'"../public/spec.md"'}),(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#ABB2BF"},children:");"})]}),"\n",(0,s.jsxs)(n.span,{className:"line",children:[(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#E5C07B"},children:" console"}),(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#ABB2BF"},children:"."}),(0,s.jsx)(n.span,{style:{color:"#4078F2","--shiki-dark":"#61AFEF"},children:"profile"}),(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#ABB2BF"},children:"();"})]}),"\n",(0,s.jsxs)(n.span,{className:"line",children:[(0,s.jsx)(n.span,{style:{color:"#A626A4","--shiki-dark":"#C678DD"},children:" const"}),(0,s.jsx)(n.span,{style:{color:"#986801","--shiki-dark":"#E5C07B"},children:" res"}),(0,s.jsx)(n.span,{style:{color:"#0184BC","--shiki-dark":"#56B6C2"},children:" ="}),(0,s.jsx)(n.span,{style:{color:"#4078F2","--shiki-dark":"#61AFEF"},children:" cmarkWASM"}),(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#ABB2BF"},children:"("}),(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#E06C75"},children:"docText"}),(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#ABB2BF"},children:");"})]}),"\n",(0,s.jsxs)(n.span,{className:"line",children:[(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#E5C07B"},children:" console"}),(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#ABB2BF"},children:"."}),(0,s.jsx)(n.span,{style:{color:"#4078F2","--shiki-dark":"#61AFEF"},children:"profileEnd"}),(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#ABB2BF"},children:"();"})]}),"\n",(0,s.jsxs)(n.span,{className:"line",children:[(0,s.jsx)(n.span,{style:{color:"#A626A4","--shiki-dark":"#C678DD"},children:" return"}),(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#ABB2BF"},children:" ("})]}),"\n",(0,s.jsx)(n.span,{className:"line",children:(0,s.jsx)(n.span,{style:{color:"#50A14F","--shiki-dark":"#98C379"},children:" `<h2>Note</h2>"})}),"\n",(0,s.jsx)(n.span,{className:"line",children:(0,s.jsx)(n.span,{style:{color:"#50A14F","--shiki-dark":"#98C379"},children:"<p>"})}),"\n",(0,s.jsx)(n.span,{className:"line",children:(0,s.jsx)(n.span,{style:{color:"#50A14F","--shiki-dark":"#98C379"},children:" <strong"})}),"\n",(0,s.jsx)(n.span,{className:"line",children:(0,s.jsx)(n.span,{style:{color:"#50A14F","--shiki-dark":"#98C379"},children:" >Open your browser's"})}),"\n",(0,s.jsx)(n.span,{className:"line",children:(0,s.jsx)(n.span,{style:{color:"#50A14F","--shiki-dark":"#98C379"},children:" <a"})}),"\n",(0,s.jsx)(n.span,{className:"line",children:(0,s.jsx)(n.span,{style:{color:"#50A14F","--shiki-dark":"#98C379"},children:' href="https://developer.chrome.com/docs/devtools/performance"'})}),"\n",(0,s.jsx)(n.span,{className:"line",children:(0,s.jsx)(n.span,{style:{color:"#50A14F","--shiki-dark":"#98C379"},children:' rel="nofollow"'})}),"\n",(0,s.jsx)(n.span,{className:"line",children:(0,s.jsx)(n.span,{style:{color:"#50A14F","--shiki-dark":"#98C379"},children:" >Performance Tab</a"})}),"\n",(0,s.jsx)(n.span,{className:"line",children:(0,s.jsx)(n.span,{style:{color:"#50A14F","--shiki-dark":"#98C379"},children:" >"})}),"\n",(0,s.jsx)(n.span,{className:"line",children:(0,s.jsx)(n.span,{style:{color:"#50A14F","--shiki-dark":"#98C379"},children:" and refresh this page to see the CPU profile.</strong"})}),"\n",(0,s.jsx)(n.span,{className:"line",children:(0,s.jsx)(n.span,{style:{color:"#50A14F","--shiki-dark":"#98C379"},children:" >"})}),"\n",(0,s.jsxs)(n.span,{className:"line",children:[(0,s.jsx)(n.span,{style:{color:"#50A14F","--shiki-dark":"#98C379"},children:"</p>`"}),(0,s.jsx)(n.span,{style:{color:"#0184BC","--shiki-dark":"#56B6C2"},children:" +"}),(0,s.jsx)(n.span,{style:{color:"#383A42",fontStyle:"inherit","--shiki-dark":"#E06C75","--shiki-dark-font-style":"italic"},children:" res"})]}),"\n",(0,s.jsx)(n.span,{className:"line",children:(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#ABB2BF"},children:" );"})}),"\n",(0,s.jsx)(n.span,{className:"line",children:(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#ABB2BF"},children:"}"})})]})})}),"\n",(0,s.jsxs)(n.p,{children:["As you can see, we have to minimize the scope of the code being executed when\r\nthe profiler is activated. Thus, the code is written so that the call to the\r\n",(0,s.jsx)(n.a,{href:"https://www.moonbitlang.com/blog/call-wasm-from-js#integrating-with-javascript:~:text=function%20cmarkWASM(md)",children:(0,s.jsx)(n.code,{children:"cmarkWASM()"})})," function is the only thing within that scope."]}),"\n",(0,s.jsxs)(n.p,{children:["On the other hand, we have chosen the 0.31.2 version of ",(0,s.jsx)(n.a,{href:"https://spec.c
1ommonmark.org/0.31.2",children:"the CommonMark\r\nSpecification"})," (a.k.a. ",(0,s.jsx)(n.code,{children:"spec.md"})," in the code above) as the input document\r\nfor the profiling mode. This is notably because of the document's richness\r\nparticularly in the employment of different Markdown features, in addition to\r\nits sheer length which can cause trouble for many Markdown parsers:"]}),"\n",(0,s.jsx)(s.Fragment,{children:(0,s.jsx)(n.pre,{className:"shiki shiki-themes one-light one-dark-pro",style:{backgroundColor:"#FAFAFA","--shiki-dark-bg":"#282c34",color:"#383A42","--shiki-dark":"#abb2bf"},tabIndex:"0",children:(0,s.jsxs)(n.code,{children:[(0,s.jsxs)(n.span,{className:"line",children:[(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#ABB2BF"},children:"> wc -l spec.md "}),(0,s.jsx)(n.span,{style:{color:"#A0A1A7",fontStyle:"italic","--shiki-dark":"#7F848E","--shiki-dark-font-style":"italic"},children:"# line count"})]}),"\n",(0,s.jsx)(n.span,{className:"line",children:(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#ABB2BF"},children:" 9756 spec.md"})}),"\n",(0,s.jsxs)(n.span,{className:"line",children:[(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#ABB2BF"},children:"> wc -w spec.md "}),(0,s.jsx)(n.span,{style:{color:"#A0A1A7",fontStyle:"italic","--shiki-dark":"#7F848E","--shiki-dark-font-style":"italic"},children:"# word count"})]}),"\n",(0,s.jsx)(n.span,{className:"line",children:(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#ABB2BF"},children:" 25412 spec.md"})})]})})}),"\n",(0,s.jsxs)(n.p,{children:['We have reorganized our frontend application so that clicking on the "Profiling"\r\nlink in the navigation bar will trigger the ',(0,s.jsx)(n.code,{children:"profileView()"})," function above,\r\ngiving the following:"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"profile-tab-stripped",src:i(52382).A+"",width:"1392",height:"1526"})}),"\n",(0,s.jsx)(n.p,{children:"If you have ever dug into performance optimization, this flame graph above\r\nshould look pretty familiar..."}),"\n",(0,s.jsxs)(n.p,{children:["Wait, what are ",(0,s.jsx)(n.code,{children:"wasm-function[679]"}),", ",(0,s.jsx)(n.code,{children:"wasm-function[367]"})," and so on? How are we\r\nsupposed to know which function corresponds to which number?"]}),"\n",(0,s.jsx)(n.p,{children:"It turns out we need to retain some debug information when building our Wasm\r\nartifact. After all, we have been using the following command to build our\r\nMoonBit project:"}),"\n",(0,s.jsx)(s.Fragment,{children:(0,s.jsx)(n.pre,{className:"shiki shiki-themes one-light one-dark-pro",style:{backgroundColor:"#FAFAFA","--shiki-dark-bg":"#282c34",color:"#383A42","--shiki-dark":"#abb2bf"},tabIndex:"0",children:(0,s.jsx)(n.code,{children:(0,s.jsxs)(n.span,{className:"line",children:[(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#ABB2BF"},children:"> moon -C cmarkwrap build --release "}),(0,s.jsx)(n.span,{style:{color:"#E45649","--shiki-dark":"#E06C75"},children:"--target"}),(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#56B6C2"},children:"="}),(0,s.jsx)(n.span,{style:{color:"#50A14F","--shiki-dark":"#98C379"},children:"wasm-gc"})]})})})}),"\n",(0,s.jsxs)(n.p,{children:["... and stripping is the standard behavior of ",(0,s.jsx)(n.code,{children:"moon"})," when producing a release\r\nbuild."]}),"\n",(0,s.jsxs)(n.p,{children:["Fortunately, there is an extra flag we can use to keep the symbol information\r\nwithout having to resort to a slow debug build: ",(0,s.jsx)(n.code,{children:"--no-strip"}),". Let's rebuild our\r\nproject with it:"]}),"\n",(0,s.jsx)(s.Fragment,{children:(0,s.jsx)(n.pre,{className:"shiki shiki-themes one-light one-dark-pro",style:{backgroundColor:"#FAFAFA","--shiki-dark-bg":"#282c34",color:"#383A42","--shiki-dark":"#abb2bf"},tabIndex:"0",children:(0,s.jsx)(n.code,{children:(0,s.jsxs)(n.span,{className:"line",children:[(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#ABB2BF"},children:"> moon -C cmarkwrap build --release --no-strip "}),(0,s.jsx)(n.span,{style:{color:"#E45649","--shiki-dark":"#E06C75"},children:"--target"}),(0,s.jsx)(n.span,{style:{color:"#383A42","--shiki-dark":"#56B6C2"},children:"="}),(0,s.jsx)(n.span,{style:{color:"#50A14F","--shiki-dark":"#98C379"},children:"wasm-gc"})]})})})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Note"})}),"\n",(0,s.jsxs)(n.p,{children:["Similarly, if we would like to use ",(0,s.jsx)(n.code,{children:"wasm-opt"})," on the resulting Wasm artifact,\r\nwe can use the ",(0,s.jsx)(n.code,{children:"--debuginfo"})," (or ",(0,s.jsx)(n.code,{children:"-g"}),") flag of ",(0,s.jsx)(n.code,{children:"wasm-opt"})," to preserve the\r\nfunction names in the optimized output."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"With the function names retained, we can finally see what is really going on in\r\nthe Performance Tab!"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"profile-tab",src:i(42480).A+"",width:"1392",height:"1526"})}),"\n",(0,s.jsx)(n.h2,{id:"analyzing-the-flame-graph",children:"Analyzing the Flame Graph"}),"\n",(0,s.jsx)(n.p,{children:"The flame graph as shown in the previous screenshot can provide a nice summary\r\nof function calls and their respective execution times in our code. In case you\r\nare not familiar with it, the main ideas behind it are as follows:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"The Y-axis"})," represents the call stack, with the topmost function being the\r\none that was called first;"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"The X-axis"})," represents the time spent in execution, with the width of each\r\nbox-shaped node
1corresponding to the total time spent in a certain function\r\ncall and its children."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Since we are investigating the performance of the Cmark library in particular,\r\nwe should move downwards and concentrate on the node for\r\n",(0,s.jsx)(n.code,{children:"@rami3l/cmark/cmark_html.render()"}),". Here, for example, we can clearly see that\r\nthe execution of ",(0,s.jsx)(n.code,{children:"render()"})," is divided into two main parts, as represented by\r\ntwo children nodes on the graph:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"@rami3l/cmark/cmark.Doc::from_string()"}),", which stands for the conversion of\r\nthe input Markdown document into a syntax tree;"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"@rami3l/cmark/cmark_html.from_doc()"}),", which stands for the rendering of the\r\nsyntax tree into the final HTML document."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["To get a better view, let's highlight the ",(0,s.jsx)(n.code,{children:"render()"}),' node in the flame graph\r\nwith a single click. This will tell Chrome to update the "Bottom-up" view to\r\nshow only the functions that are transitively called by ',(0,s.jsx)(n.code,{children:"render()"}),", and we will\r\nget something like the following:"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"profile-tab-focused",src:i(62908).A+"",width:"1392",height:"1526"})}),"\n",(0,s.jsx)(n.p,{children:'After sorting the items by self time (i.e. total time excluding child time) in\r\nthe "Bottom-up" view, we can easily find out the functions that have consumed\r\nthe most time on their own. This suggests that their implementation might be\r\nworth a closer look. Meanwhile, we would also want to try eliminating deep call\r\nstacks when possible, which can be found by looking for the long vertical bars\r\nin the flame graph.'}),"\n",(0,s.jsx)(n.h2,{id:"achieving-high-performance",children:"Achieving High Performance"}),"\n",(0,s.jsx)(n.p,{children:"During its development process, Cmark has been profiled hundreds of times using\r\nthe very method we have seen above in pursuit of satisfactory performance, but\r\nhow does it actually perform against popular JavaScript Markdown libraries?"}),"\n",(0,s.jsxs)(n.p,{children:["For this test, we have chosen ",(0,s.jsx)(n.a,{href:"https://github.com/micromark/micromark",children:"Micromark"})," and ",(0,s.jsx)(n.a,{href:"https://github.com/remarkjs/remark",children:"Remark"}),"---two well-known Markdown\r\nlibraries in the JavaScript ecosystem---as our reference. We have used a recent\r\nbuild of Chrome 133 in this test as our JS and Wasm runtimes, and have imported\r\n",(0,s.jsx)(n.a,{href:"https://github.com/tinylibs/tinybench",children:"Tinybench"})," to measure the average throughput of each library."]}),"\n",(0,s.jsx)(n.p,{children:"Below is the average throughput of these libraries converting a single copy of\r\nthe CommonMark spec to HTML on a MacBook M1 Pro:"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Test (Fastest to Slowest)"}),(0,s.jsx)(n.th,{children:"Samples"}),(0,s.jsx)(n.th,{children:"Average/Hz"}),(0,s.jsx)(n.th,{children:"\xb1/%"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"cmark.mbt (WASM-GC + wasm-opt)"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"21"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"203.66"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.strong,{children:"3.60"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"cmark.mbt (WASM-GC)"}),(0,s.jsx)(n.td,{children:"19"}),(0,s.jsx)(n.td,{children:"188.46"}),(0,s.jsx)(n.td,{children:"3.84"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"micromark"}),(0,s.jsx)(n.td,{children:"10"}),(0,s.jsx)(n.td,{children:"15.48"}),(0,s.jsx)(n.td,{children:"2.07"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"remark"}),(0,s.jsx)(n.td,{children:"10"}),(0,s.jsx)(n.td,{children:"14.28"}),(0,s.jsx)(n.td,{children:"3.16"})]})]})]}),"\n",(0,s.jsxs)(n.p,{children:["The results are quite clear: thanks to the continuous profiling-and-optimizing\r\nprocess, Cmark is now significantly faster than both JavaScript-based libraries\r\nby a factor of about 12x for Micromark and 13x for Remark. Furthermore,\r\n",(0,s.jsx)(n.code,{children:"wasm-opt"}),"'s extra optimization passes can give Cmark another performance boost,\r\nbringing these factors up to about 13x and 14x respectively."]}),"\n",(0,s.jsx)(n.p,{children:"In conclusion, the performance of Cmark is a testament to the power of MoonBit\r\nin providing visible efficiency improvements in an actual frontend development\r\nscenario."}),"\n",(0,s.jsxs)(n.p,{children:["If you are interested in the details of this demo, you may check out the final\r\ncode ",(0,s.jsx)(n.a,{href:"https://github.com/moonbit-community/cmark-frontend-example/tree/profiling",children:"on GitHub"}),". The code based used in\r\nbenchmarking is also available ",(0,s.jsx)(n.a,{href:"https://github.com/moonbit-community/cmark-frontend-example/tree/bench",children:"here"}),"."]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"New to MoonBit?"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"https://www.moonbitlang.com/download/",children:"Download MoonBit"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:["Explore\r\n",(0,s.jsx)(n.a,{href:"https://docs.moonbitlang.com/en/latest/tutorial/tour.html",children:"MoonBit Beginner's Guide"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:["Play with ",(0,s.jsx)(n.a,{href:"https://tour.moonbitlang.com/",children:"MoonBit Language Tour"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:["Check out ",(0,s.jsx)(n.a,{href:"https://docs.moonbitlang.com/en/latest/index.html",children:"MoonBit Docs"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:["Join our ",(0,s.jsx)(n.a,{href:"https://discord.gg/5d46MfXkfZ",children:"Discord"})," community."]}),"\n"]})]})}function d(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},7960:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/cover-66434e7cfd835e3f020b7dc0fd23cf6f.jpg"},62908:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/profile-tab-focused-d7d88b8f494132acffa7e0168076aaf4.png"},52382:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/profile-tab-stripped-9c3f95fe27cc47c087063f6f6db5e926.png"},42480:(e,n,i)=>{i.d(n,{A:()=>r});const r=i.p+"assets/images/profile-tab-2f9210abd7c013a6b6e22ec9b1efafcb.png"},98703:(e,n,i)=>{i.d(n,{R:()=>t,x:()=>a});var r=i(79474);const s={},o=r.createContext(s);function t(e){const n=r.useContext(o);return r.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:t(e.components),r.createElement(o.Provider,{value:n},e.children)}},54512:e=>{e.exports=JSON.parse('{"permalink":"/blog/profile-wasm-from-js","source":"@site/blog/2025-02-14-profile-wasm-from-js/index.md","title":"Profiling MoonBit-Generated Wasm using Chrome","description":"Profiling MoonBit-Generated Wasm using Chrome","date":"2025-02-14T00:00:00.000Z","tags":[{"inline":true,"label":"MoonBit","permalink":"/blog/tags/moon-bit"},{"inline":true,"label":"Wasm","permalink":"/blog/tags/wasm"},{"inline":true,"label":"JavaScript","permalink":"/blog/tags/java-script"}],"readingTime":7.03,"hasTruncateMarker":false,"authors":[],"frontMatter":{"description":"Profiling MoonBit-Generated Wasm using Chrome","slug":"profile-wasm-from-js","image":"/img/blogs/2025-02-14-profile-wasm-from-js/cover.jpg","tags":["MoonBit","Wasm","JavaScript"]},"unlisted":false,"prevItem":{"title":"How to Use Moonpad on Your Website","permalink":"/blog/how-to-use-moonpad"},"nextItem":{"title":"Consuming a High Performance Wasm Library in MoonBit from JavaScript","permalink":"/blog/call-wasm-from-js"}}')}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.