{"id":96,"date":"2025-02-17T19:32:14","date_gmt":"2025-02-17T11:32:14","guid":{"rendered":"https:\/\/web.eo2suite.cn\/blog\/?p=96"},"modified":"2025-02-17T19:32:14","modified_gmt":"2025-02-17T11:32:14","slug":"eo2suite-file-open-speed-optimization-a-step-by-step-exploration","status":"publish","type":"post","link":"https:\/\/blog.eo2suite.cn\/?p=96&lang=en","title":{"rendered":"EO2Suite File Open Speed Optimization: A Step-by-Step Exploration"},"content":{"rendered":"\n<p>As an engineer focused on user experience, I&#8217;ve always been attentive to the speed at which O-xygen opens files. Improving open speed and enhancing user experience are continuous goals for us. This article will share some of the explorations and attempts our team made in optimizing file open speed.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Problem Analysis: Where are the Performance Bottlenecks?<\/h3>\n\n\n\n<p>After some analysis, we identified that the time-consuming aspects of opening files mainly concentrate in the following stages:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>**JS SDK Loading:**<\/strong> Loading and parsing the large <code>`sdkjs`<\/code> file consumes a certain amount of time.<\/li>\n\n\n\n<li><strong>**Font File Loading:**<\/strong> Rendering text requires loading multiple font files, and some of these files are large in size, taking time to load.<\/li>\n\n\n\n<li><strong>**X2T Parsing:**<\/strong> Invoking <code>`x2t`<\/code> parses the file into <code>`Editor.bin`<\/code>, which is then loaded into the JS environment.<\/li>\n<\/ol>\n\n\n\n<p>Among these, <code>`x2t`<\/code> is a key step involving file format conversion, and it&#8217;s an indispensable part of the document opening process.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Solution Exploration: Various Attempts and Considerations<\/h3>\n\n\n\n<p>To address these bottlenecks, our team brainstormed and tried several optimization solutions, some of which also brought new insights.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>**Font File Caching + Memmap?**<\/strong>We had considered caching font files and then using <code>`memmap`<\/code> to load them directly. <code>`memmap`<\/code> can map files into memory, reducing redundant reads. To implement this solution, we also studied Electron&#8217;s <code>`nodeIntegration`<\/code> API, trying to invoke the Node.js environment in the rendering thread.<br><code><em><a href=\"https:\/\/www.electronjs.org\/docs\/latest\/api\/browser-window#new-browserwindowoptions nodeIntegration\">https:\/\/www.electronjs.org\/docs\/latest\/api\/browser-window#new-browserwindowoptions nodeIntegration<\/a>  <\/em><\/code><br><code><em>Testing npm library <\/em><\/code><a href=\"https:\/\/www.npmjs.com\/package\/mmap-io \"><em><code>https:\/\/www.npmjs.com\/package\/mmap-io<\/code> <\/em><\/a><br>However, in practical attempts, we found that <code>`memmap`<\/code> had some issues: Page Fault Remap might block the rendering thread, causing UI stuttering. Additionally, considering Electron version upgrades and V8 engine restrictions on external memory access, we ultimately abandoned this solution.<\/li>\n\n\n\n<li><strong>**V8 Heap Snapshot?**<\/strong>We also considered using V8 Heap Snapshot to accelerate loading. However, we quickly realized that a Heap Snapshot is just a memory snapshot; essentially a data file, it cannot be directly used for code execution. JavaScript code still needs to be parsed and compiled before it can be executed, and this process cannot be omitted.<br><code><em>Heap Snapshot is not executable code: A Heap Snapshot is a snapshot of the V8 engine's memory state; It is essentially a data file containing information such as objects, types, and references. It's not directly executable JavaScript code or compiled machine code. Compilation is necessary: The JavaScript engine must first parse JavaScript code into an Abstract Syntax Tree (AST), and then compile the AST into machine code before it can be executed. This compilation process is unavoidable. A Heap Snapshot can only be generated after code execution.<\/em><\/code><br>Therefore, this solution also failed to achieve the desired effect.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">New Ideas: The Application of WASM<\/h3>\n\n\n\n<p>During the exploration, we gradually turned our attention to WASM: <strong>**Running <\/strong><code>`x2t`<\/code><strong> in WASM!**<\/strong><\/p>\n\n\n\n<p>Doing so has the following potential advantages:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>**Improve File Parsing Efficiency:**<\/strong> WASM has execution efficiency close to native code, which can improve the parsing speed of <code>`x2t`<\/code>.<\/li>\n\n\n\n<li><strong>**Enhance Security:**<\/strong> WASM runs in a sandbox environment, which can reduce the risk of malicious files affecting the backend.<\/li>\n<\/ol>\n\n\n\n<p>Excitingly, we found that a developer on GitHub had already completed the task of compiling <code>`x2t`<\/code> to WASM: <a href=\"https:\/\/github.com\/cryptpad\/onlyoffice-x2t-wasm\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/cryptpad\/onlyoffice-x2t-wasm<\/a>.<\/p>\n\n\n\n<p>With the help of WASM, we can move the process of calling the executable program via Node.js <code>`child_process.spawn`<\/code> to the rendering thread. Furthermore, we can consider passing the <code>`Editor.bin`<\/code> content directly through memory to the rendering process, reducing file read\/write steps.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Technical Research: JS and WASM Data Interaction<\/h3>\n\n\n\n<p>After determining the direction of WASM, we need to solve an important problem: how to efficiently exchange data between JS and WASM?<\/p>\n\n\n\n<p>We consulted AI assistants about various methods of JS and WASM data exchange and obtained the following solutions:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ 1. Direct numeric parameter passing (fastest, but limited to numbers)\n\/\/ 2. Linear Memory Access (high performance for bulk data)\n\/\/ 3. SharedArrayBuffer (zero-copy, but requires cross-origin isolation)\n\/\/ 4. Structured Data Exchange (for complex objects)\n\/\/ 5. Transferable Objects (e.g., ArrayBuffer)\n\/\/ 6. WebAssembly.Table for function references\n\/\/ 7. Streaming SIMD Data\n\/\/ 8. Asyncify for Async Operations\n\/\/ 9. Web Workers with SharedArrayBuffer\n\/\/ 10. Atomics for Synchronization<\/code><\/pre>\n\n\n\n<p>To find a suitable solution for us, we conducted performance testing.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Performance Testing: Data-Driven Decisions<\/h3>\n\n\n\n<p>With the assistance of tools, we wrote a series of test programs and conducted relatively detailed performance tests on browser-related APIs (WASM Shared Memory, Atomics, SharedArrayBuffer).<\/p>\n\n\n\n<p>The test results showed:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>In the JS rendering thread and WebWorker, the performance difference is not significant, but processing large chunks of data may block the UI.<\/li>\n\n\n\n<li>WASM requires the use of <code>`WebAssembly.Memory`<\/code> to instantiate objects, and <code>`SharedArrayBuffer`<\/code> objects cannot be directly passed to WASM for use.<\/li>\n\n\n\n<li>It is feasible not to use <code>`WebAssembly.Memory`<\/code> to instantiate Memory objects, but to access the memory in WASM directly through WASM-instantiated objects.<\/li>\n\n\n\n<li><code>`WebAssembly.Memory`<\/code> has memory size limitations.<\/li>\n\n\n\n<li>When JS and WASM alternately operate on the same memory location, problems may occur, and the Atomics API needs to be used for synchronization. However, even with the Atomics API, problems may still occur when WASM reads and writes data in some cases.<\/li>\n<\/ul>\n\n\n\n<p>We also tested and optimized the performance of WASM reading and writing files and achieved some progress:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><th>Data Size<\/th><th>Before Optimization (ms)<\/th><th>After Optimization (ms)<\/th><\/tr><tr><td>64M<\/td><td>11805.45<\/td><td>134.99<\/td><\/tr><tr><td>256M<\/td><td>46676.62<\/td><td>576.21<\/td><\/tr><tr><td>512M<\/td><td>94539.74<\/td><td>1264.35<\/td><\/tr><tr><td>1G<\/td><td>188446.26<\/td><td>2676.79<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>In addition, we also tested the performance of <code>`Transferable Objects`<\/code>. The results show that using <code>`Transferable Objects`<\/code> to transfer large files in WebWorker can bring significant performance improvements.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><th>Scenario<\/th><th>Time Consumed (ms)<\/th><\/tr><tr><td>IPC<\/td><td>1788<\/td><\/tr><tr><td>WebWorker<\/td><td>357.599<\/td><\/tr><tr><td>WebWorkerTransfer<\/td><td>145.4<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Regarding data transfer, we compared memory sharing and Transferable Objects. Memory sharing requires complex synchronization mechanisms, while Transferable Objects provide a zero-copy transfer method that &#8220;transfers ownership.&#8221; Considering that the WASM X2T scenario is to transfer the processing results to the main thread, rather than sharing data, we ultimately chose the Transferable Objects solution. This avoids data copying, improves performance, and simplifies the complexity of concurrent programming. For detailed information, please refer to <mark><a href=\"https:\/\/web.eo2suite.cn\/blog\/2025\/02\/17\/memory-share-transferable-objects\/\">this article<\/a><\/mark>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Solution Selection: WebWorker + WASM X2T<\/h3>\n\n\n\n<p>Considering the test results comprehensively, we chose the following solution:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Execute WASM X2T in the rendering process through WebWorker.<\/li>\n\n\n\n<li>Use Electron&#8217;s <code>`nodeIntegrationInWorker`<\/code> to process files using Node.js&#8217;s <code>`fs`<\/code> module in the Worker.<\/li>\n\n\n\n<li>Use Transferable Objects to transfer data between the main thread and the WebWorker.<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Font File Processing: Custom Protocol<\/h3>\n\n\n\n<p>It is worth mentioning that the current version of O-xygen already uses WASM to process font source files. The loading method uses a custom protocol, which can achieve an effect similar to direct disk IO.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Summary and Outlook<\/h3>\n\n\n\n<p>Currently, we have completed the design and technical verification of the file open speed optimization solution. Whether the solution can achieve the desired effect requires further testing after actual development is completed.<\/p>\n\n\n\n<p>This optimization process has been full of challenges, and we are constantly exploring and learning. In subsequent development, we will continue to focus on performance and continuously optimize the user experience.<\/p>\n\n\n\n<p>If you have experience in file processing optimization, you are welcome to share your insights in the comments!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>As an engineer focused on user experience, I&#8217;ve a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":128,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-96","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-3"],"_links":{"self":[{"href":"https:\/\/blog.eo2suite.cn\/index.php?rest_route=\/wp\/v2\/posts\/96","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.eo2suite.cn\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.eo2suite.cn\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.eo2suite.cn\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.eo2suite.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=96"}],"version-history":[{"count":0,"href":"https:\/\/blog.eo2suite.cn\/index.php?rest_route=\/wp\/v2\/posts\/96\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.eo2suite.cn\/index.php?rest_route=\/"}],"wp:attachment":[{"href":"https:\/\/blog.eo2suite.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=96"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.eo2suite.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=96"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.eo2suite.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=96"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}