Comments (13)
One major difference it that you pay the compilation cost only on the first compilation and each following compilation is fast after that. tapable also optimizes for the polymorphic case, so when different plugins are added and multiple hooks are used. The new Function
approach allows v8 to optimize the call
method for the compilation of plugins added and even allows to inline plugin functions into the generated code for call.
But I played around with benchmarking the different approaches with the following conclusions:
- Here is the code I used if you want to play with it: https://gist.github.com/sokra/7c09cec71b4753a774ab07cc565087bd
- It tries to be a bit more realistic by providing multiple different functions and arguments.
- There is a large code generation overhead in tapable to generate the code for the
new Function()
. With large I mean in the µs range, e. g. for 50 plugins about 80µs for me.- Not sure if we can compare that to simplified implementations, since it also handles return values, interceptors, async calls, etc.
...args
is slower than generating a new function with the right arguments.- Unrolling the taps in a
new Function
is faster than a loop/forEach. Expect when the plugin count is really high. - The
CALL_DELEGATE
approach is only faster than checking for deopt in the generated code when the hook is called more than 10000 times.CALL_DELEGATE
avoids the extra check in each call.
=> While tapable is a bit slower when Hooks are small or called only a few times, it's very faster when hooks are called often. But when Hooks are small or called only a few times, they are also very fast in general, so maybe that's not the thing we should focus on.
Anyway maybe it makes sense to have a "interpreter mode" which is used for the first 10 calls, so we improve performance on hooks that are only calls once.
And we could look into caching the code generation when Hooks are recreated.
from tapable.
JavaScript 引擎的编译优化:
new Function 方法: 当你使用 new Function 创建一个函数时,这个函数通常会被 JavaScript 引擎视为一个独立的脚本。这意味着它可以进行更彻底的优化。由于该函数是在运行时创建的,引擎有机会根据当前上下文优化它,这可以包括内联优化、避免不必要的变量查找等。
forEach 循环: 相比之下,使用 forEach 循环直接调用数组中的函数则涉及到多次函数调用的开销。每次循环都会调用一个函数,这可能会导致更多的上下文切换和较少的优化机会。每次函数调用都涉及到创建新的调用栈、传递参数等开销。
函数调用的开销:
在 forEach 循环中,每次迭代都会进行一次函数调用。这些调用涉及到创建调用上下文、传递参数、以及在调用栈上进出的开销。
而使用 new Function 方法创建的函数,在它的内部直接编码了所有的函数调用。这种方式减少了函数调用的次数,因为所有的调用都被内联到一个单独的函数体内。这就减少了调用栈的变化,提高了执行效率。
总之,使用 new Function 创建的函数,由于更优的编译优化和减少的函数调用开销,往往在性能上优于简单的 forEach 循环调用。然而,这种优化的程度可能会因 JavaScript 引擎的实现细节而有所不同,而且它也带来了代码的复杂性和可维护性的挑战。在实际应用中,选择哪种方法取决于具体场景和性能需求。
from tapable.
The number of calls in the above code is set to 100 0000 times, and the conclusion is correct,new Function performance is faster !!!
from tapable.
wow, you are so nice and patient. Thank you! My last question is, why not reset this.call with CALL_DELEGATE , but use this._call. such as
_resetCompilation() { this.call = CALL_DELEGATE ; this.callAsync = CALL_ASYNC_DELEGATE; this.promise = PROMISE_DELEGATE; }
from tapable.
Try to call the tap many many times
from tapable.
First of all thank you for your reply. I try to call the callback 10,000 times, and the result is that the official tapable synchook execution time is still dozens of times longer than I wrote. Does this mean that the method of dynamically generating the function body through new Function is not suitable? Finally, here are some tapable hooks I wrote myself. I tested that the execution time of these hooks is shorter than the official tapable hooks. Of course, the robustness is not as strong as the tapable official. mini-tapable
from tapable.
@sokra I just called the tap 10,000 times,and found that the official tapable sync hook took almost 60ms, while my synchook took
0.8ms.
from tapable.
I just called the tap 10,000 times
Try to call call
multiple times. That's what happens in practice. Registering 10000 plugins it unlikely, but the plugin handler could be called 10000 times (e. g. hooks in the javascript parser are called for every statement)
from tapable.
@sokra This time I call the tap 1000 times and call the call
10,000 times,the result as the image shows
`const { SyncHook } = require('tapable')
class MySyncHook{
constructor(argNames){
this.argNames = argNames;
this.tasks = []
}
tap(plugin, callback){
this.tasks.push(callback)
}
call(...args){
this.tasks.forEach(task => task(...args))
}
}
const hook = new SyncHook(['compilation'])
const myHook = new MySyncHook(['compilation'])
const compilation = { sum: 0 }
const myCompilation = { sum: 0}
for(let i = 0; i < 1000; i++){
hook.tap(plugin${i}
, (compilation) => {
compilation.sum = compilation.sum + i
})
myHook.tap(plugin${i}
, (compilation) => {
compilation.sum = compilation.sum + i
})
}
console.time('tapable')
for(let i = 0; i < 10000; i++){
hook.call(compilation)
}
console.timeEnd('tapable')
console.time('my')
for(let i = 0; i < 10000; i++){
myHook.call(myCompilation)
}
console.timeEnd('my')
`
I think you can take a moment to try.
Thank you!
from tapable.
@sokra I think it should be the new version of nodejs that has been optimized, and the performance is better than the function body dynamically generated by new Function.
from tapable.
I just tried it. When the number of calls is more than 17000 times, the efficiency of tapable does to be faster ! But in what scenarios will the call
be called so many times
from tapable.
`const { SyncHook } = require('tapable')
const CALL_DELEGATE = function(...args){
this.call = this._createCall(); // The function is dynamically generated when it is called for the first time
return this.call(...args)
}
class MySyncHook{
constructor(argNames){
this.argNames = argNames;
this.tasks = []
// this._call = CALL_DELEGATE;
this.call = CALL_DELEGATE;
}
tap(plugin, callback){
// This.call must be reset every time a new plugin is added
this.call = CALL_DELEGATE;
this.tasks.push(callback)
}
_createCall(){
const params = this.argNames.join(',');
return new Function(params, "this.tasks.forEach(task => task(" + params + "))")
}
// call(...args){
// this.tasks.forEach(task => task(...args))
// }
}
const hook = new SyncHook(['compilation'])
const myHook = new MySyncHook(['compilation'])
const compilation = { sum: 0 }
const myCompilation = { sum: 0}
for(let i = 0; i < 1000; i++){
hook.tap("plugin" + i, (compilation) => {
compilation.sum = compilation.sum + i
})
myHook.tap("plugin" + i, (compilation) => {
compilation.sum = compilation.sum + i
})
}
const count = 20000000;
console.time('tapable')
for(let i = 0; i < count; i++){
hook.call(compilation)
}
console.timeEnd('tapable')
console.time('my')
for(let i = 0; i < count; i++){
myHook.call(myCompilation)
}
console.timeEnd('my')
`
This time i register 1000 plugins and call the call
2000,0000 times. But i generate the this.call
function by new Function when i first call the this.call
function. The result is as the image shows:
from tapable.
When comparing the use of new Function to create and execute functions with directly using a forEach loop to execute functions, the performance differences primarily stem from two aspects: the compilation optimization of the JavaScript engine and the overhead of function calls.
JavaScript Engine's Compilation Optimization:
Using new Function Method: When you create a function using new Function, it is often treated by the JavaScript engine as an independent script. This means it can undergo more thorough optimization. As the function is created at runtime, the engine has the opportunity to optimize it based on the current context, which can include inline optimization, avoiding unnecessary variable lookups, etc.
Using forEach Loop: In contrast, directly calling functions in an array with a forEach loop involves the overhead of multiple function calls. Each loop iteration calls a function, which may lead to more context switching and fewer opportunities for optimization. Each function call involves creating a new call stack, passing arguments, etc.
Overhead of Function Calls:
In a forEach loop, each iteration involves a function call. These calls involve creating a calling context, passing parameters, and the overhead of entering and exiting the call stack.
However, a function created using the new Function method encodes all the function calls inside it. This approach reduces the number of function calls, as all calls are inlined into a single function body. This reduces the changes in the call stack, enhancing execution efficiency.
In summary, functions created using new Function, due to better compilation optimization and reduced function call overhead, tend to perform better than simple forEach loop calls. However, the degree of this optimization might vary depending on the implementation details of the JavaScript engine, and it also introduces challenges in code complexity and maintainability. In practical applications, the choice between these methods depends on the specific scenario and performance requirements.
from tapable.
Related Issues (20)
- Re-order taps after tap function is called to guarantee old taps are in correct order HOT 3
- what is the idea of reassignment a class propery
- AsyncParallelBailHook, I use tapAsync/callAsync, it doesn't work as expected HOT 3
- DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead HOT 2
- Not compatible with webpack v5 HOT 1
- Why hooks constructor can not use arguments like readme.md said HOT 1
- has `hooks.hookName.taps` been not recommanded? HOT 3
- i use tapable singlely in my project,but in my webpack,"node_modules" folders has no compile. HOT 4
- AsyncHook in types HOT 6
- May I ask some question?
- Is it a BUG? callback of tapAsync not work in AsyncSeriesLoopHook HOT 2
- is there an way to intercept waterfall hook result ?
- Why not put _resetCompilation in this.call HOT 1
- .tap() `before` parameter do not respects existing befores HOT 1
- TS Error HOT 3
- `context` property for interceptor is un-typed
- Re-order hooks
- [Bug] Async hook doesn't catch error when reject a falsy value HOT 6
- Facing TypeError: Cannot read properties of undefined (reading 'tap') HOT 7
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from tapable.