{"id":93,"date":"2025-02-17T19:27:58","date_gmt":"2025-02-17T11:27:58","guid":{"rendered":"https:\/\/web.eo2suite.cn\/blog\/?p=93"},"modified":"2025-02-17T19:27:58","modified_gmt":"2025-02-17T11:27:58","slug":"memory-share-transferable-objects","status":"publish","type":"post","link":"https:\/\/blog.eo2suite.cn\/?p=93&lang=en","title":{"rendered":"&#8220;Memory Share&#8221; &amp; &#8220;Transferable Objects&#8221;"},"content":{"rendered":"\n<p>In our previous optimization efforts, we ported x2t to WASM and attempted to share memory between JS and WASM, hoping to reduce data copying and improve performance. However, we encountered some problems in practice, especially <strong>**Segmentation Fault**<\/strong> errors, which prompted us to conduct a more in-depth study of WASM&#8217;s memory model and the mechanisms of JS and WASM shared memory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">WASM&#8217;s Linear Memory Model<\/h3>\n\n\n\n<p>A WASM instance has a linear memory, which is essentially an <code>`ArrayBuffer`<\/code> that can be accessed by both WASM code and JS code. This linear memory is the primary location where WASM programs store data.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>**Memory Page:**<\/strong> WASM&#8217;s linear memory is divided into memory pages of fixed size, typically 64KB per page.<\/li>\n\n\n\n<li><strong>**Memory Growth:**<\/strong> WASM programs can increase the size of the linear memory by using the <code>`memory.grow`<\/code> instruction, adding one page at a time. The maximum size of linear memory is limited by the configuration of the WebAssembly.Memory instance, typically 4GB (65536 pages).<\/li>\n\n\n\n<li><strong>**Address Space:**<\/strong> WASM code accesses memory through a linear address, which is the offset relative to the starting position of the linear memory.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">How JS Accesses WASM Memory<\/h3>\n\n\n\n<p>JS can obtain the <code>`ArrayBuffer`<\/code> object of the linear memory through the <code>`buffer`<\/code> property of the <code>`WebAssembly.Memory`<\/code> instance. After getting the <code>`ArrayBuffer`<\/code>, JS can read and write WASM&#8217;s memory just like operating on a normal array.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const wasmMemory = new WebAssembly.Memory({ initial: 10 }); \/\/ Initial 10 pages\nconst buffer = wasmMemory.buffer; \/\/ Get ArrayBuffer\nconst uint8Array = new Uint8Array(buffer); \/\/ Create TypedArray view\n\n\/\/ JS writes data to WASM memory\nuint8Array&#91;0] = 42;\n\n\/\/ Read data from WASM memory\nconst value = uint8Array&#91;0];\nconsole.log(value); \/\/ Output 42<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Challenges of Sharing Memory Between JS and WASM<\/h3>\n\n\n\n<p>Although JS can directly access WASM&#8217;s linear memory, sharing memory also brings some challenges:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>**Memory Alignment:**<\/strong>\n<ul class=\"wp-block-list\">\n<li>Different types of data need to be aligned at specific byte boundaries. For example, 32-bit integers usually need 4-byte alignment.<\/li>\n\n\n\n<li>If JS and WASM access unaligned memory in different ways, it may lead to performance degradation or even errors.<\/li>\n\n\n\n<li><strong>**Solution:**<\/strong> Ensure that JS and WASM use the same memory alignment. The <code>`DataView`<\/code> object can be used for precise memory access.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>**Data Type Conversion:**<\/strong>\n<ul class=\"wp-block-list\">\n<li>JS and WASM use different data type representations. For example, JS numbers are 64-bit floating-point numbers, while WASM can use 32-bit integers, 64-bit integers, and other types.<\/li>\n\n\n\n<li>Type conversion is required when passing data between JS and WASM.<\/li>\n\n\n\n<li><strong>**Solution:**<\/strong> Use <code>`TypedArray`<\/code> (e.g., <code>`Int32Array`<\/code>, <code>`Float64Array`<\/code>) to create a view in JS that matches WASM&#8217;s memory layout.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>**Race Conditions:**<\/strong>\n<ul class=\"wp-block-list\">\n<li>If JS and WASM access the same block of memory simultaneously, race conditions may occur, leading to data inconsistency.<\/li>\n\n\n\n<li><strong>**Solution:**<\/strong> Use the <code>`Atomics`<\/code> API for synchronization. The <code>`Atomics`<\/code> API provides some atomic operations, such as atomic add, atomic subtract, atomic compare and exchange, which can ensure the safety of concurrent access.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>**Causes and Solutions for Segmentation Faults:**<\/strong>\n<ul class=\"wp-block-list\">\n<li>The Segmentation Fault errors we encountered may be caused by the following reasons:\n<ul class=\"wp-block-list\">\n<li><strong>**Out-of-bounds Access:**<\/strong> JS or WASM accessed an address outside the linear memory range. This may be due to calculation errors or unsynchronized memory growth.<\/li>\n\n\n\n<li><strong>**Illegal Address:**<\/strong> Access to an unallocated or protected memory address.<\/li>\n\n\n\n<li><strong>**Data Race:**<\/strong> Even though the Atomics API was used, race conditions may still exist in some complex concurrent scenarios.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>**Solution:**<\/strong>\n<ul class=\"wp-block-list\">\n<li><strong>**Carefully Check Memory Access Logic:**<\/strong> Ensure that JS and WASM memory accesses are within the valid range of linear memory.<\/li>\n\n\n\n<li><strong>**Use Debugging Tools:**<\/strong> Use WASM debugging tools (e.g., Chrome DevTools&#8217; WebAssembly Inspector) to track memory access and find the root cause of the errors.<\/li>\n\n\n\n<li><strong>**Finer-Grained Synchronization:**<\/strong> Consider using finer-grained Atomics operations, or other synchronization mechanisms (e.g., mutexes) to protect shared memory.<\/li>\n\n\n\n<li><strong>**Avoid Sharing Complex Data Structures:**<\/strong> Avoid sharing complex data structures between JS and WASM as much as possible. If sharing is necessary, you can use serialization\/deserialization to transfer data.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Notes on Using the Atomics API for Synchronization<\/h3>\n\n\n\n<p>Although the <code>`Atomics`<\/code> API can be used to synchronize JS and WASM memory access, the following points also need to be noted:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>`Atomics.wait`<\/code> can only be called in a WebWorker and cannot be used in the main thread.<\/li>\n\n\n\n<li><code>`Atomics`<\/code> operations still have some performance overhead, and excessive use may affect performance.<\/li>\n\n\n\n<li>In complex concurrent scenarios, synchronization strategies need to be carefully designed to avoid problems such as deadlocks.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>What are Transferable Objects?<\/strong><\/h3>\n\n\n\n<p>Transferable Objects are special objects that, when transferred, do not involve data copying. Instead, they directly transfer ownership of the underlying memory of the object from one context to another. This means that after the transfer, the original context can no longer access the object, while the receiving context has full control over it.<\/p>\n\n\n\n<p>Common Transferable Objects include:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>`ArrayBuffer`<\/code><\/li>\n\n\n\n<li><code>`MessagePort`<\/code><\/li>\n\n\n\n<li><code>`ImageBitmap`<\/code><\/li>\n\n\n\n<li><code>`OffscreenCanvas`<\/code><\/li>\n<\/ul>\n\n\n\n<p><strong>**Advantages of Transferable Objects:**<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>**Zero-Copy:**<\/strong> This is the biggest advantage of Transferable Objects. Because there is no data copying, the transfer speed is very fast, especially suitable for transferring large data blocks.<\/li>\n\n\n\n<li><strong>**Avoid Memory Contention:**<\/strong> Due to the ownership transfer, only one context can access the object, thus avoiding memory contention and data inconsistency issues. This simplifies concurrent programming, eliminating the need for complex synchronization mechanisms.<\/li>\n<\/ol>\n\n\n\n<p><strong>**Disadvantages of Transferable Objects:**<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>**Ownership Transfer:**<\/strong> This is a limitation of Transferable Objects. After the transfer, the original context can no longer access the object. If the same data needs to be used simultaneously in multiple contexts, Transferable Objects are not suitable.<\/li>\n\n\n\n<li><strong>**Object Type Restrictions:**<\/strong> Only specific types of objects can be transferred as Transferable Objects. Common object types (e.g., ordinary JavaScript objects) do not support Transferable Objects.<\/li>\n\n\n\n<li><strong>**Unidirectional Transfer:**<\/strong> Transferable Objects are typically used for unidirectional data transfer. If data needs to be exchanged frequently between two contexts, the efficiency may not be high.<\/li>\n<\/ol>\n\n\n\n<p><strong>**Comparison of Transferable Objects Scheme and Shared Memory Scheme:**<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><th>Feature<\/th><th>Transferable Objects<\/th><th>Shared Memory (SharedArrayBuffer)<\/th><\/tr><tr><td>Data Copying<\/td><td>Zero-copy<\/td><td>Copy required (initial copy), subsequent modifications are shared<\/td><\/tr><tr><td>Concurrency Safety<\/td><td>Inherently safe, no synchronization mechanism required<\/td><td>Requires the use of Atomics API for synchronization, adding complexity<\/td><\/tr><tr><td>Object Types<\/td><td>Only specific types are supported (ArrayBuffer, MessagePort, ImageBitmap, OffscreenCanvas)<\/td><td>Supports any type, but serialization\/deserialization is required<\/td><\/tr><tr><td>Usage Scenarios<\/td><td>Suitable for scenarios involving unidirectional transfer of large data blocks, e.g., transferring processed image data from a WebWorker to the main thread<\/td><td>Suitable for scenarios where multiple threads\/contexts need to simultaneously access and modify the same data, e.g., collaborative document editing<\/td><\/tr><tr><td>Complexity<\/td><td>Simple<\/td><td>Complex, requires handling memory alignment, data type conversion, race conditions, etc.<\/td><\/tr><tr><td>Suitability<\/td><td>Suitable for WASM X2T scenarios. After X2T processes the file in WebWorker, the <code>`Editor.bin`<\/code> data is transferred to the main thread via Transferable Objects. After the main thread receives the data, the WebWorker no longer needs it. This avoids data copying and improves performance.<\/td><td>Less suitable for WASM X2T scenarios. The main requirement of X2T is to transfer processing results to the main thread, rather than sharing data. Using shared memory would require complex synchronization between JS and WASM, which would increase complexity.<\/td><\/tr><tr><td>WASM Integration<\/td><td>Very suitable: After WASM processes the data in WebWorker, it can use Transferable Objects to transfer the results to the main thread without any data copying.<\/td><td>Can be combined: JS and WASM can share linear memory, but careful handling of memory alignment, data type conversion, and race conditions is required.<\/td><\/tr><tr><td>WebWorker Support<\/td><td>Good<\/td><td>Good<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p><strong>**Conclusion:**<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Transferable Objects are a simple and efficient data transfer method, suitable for scenarios involving the unidirectional transfer of large data blocks.<\/li>\n\n\n\n<li>Shared Memory (SharedArrayBuffer) is suitable for scenarios where multiple threads\/contexts need to simultaneously access and modify the same data, but it is more complex to implement.<\/li>\n<\/ul>\n\n\n\n<p><strong>**In the WASM X2T scenario, Transferable Objects are likely to be a better choice.**<\/strong> This is because the main requirement of X2T is to transfer the processing results to the main thread, rather than sharing data. Using Transferable Objects can avoid data copying, improve performance, and simplify the complexity of concurrent programming.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In our previous optimization efforts, we ported x2t to  [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-93","post","type-post","status-publish","format-standard","hentry","category-3"],"_links":{"self":[{"href":"https:\/\/blog.eo2suite.cn\/index.php?rest_route=\/wp\/v2\/posts\/93","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=93"}],"version-history":[{"count":0,"href":"https:\/\/blog.eo2suite.cn\/index.php?rest_route=\/wp\/v2\/posts\/93\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.eo2suite.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=93"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.eo2suite.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=93"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.eo2suite.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=93"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}