diff --git a/packages/reactant-share/package.json b/packages/reactant-share/package.json index ab058e64..fa3078cd 100644 --- a/packages/reactant-share/package.json +++ b/packages/reactant-share/package.json @@ -28,12 +28,12 @@ "license": "MIT", "peerDependencies": { "broadcast-channel": "^5 || ^4", - "data-transport": "^4.3.6", + "data-transport": "^4.5.0", "react": "^16.12.0 || ^17" }, "dependencies": { "broadcast-channel": "^5.3.0", - "data-transport": "^4.3.6", + "data-transport": "^4.5.0", "reactant": "^0.123.0", "reactant-last-action": "^0.123.0", "reactant-router": "^0.123.0", diff --git a/website/blog/2023-12-29-how-to-build-high-performance-front-end-applications-based-on-multi-processing/index.md b/website/blog/2023-12-29-how-to-build-high-performance-front-end-applications-based-on-multi-processing/index.md index b97d12a3..fc845485 100644 --- a/website/blog/2023-12-29-how-to-build-high-performance-front-end-applications-based-on-multi-processing/index.md +++ b/website/blog/2023-12-29-how-to-build-high-performance-front-end-applications-based-on-multi-processing/index.md @@ -1,61 +1,61 @@ --- -title: How to build high-performance front-end applications based on multi-processing +title: How to Build High-Performance Front-End Applications Based on Multithreading author: unadlib tags: [reactant] --- ## Motivation -As modern front-end applications become larger, making full use of the device's CPU multi-cores to improve performance may become an important trend. +As modern front-end applications become larger, leveraging the device's CPU multi-cores to improve performance is becoming an important trend. -Front-end applications often run in a single browser window, and JavaScript runs on a single thread. This means that common web applications cannot take full advantage of a CPU's multiple-cores. As applications become larger and more complex, this can lead to performance problems and a poor user experience. +Front-end applications often run in a single browser window, where JavaScript executes on a single thread. This means that common web applications cannot fully utilize a CPU's multiple cores. As applications become larger and more complex, this can lead to performance problems and a poor user experience. -However, there is good news (the gradual phasing out of IE and Safari v16 support for Shared Worker). Modern browsers widely support various types of Workers, including Shared Workers. Shared Workers are a mature technology that allows multiple threads of JavaScript code to share data and communicate with each other. This makes them ideal for building multi-process front-end applications. +However, there is good news: modern browsers widely support various types of workers, including Shared Workers, as IE and Safari v16 are gradually being phased out (regarding Shared Worker support). Shared Workers are a mature technology allowing multiple threads of JavaScript code to share data and communicate with each other. This makes them ideal for building multithreaded front-end applications. -Multi-process front-end applications have several benefits. They can better resolve computation-intensive and slow-running JavaScript, which can improve performance and fluidity. They can also increase the number of concurrent requests that can be processed, which can improve the responsiveness of the application. +Multithreaded front-end applications offer several benefits. They can better handle computation-intensive and slow-running JavaScript, which can improve performance and responsiveness. They can also increase the number of concurrent requests that can be processed, which can further improve the application's responsiveness. -So we aim to explore a Web application framework that leverages multi-processing. +Therefore, we aim to explore a web application framework that leverages multithreading. -## Web application with Multi-Processing +## Web application with Multithreading -In a multi-process web architecture, we can leverage the Shared Web Apps concept of reactant-share to extend general multi-process programming. +In a multithreaded web architecture, we can leverage the Shared Web Apps concept of reactant-share to expand upon general multithreading programming. -Shared Web Apps allows running web applications in multiple browser windows or workers. It uses a unique front-end server (like a Shared Worker) to share web apps, whether it's code sharing, local storage sharing, state sharing, and so on. Regardless of how many browser windows are opened, there's always only one server application instance shared among multiple client applications for the Shared Web Apps. It enables Web Tabs to only perform rendering separation, thus making better use of the device's multi-cores and ensuring smooth operation of the web application. +Shared Web Apps allows web applications to run in multiple browser windows or workers. It uses a unique front-end server (such as a Shared Worker) to facilitate sharing across web apps, including code, local storage, state, and more. Regardless of the number of browser windows opened, there is always only one server application instance shared among multiple client applications in Shared Web Apps. This enables web tabs to focus solely on rendering, thereby better utilizing the device's multi-cores and ensuring the smooth operation of the web application. -Shared Web Apps provides the following benefits: +Shared Web Apps offers the following benefits: -- Reduces the mental burden of multi-process programming by implementing Isomorphism with a universal modular model. Isomorphism is the ability to execute the same code on both the server process, client process or other process, which simplifies multi-process programming. -- Ensures smooth operation of the front-end server process by transferring compute-intensive tasks to another process. This frees up the front-end server process to focus on business logic and the client process to focus on rendering, which improves performance and responsiveness. -- Improves request concurrency by using a better multi-process model. This allows the web application to handle more requests simultaneously. +- Reduces the mental burden of multithreading programming by implementing Isomorphism through a universal modular model. Isomorphism refers to the ability to execute the same code on the server thread, client thread, or other threads, simplifying multithreading programming. +- Ensures the smooth operation of the front-end server thread by transferring compute-intensive tasks to another thread. This frees up the front-end server thread to focus on business logic and the client thread on rendering, improving performance and responsiveness. +- Improves request concurrency through a more efficient multithreading model. ## Coworker based on reactant-share -Based on reactant-share, we have implemented the Coworker model, which facilitates state sharing across multiple processes, synchronizes state, and minimizes state changes with patches to ensure optimal performance in multi-process execution. +Building upon reactant-share, we have implemented the Coworker model, which facilitates state sharing across multiple threads, synchronizes state, and minimizes state changes using patches to ensure optimal performance in multithreaded execution. ![Workflow](./workflow.png) -The Coworker model consists of three types of processes: +The Coworker model comprises three types of threads: -- Client Process: The rendering process, which accepts shared state and only renders the web UI. It is lightweight to ensure smooth rendering. -- Server Process: The main process, which executes most of the application business logic. It should also ensure smooth running. -- Coworker Process: The process responsible for compute-intensive business or request-intensive logic. This process frees up the server process to focus on business logic. The server process can reduce blocking caused by JavaScript and is less susceptible to the effects of request-intensive logic. +- client thread: The rendering thread, responsible for accepting shared state and solely rendering the web UI. Its lightweight nature ensures smooth rendering. +- server thread: The main thread, responsible for executing the majority of the application's business logic. Smooth operation is also crucial for this thread. +- Coworker thread: This thread handles compute-intensive business logic or request-intensive logic. This frees the server thread to focus on business logic. This allows the server thread to reduce blocking caused by JavaScript and makes it less susceptible to the impact of request-intensive logic. -In "Base" mode, Reactant Shared Apps has only two processes: the Tab process and the Coworker process. The Coworker process uses a Web Worker by default. +In 'Base' mode, Reactant Shared Apps has only two threads: the Tab thread and the Coworker thread. By default, the Coworker thread utilizes a Web Worker. ## Implementation of Coworker -For the related principles of Reactant-Share, please see the following link: https://reactant.js.org/blog/2021/10/03/how-to-make-web-application-support-multiple-browser-windows +For details on the underlying principles of Reactant-Share, please refer to the following link: https://reactant.js.org/blog/2021/10/03/how-to-make-web-application-support-multiple-browser-windows -Coworker consists of two modules: +Coworker comprises two modules: -- **CoworkerAdapter**: Provides transport for communication between the server process and the coworker process. -- **CoworkerExecutor**: Handles synchronization of shared state between processes and custom Coworker type modules (used for proxy execution of coworkers). Coworkers are synchronously sent to the main process in one direction. Each time a Coworker syncs its state, it carries a sequence tag. If the sequence is abnormal, a complete Coworker state synchronization is triggered automatically to ensure the consistency of the shared state between the Coworker and the main process. +- **CoworkerAdapter:** Provides a communication channel between the server thread and the coworker thread. +- **CoworkerExecutor:** Manages the synchronization of shared state between threads and custom Coworker type modules (used for proxy execution of coworkers). Coworker state is synchronously sent to the main thread in one direction. Each time a Coworker synchronizes its state, it includes a sequence tag. If the sequence is out of order, a complete Coworker state synchronization is automatically triggered to ensure the consistency of the shared state between the Coworker and the main thread. ## Core Concepts and Advantages of Coworker -- **Isomorphism**: All processes execute the same code, which enhances the maintainability of multi-process programming in JavaScript. -- **Process Interaction based on the Actor Model**: Relying on the Actor model, this method reduces the cognitive load of multi-process programming in JavaScript. -- **Generic Transport Model**: Coworker supports any transport based on data-transport (https://github.com/unadlib/data-transport), so it can run in any container that supports transport, including SharedWorker. The following is a list of supported transports: +- **Isomorphism:** The ability for all threads to execute the same code enhances the maintainability of multithreading programming in JavaScript. +- **Thread Interaction based on the Actor Model:** By leveraging the Actor model, this approach reduces the cognitive load associated with multithreading programming in JavaScript. +- **Generic Transport Model:** Coworker supports any transport mechanism based on data-transport (https://github.com/unadlib/data-transport), enabling it to run in any container that supports transport, including SharedWorker. The following is a list of supported transports: - iframe - Broadcast - Web Worker @@ -66,23 +66,23 @@ Coworker consists of two modules: - WebRTC - Electron - Any other port based on data-transport -- **High Performance Based on Mutative**: [Mutative](https://github.com/unadlib/mutative) is faster than the naive handcrafted reducer and 10x faster than Immer. Updates to immutable data based on Mutative also maintain good performance. The patches obtained from the shared state update are used for state synchronization. -- **High Performance**: Due to Coworker taking on a large number of requests and compute-intensive tasks, the main process and rendering process maintain extremely high performance and user experience. -- **Support for Large Applications**: Reactant provides a complete module model design, including dependency injection and class first, as well as various modular design and dynamic module injections. -- **Separation of Service and Rendering View Modules**: Service modules, which are primarily based on business logic, can execute separately from view modules. This not only achieves separation of concerns but also allows the process to have its own containerization. -- **Graceful Degradation**: If the JavaScript host environment does not support SharedWorker, Coworker reverts to a regular SPA. This does not affect the behavior of any current application. +- **High Performance Based on Mutative:** [Mutative](https://github.com/unadlib/mutative) offers high performance, being faster than a naive handcrafted reducer and 10 times faster than Immer. Mutative also maintains good performance when updating immutable data. Patches generated from shared state updates are used for state synchronization. +- **High Performance:** Because Coworker handles a large number of requests and compute-intensive tasks, the main thread and rendering thread can maintain extremely high performance and a superior user experience. +- **Support for Large Applications:** Reactant offers a complete module model design, including dependency injection and a class-first approach, along with various modular design patterns and dynamic module injection capabilities. +- **Separation of Service and Rendering View Modules:** Service modules, primarily focused on business logic, can execute independently from view modules. This not only achieves separation of concerns but also allows each thread to have its own containerization. +- **Graceful Degradation:** If the JavaScript host environment does not support SharedWorker, Coworker gracefully degrades to a regular SPA. This does not affect the behavior of existing applications. ## API -`delegate()` - It will forward execution to the module and specified function proxies in Coworker, inspired by the Actor model. +`delegate()` - This function forwards execution to the specified module and function proxies within the Coworker, drawing inspiration from the Actor model. ## Examples -We will create a Counter application with Coworker based on the ‘Base‘ pattern. +We will create a Counter application using Coworker based on the 'Base' pattern. -1. Firstly, create app.tsx that contains the ProxyCounter module which needs to be executed in Coworker. +1. First, create `app.tsx`, which contains the `ProxyCounter` module that will be executed in the Coworker. -Its calling method `delegate(this.proxyCounter, 'increase', [])` is exactly the same as that of general Shared Web Apps. Whether it will be executed with a proxy in Coworker depends on the configuration of createApp. +Its calling method, `delegate(this.proxyCounter, 'increase', [])`, is identical to that used in general Shared Web Apps. Whether it is executed with a proxy in the Coworker depends on the configuration of `createApp`. ```tsx import React from "react"; @@ -127,7 +127,6 @@ export class AppView extends ViewModule { component(this: AppView) { const [count, proxyCount] = useConnector(() => [ this.count, - this.proxyCounter.count, ]); @@ -151,7 +150,7 @@ export class AppView extends ViewModule { } ``` -2. Create the main file `index.ts`. Here, we set `ProxyCounter` as a module of Coworker, and set `isCoworker` to `false`. +2. Create the main file, `index.ts`. Here, we configure `ProxyCounter` as a module of Coworker and set `isCoworker` to `false`. ```tsx import { render } from 'reactant-web'; @@ -187,7 +186,7 @@ createSharedApp({ }); ``` -3. Create the Coworker file `coworker.ts`. Here, we also set ProxyCounter as a module of Coworker, but set `isCoworker` to `true`. +3. Create the Coworker file, `coworker.ts`. Here, we also configure `ProxyCounter` as a module of Coworker, but this time set `isCoworker` to `true`. ```tsx import { @@ -220,35 +219,34 @@ createSharedApp({ }); ``` -So far, we have completed a basic application with a Coworker. Users trigger the `delegate(this.proxyCounter, 'increase', [])` in the main process via the UI. It will be forwarded to the coworker to execute the increase function of proxyCounter, and the shared state will automatically synchronize back to the main process. The rendering update is completed by the `useConnector()` Hook. +Thus far, we have created a basic application using a Coworker. Users trigger the `delegate(this.proxyCounter, 'increase', [])` function in the main thread via the UI. This action is forwarded to the coworker to execute the `increase` function of `proxyCounter`, and the shared state automatically synchronizes back to the main thread. The rendering update is handled by the `useConnector()` Hook. ## Q&A -**1. What are the challenges of multi-process programming with Coworker based on reactant-share?** - -State sharing and synchronization among processes in multi-process programming are relatively complex. Fortunately, Reactant-share ensures robustness through a shared state design with consistency. The dependencies between isomorphic modules of Coworker should also be taken into account. In development, concepts such as Domain-Driven Design should be practiced as much as possible to avoid incorrect module design. +**1. What are the challenges of multithreaded programming with Coworker based on reactant-share?** -**2. What are the possible use case types for Coworker?** +State sharing and synchronization among threads in multithreaded programming are inherently complex. Fortunately, Reactant-share ensures robustness through a consistent shared state design. The dependencies between isomorphic modules within Coworker should also be considered. During development, adopting concepts like Domain-Driven Design is recommended to prevent improper module design. -- **Request Queue** - Coworker is particularly suitable for modules with intensive requests. Running these in Coworker ensures they don't occupy the main process's request queue, allowing other main process requests to execute. -- **Large Task Execution Blocking** - When a computationally intensive task is executed, the application's main process should not be blocked. Such tasks are well suited for asynchronous execution in Coworker. -- **Isolatable Modules** - Coworker can also be used as a sandbox to isolate execution of some modules. +**2. What are the potential use cases for Coworker?** -**3. Are there any specific examples to demonstrate that Coworkers can improve application performance?** +- **Request Queue:** Coworker is particularly well-suited for modules handling a high volume of requests. Running these within Coworker prevents them from occupying the main thread's request queue, allowing other main thread requests to execute unimpeded. +- **Large Task Execution Blocking:** To avoid blocking the application's main thread during the execution of computationally intensive tasks, such tasks are ideal for asynchronous execution within Coworker. +- **Isolatable Modules:** Coworker can also serve as a sandbox to isolate the execution of specific modules. -In production, we've introduced Coworker into some specific scenarios for modules related to large data volume text matching. It resulted in a substantial performance improvement, even up to 10x more, significantly enhancing the user experience. +**3. Are there specific examples demonstrating how Coworker can improve application performance?** -Such computationally intensive text matching used to require users to wait more than 1s in the past, with the webpage being completely blocked. However, after using Coworker, the webpage blockage was reduced to less than 100ms (of course, the actual degree of improvement varies with different data sizes). +In production, we have implemented Coworker in specific scenarios for modules involving text matching with large data volumes. This resulted in a substantial performance improvement, in some cases up to 10x, significantly enhancing the user experience. Previously, such computationally intensive text matching would require users to wait over 1 second, during which the webpage would be completely blocked. However, after implementing Coworker, the webpage blockage was reduced to less than 100ms (the actual degree of improvement varies depending on data size). -**4. Is Coworker usable across different browsers, or does it only support within browser tabs? Can Coworker be used across tabs in different domains?** +**4. Is Coworker usable across different browsers, or is its support limited to within browser tabs? Can Coworker be used across tabs in different domains?** -Coworker is a multi-process model based on reactant-share, and reactant-share is based on data-transport. Therefore, we only need to use WebRTC transport from data-transport in CoworkerAdapter within Coworker to achieve cross-browser support. Additionally, to support usage across tabs in different domains, we can implement the use of Coworker under cross-domain tabs with an approach using iframe + shared worker. +Coworker is a multithreaded model built upon reactant-share, which in turn is based on data-transport. Therefore, utilizing the WebRTC transport from data-transport within Coworker's CoworkerAdapter is sufficient to achieve cross-browser support. Additionally, to enable usage across tabs in different domains, we can implement Coworker under cross-domain tabs using an iframe and shared worker approach. ## Conclusion -Front-end development is at a turning point, driven by advances in front-end technology and browser capabilities. Multi-core CPUs and multi-process tools such as Shared Workers and other Workers are now being used to great effect in front-end development. The emergence of Shared Web Apps with Coworker introduces a new multi-process model for front-end applications, which significantly improves application performance, user experience, and code maintainability. For developers, this means more technical choices and challenges, but also more opportunities and potential. +Front-end development is at a turning point, driven by advancements in front-end technology and browser capabilities. Multi-core CPUs and multithreading tools like Shared Workers and other Workers are now being utilized effectively in front-end development. The emergence of Shared Web Apps with Coworker introduces a novel multithreading model for front-end applications, significantly improving application performance, user experience, and code maintainability. For developers, this signifies more technical choices and challenges, but also greater opportunities and potential. -Multi-process programming for front-end applications is likely to become a key solution for improving front-end performance. This would result in a smoother, more efficient, and more responsive user experience. +Multithreaded programming for front-end applications is likely to become a key solution for enhancing front-end performance. This will result in a smoother, more efficient, and more responsive user experience. - reactant-share Document:https://reactant.js.org/docs/shared-app - reactant-share Repo: https://github.com/unadlib/reactant/tree/master/packages/reactant-share + diff --git a/website/blog/2023-12-29-how-to-build-high-performance-front-end-applications-based-on-multi-processing/workflow.png b/website/blog/2023-12-29-how-to-build-high-performance-front-end-applications-based-on-multi-processing/workflow.png index c45bcbe7..bf76b488 100644 Binary files a/website/blog/2023-12-29-how-to-build-high-performance-front-end-applications-based-on-multi-processing/workflow.png and b/website/blog/2023-12-29-how-to-build-high-performance-front-end-applications-based-on-multi-processing/workflow.png differ diff --git a/yarn.lock b/yarn.lock index f04ae3e7..d5c242f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5069,10 +5069,10 @@ dargs@^7.0.0: resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== -data-transport@^4.3.6: - version "4.3.6" - resolved "https://registry.yarnpkg.com/data-transport/-/data-transport-4.3.6.tgz#6f51f5d91a9d43e5f4ec1b491d009e80c47e243c" - integrity sha512-YIhrpsHU4xy4BX4RH9cPkDvpF2YmCmVnyV6yJQ79MrSg74mEdMel16tGyL/BkCSZDned0sqBye8jC5fqQxa5PQ== +data-transport@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/data-transport/-/data-transport-4.5.0.tgz#c8112cf01c13d65181a1b60b6529b29f46084aa5" + integrity sha512-FWaTFU3O6FVAjIsa3kR2AwUbTSpz7Ijh51W/pQ+RULpLf/YuANERJFGhX4TGM4by2D5hGAdux6LttxlJQ/P+5Q== dependencies: uuid "^9.0.0"