-
-
Notifications
You must be signed in to change notification settings - Fork 16.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to make Express 2x faster #5998
Comments
@andrehrferreira v5 is available https://www.npmjs.com/package/express/v/5.0.0 defineGetter still in version 5 |
@cesco69 Yes, I checked earlier before posting, I will wait for the project maintainers to respond to the post to see if I implement the resolution and send it to them or if they will make the change themselves. |
And also... While req.secure = req.protocol === 'https'; instead of defineGetter(req, 'secure', function secure(){
return this.protocol === 'https';
}); |
Would it be beneficial to extend the res properties using the Object.create second argument? Line 30 in 344b022
for example var req = Object.create(http.IncomingMessage.prototype, {
xhr: {
configurable: true,
enumerable: true,
get () {
var val = this.req.headers["X-Requested-With"] || '';
return val.toLowerCase() === 'xmlhttprequest';
}
}
}) |
defineGetter(req, 'xhr', function xhr(){
var val = this.get('X-Requested-With') || '';
return val.toLowerCase() === 'xmlhttprequest';
}); VS req = Object.create(req, {
xhr: {
configurable: true,
enumerable: true,
get() {
var val = this.req.headers["X-Requested-With"] || '';
return val.toLowerCase() === 'xmlhttprequest';
}
}
}); RESULT defineGetter wins (3x FASTER)! |
@tjdav From what I've seen, the problem is precisely extending based on the request that already has many parameters/functions. Object.create and Object.defineProperty probably perform some type of validation that ends up weighing down the process. That's why the alternative used by Koa and Fastify is to use req and res as a direct property and create getters by calling the property. |
With For example, consider the difference between these two methods: Direct assignment: 4,937,680 ops/s const obj = {};
obj.prop = 42; Object.defineProperty: 1,191,010 ops/s const obj = {};
Object.defineProperty(obj, 'prop', {
value: 42,
writable: true,
enumerable: true,
configurable: true
}); what I don't understand is why some functions have been assigned to the request with direct assignment (eg.: https://github.com/expressjs/express/blob/5.0/lib/request.js#L257) while others are with Object.defineProperty see https://humanwhocodes.com/blog/2015/11/performance-implication-object-defineproperty/ and https://v8.dev/blog/fast-properties Using direrect assignment make all a bit faster |
Hi, I have tried (in PR #6004) to extend the request class Request extends http.IncomingMessage {
} ad set the getter without class Request extends http.IncomingMessage {
get query() {
var queryparse = this.app.get('query parser fn');
if (!queryparse) {
// parsing is disabled
return Object.create(null);
}
var querystring = parse(this).query;
return queryparse(querystring);
}
} all 1227 tests passing I'm on Windows and the express benchmark works only on linux. Can someone run this for me and show me if my PR improves performance? |
Run on Linux Mint 22, on a Ryzen 3900X, with Node 22.9.0 Unfortunately there were no significant changes: Flame chart using flame with default settings and one 30sec run with 10 middleware, 100 conn |
I'm testing my project using the same parameters and functions that exist in Express and the result is well balanced. I'll leave it here for reference. Obviously, it needs to be adapted to the reality of Express, which uses pure JavaScript. https://github.com/andrehrferreira/cmmv-server/blob/main/packages/server/lib/request.ts https://github.com/andrehrferreira/cmmv-server/blob/main/packages/server/lib/response.ts It is not yet 100% implemented but preliminary results have been:
https://github.com/andrehrferreira/cmmv-server/blob/main/tools/benchmarks/benchmarks-allservers.js |
@andrehrferreira Wow! cmmv is really close to fastify! |
@cesco69 I honestly want Express to solve the performance problem so I don't have to use my project, it's a lot of work, but since my focus is SEO and keeping the application's TTFB low, I need the request latency to be as low as possible. |
Interesting, I was looking at router module the other day and I think using something smarter than a liner search approach for route matching might make it a little bit fast. Maybe it could improve the benchmarks for requests handled per second but I am skeptical. cc: @wesleytodd |
@IamLizu Have you taken a look at https://www.npmjs.com/package/find-my-way ? |
@andrehrferreira congratulations, your server is very interesting. I wonder if you think you can make a pull request on express and get the same performance without breaking changes? |
@andrehrferreira yes, I have. That said, I am patiently waiting to see what others think about this. Folks have a plan to make Express faster and compare the benchmarks with its own earlier version. |
@nigrosimone I'm thinking about doing this, but it's a big change so I'd prefer the maintainers to signal whether they prefer to do it or want me to do it. |
@IamLizu I'm using it in my project and it's more efficient than the Express router, and the same one used in Fastify. |
@IamLizu perhaps it would be useful to have a backlog of ideas to implement to improve performance, so those who want to help can work on something |
I speculate that Express uses getters to lazily compute property values, thus avoiding calculating properties that users do not access. However, for optimizations (fast mode) it might be better to define “get” directly on the object. |
yep, sure, but seems it's more slower add getter instead of compute the properties. |
Some properties can be static, eg. defineGetter(req, 'protocol', function protocol(){
var proto = this.connection.encrypted
? 'https'
: 'http';
var trust = this.app.get('trust proxy fn');
if (!trust(this.connection.remoteAddress, 0)) {
return proto;
}
// Note: X-Forwarded-Proto is normally only ever a
// single value, but this is to be safe.
var header = this.get('X-Forwarded-Proto') || proto
var index = header.indexOf(',')
return index !== -1
? header.substring(0, index).trim()
: header.trim()
});
defineGetter(req, 'secure', function secure(){
return this.protocol === 'https';
}); |
Switch to Deno at runtime and obtain 3 times performance improvement. |
@zhang-wenchao Sorry but I don't trust projects guided by Ryan Dahl after all the history that happened with node |
@andrehrferreira eg.: const express = require('express');
const http = require('http');
express['request'] = {
// ...
raw: http.IncomingMessage.prototype
};
express['response'] = {
// ...
raw: http.ServerResponse.prototype
};
const app = express()
app.get('/', function (req, res) {
res.send('Hello World')
})
app.listen(3000) |
@cesco69 It's a very big change, it's not as simple as it seems, especially since this change needs to ensure that all the project's existing tests pass. |
I believe the primary challenge lies in the custom middleware that interacts with the properties of the HTTP module. One potential solution to avoid breaking existing functionality is to introduce this change behind an opt-in configuration in a future 6.0.0 branch. Here's a suggested implementation: const express = require('express');
const app = express({ raw: true }); // Opt-in raw property for request/response
app.get('/', function (req, res) {
res.send('Hello World');
});
app.listen(3000); This approach allows users to test the changes and verify that all middleware remains compatible. Additionally, custom middleware developers can check for the existence of this property and adjust its behavior accordingly (if need to access to the http request/response): app.use((req, res, next) => {
if (req.raw) {
// Handle the raw request
req.raw.xyz
} else {
// Handle the standard request
req.xyz
}
}); By implementing this feature as opt-in, we can ensure that users have the flexibility to adopt it at their own pace while maintaining backward compatibility. |
@cesco69 It is possible to change it while maintaining exactly the same behavior that already exists today by creating gates that directly call raw as an abstraction, and just not merging the classes that works well, it takes a little work to do, there is no need to do anything palliative. |
@andrehrferreira You could try doing this PR as a POC. I think the Express team isn't very interested, but perhaps, seeing the results, they might change their minds. I can help you test it on some of my projects. |
@nigrosimone In my personal project I have already made this change, I will leave the results below, its name is CMMV (https://github.com/andrehrferreira/cmmv-server)
|
Yep! I have see your awesome projects! But i have Legacy project with Express to Speed up 🥲 |
@nigrosimone It's a big change, it will take a few days to make it 100% and pass all the tests, my concern is wasting time doing this PR and it not being in the interest of the maintainers to change the base structure needed to improve performance, since version 5 is basically the same code as version 4. |
I understand and you're right, but if the team doesn't care about your idea, you could fork "Express" and call it for example "Fastpress" as a drop to replace Express and anyone who uses Express could switch to yours and a new community could be born around it. You are working on your own server anyway, so I believe you have the motivation and skills to carry out a large project independently. |
@nigrosimone It is not my goal to honestly fork Express, because if I were to do that I would just have to adapt my project to have the same features and pass all the Express tests, what I want is to not have to maintain independent code and solve Express' problems to use it again in my products, it is a lot of work to maintain the code and I have no way of dedicating myself to that. |
@dougwilson @wesleytodd give instructions to @andrehrferreira, whether he should work or we can abandon this idea. |
Hi @cesco69 I'm sure you have put in a lot of work, but apologies I am no longer involved in Express these days. There are a lot of nice people who are, check out the README for the list 👍 |
Maybe @UlisesGascon can help you |
Guys, leave a +1 for keep more attention on this isuue on chromium/V8/Turbofan: Object.defineProperty is slow |
Thanks for the ping @bjohansebas! Currently, in the @expressjs/express-tc, we are discussing priorities for 2025 and what to focus on after the v5 release. 100% performance will be part of it, and I am glad to see that the community is heavily interested in helping with this matter ❤️ In a few weeks, we can organize a solid backlog and start focusing our energies on making Express faster and faster with each release ⚡ |
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
While I love this discussion a lot, I marked some comments as "off-topic" (as requested) to keep the discussion around the main idea. As mentioned in expressjs/discussions#173.
That said, I think that is great to learn from others and see how much of this ideas and improvements we can adapt to Express without sacrificing stability, security, etc... |
Hello friends of the community, how are you?
First of all, I always like to make it clear that I simply love the Express project. I have been using it in my projects for over 8 years. Therefore, this post seeks to help improve the project even more. I recently opened an inquiry regarding the performance of Express compared to other HTTP server options available. The focus on the release of version 5 was mentioned first. I fully agree that there are several priority issues in the project. However, on my own initiative, I began to study the codes more deeply to try to understand what causes Express to have a lower performance than Fasfity or Koa, for example. So I started a reinterpretation by implementing the same Express functions in a new project. In my specific case, my focus is on integrating Vite functionalities into my HTTP server and creating other layers of abstraction such as decorators. However, using Express as a base, during the development of this project I realized that the biggest performance problem that Express faces is related to the use of 'Object.defineProperty', I'll explain why.
Both Koa and Fastify use an approach of creating a new Request and Response object, defining getters and applying the objects generated by HTTP and HTTP2 from nodejs as a 'raw', assigning them as a simple property, through the getters retrieving the necessary data such as headers, body, params and queries, Express assigns getters dynamically in both the request and response using the "defineGetter" function, I understand that the way it was done takes advantage of the entire structure of original properties and functions, but the processing cost to dynamically add these getters is very high, even using Reflect.defineProperty as an alternative there is still a delay that considerably reduces the amount of requests that Express can process per second.
To make it clearer, I'll leave a simple test comparing the two approaches:
For the test I am using my personal computer a Core i9 10980XE, with 256GB DDR4, Sansung NVME SSD, on Windows 10, using WLS from Ubuntu 22.04, Node in version 20.17.0, Autocannon in version 7.15.0
Object Define Property
Result:
Object Create
Result:
Note that in the examples above we are defining only 1 getter in the request, but this action occurs several times in both the request and the response, greatly reducing the number of requests per second that Express can serve. With this change, Express will have the same performance as Koa and Fastify. Basically, I know because I have already tested it by basically rewriting the request/response with the same functions currently present. I did not send a PR for the change because I was waiting for version 5 to be officially available to check if this point was changed. However, checking the version code I found that it is apparently the same as version 4.
I hope I have helped improve the project, and if you want my help to change the code I am available, see you later =)
The text was updated successfully, but these errors were encountered: