Bun vs Go Perf | Prime Reacts

Table of contents

When it comes to performance, Go consistently outshines Bun, proving that compiled languages have the edge over JavaScript in real-world applications.

In this discussion, we explore the comparison between Bun and Go, particularly focusing on their performance in handling web applications. Bun has shown promising results in previous benchmarks, prompting a deeper analysis against Go. In my view, Go operates in a different class compared to JavaScript, which is what Bun utilizes.

We will first compare Bun with Go using the standard library, specifically the SL API and SL devices endpoint, which returns a hardcoded device in JSON format to the client. Our analysis will concentrate on four golden signals, with a primary focus on latency, particularly the P99 percentile. For those unfamiliar with the differences between these technologies, it is important to note that Bun runs on JavaScript, while Go is a distinct programming language.

The expectation is that JavaScript should never be faster than Go due to the inherent complexities involved in the V8 engine. The V8 engine processes text, converts it into bytecode, and manages that bytecode with various optimization techniques. Observing memory usage reveals that it can spike significantly on a per-function basis, especially when functions are called multiple times. In V8, if a function is invoked frequently with similar objects, it may trigger just-in-time (JIT) compilation, which attempts to optimize performance.

While Bun utilizes JavaScript Core, the exact rules governing its performance may differ. However, it is widely understood that JIT compilation does not achieve the same speed as compiled languages. Reports suggest that Bun could be twice as slow as standard programming languages, although this remains a point of contention. As long as there are no significant programming issues, Go should outperform Bun substantially. This is because, during function calls, there is still a considerable amount of information being tracked, which adds overhead.

Next, we will evaluate throughput, which, in the context of web applications, refers to the number of requests per second that each application can handle. We will also assess saturation by measuring how full the service is. In a previous test conducted when Bun was released, Go demonstrated performance that was two to four times faster than Bun for equivalent tasks. This comparison will include CPU and memory usage of the applications relative to the limits defined in Kubernetes. Additionally, we will measure CPU throttling, as it significantly impacts performance; when a service is throttled, it leads to increased latency and degraded overall performance.

Finally, we will measure availability by examining the number of failed requests relative to the total number of requests over a specific period. While most benchmarks rely on synthetic tests, I aim to evaluate how these applications perform in real-world scenarios. This analysis will involve different use cases across various benchmarks. In this instance, we will incorporate a persistence layer using a MongoDB database. Each time the application receives a POST request, it will process the request body, generate a unique identifier, and save the item into MongoDB.

In addition to standard metrics, I have instrumented each application to gather more comprehensive data. However, it is essential to acknowledge that this comparison may not be entirely fair. Synthetic tests often utilize smaller header sizes, which do not accurately reflect real-world conditions. The disparity in header sizes can significantly influence performance outcomes. gRPC addresses this issue effectively by employing HPACK, which reduces header sizes. However, if your application is not utilizing HTTP/2, which is now prevalent across the internet, you may encounter significant header problems.

In conclusion, this analysis will provide valuable insights into the performance differences between Bun and Go, particularly in the context of real-world applications and the challenges associated with header sizes and data handling.

=> 00:05:23

Understanding the limitations of JavaScript's single-threaded nature is crucial for optimizing performance; embracing multi-threaded solutions like Go can unlock significant speed advantages.

The size of your headers in these synthetic tests is a significant factor. The size of your headers alone is rather small; they are not going to contain all the data that you would encounter in the real world. Consequently, the headers are just much smaller. gRPC solves this issue effectively. gRPC uses HPACK, which will greatly reduce your header size. However, I acknowledge that your headers are huge, and that is indeed the problem. If you are not on HTTP/2, which most of the internet is now utilizing, you are going to face huge header problems. At least if you are on HTTP/2, you won't have that header issue, or at least you won't mostly have that header issue.

In my application with Primal Mric, I aim to measure how long it takes for each application, including the difference between H and headest, as well as database metrics. I have also added CPU usage of the application itself. In these tests, I will generate enough load to find the breaking point of both applications. I deploy both applications to a production Kubernetes cluster using large instances for the applications, each equipped with two CPUs and 8 GB of memory. Since BN uses a single thread for most operations, I limit each application to one CPU, which can be viewed as 100% of a 100-millisecond interval in a cycle that repeats indefinitely, enforced by cgroups. Additionally, I allocate 256 MB of memory.

I also scale applications horizontally by deploying two instances of each application on each EC2 instance. To generate load, I use Graviton instances, which are a bit cheaper, and deploy 20 pods for each application. These pods gradually increase the number of virtual clients until both applications fail.

Any advice on how to improve either? By the way, it’s good that he’s using real machines instead of localhost. Localhost can be kind of deceiving; there are a lot of optimizations that occur during localhost-type performance tests. Therefore, I always remain very wary of anything that involves localhost, as it can take away the performance benefit by fitting the test.

That’s a really good point. One thing that is deeply unfair about all of this is that for those who don’t know anything about how JavaScript works or any of these runtimes, the runtime is the environment in which JavaScript runs. JavaScript is not the full picture; there is also all the network stuff involved. This can technically operate on many threads, but JavaScript itself must be executed on a single thread. You can aggregate your requests, and every one of the requests that come in can be parsed, operated on, and then put onto the process queue. This ensures that they are answered in order, which is beneficial.

However, the actual popping of the process queue will happen in order and on a single thread. In contrast, with Go, you don’t have to operate this way. Go can answer requests in a very multi-threaded approach with its goroutines, which is fantastic and allows for significant speedups.

Whoever said that is absolutely right; this is something I just didn’t even read. They took these factors into account, and you can really dominate your ability with Go by simply saying, "Oh, you only get one CPU." The reality is that it is extremely easy to vertically scale Node. Goroutines allow you to simply say, "Oh, I have four CPUs; I have eight CPUs—boom!" It is pretty amazing what you can achieve.

Node is not single-threaded, yes, because Node is the runtime. As I described earlier, Node is the runtime, or Bun is the runtime. JavaScript itself is executed on the JavaScript engine (V8), which is required to be single-threaded when running JavaScript. You cannot add and remove items within it on anything but a single thread. Even garbage collection is largely done off-thread, with scavenger tasks occurring off-thread. However, the actual JavaScript execution is single-threaded.

Regarding promises, they are not multi-threaded. If you think they are multi-threaded, don’t worry, Nightshade; you don’t have to be concerned. Promises are not multi-threaded; you just don’t understand how this works. What happens underneath the hood with a promise could be...

=> 00:09:40

JavaScript runs on a single thread, meaning everything executes in order; promises may seem multi-threaded, but they're not—it's all about the event loop.

The JavaScript engine is required to be single-threaded when you're running JavaScript. This means that you cannot add and remove items within it on anything but single-threaded contexts. Even garbage collection is largely done off-thread, with scavenger tasks occurring off-thread. However, the actual JavaScript execution remains single-threaded.

Some people mistakenly believe that promises are multi-threaded. If you think they are, don't worry; you just may not understand how this works. Promises themselves are not multi-threaded. What happens underneath the hood with a promise could involve multi-threading, but the promises themselves are not. There is an event loop, and every single thing gets added to this event loop, which is how these things work. The method Promise.all represents concurrency.

It's important to clarify some basic concepts. Let's pretend we have three network requests and we use Promise.all. The constructor for each promise executes in order. For example, if we consider how long the constructor takes, how long the network request takes, and how long the processing of the result takes, we can visualize this process. If you execute three of these in a row, they will appear to execute in order.

When using Promise.all, the array does not execute immediately; it has to wait for the ability to have the thread. After the constructor has executed, the network request is sent out, and then the next constructor can execute. However, it will be waiting on the network and some sort of processing at the end. When the network request comes back, it will have the ability to execute, but if it comes back later, it will still have to wait until it can construct.

JavaScript executes always synchronously. It does not have to operate differently. Anytime JavaScript is executing, it is single-threaded. There is no difference at any point in this regard. Although it is true that underneath, things can be multi-threaded, JavaScript itself remains single-threaded. Workers are a different concept; they represent a new V8 isolate. Yes, things can be processed underneath, but on the surface, they cannot be.

Now that we understand this, it's essential to read and look deeper into how these processes work. The runtime can indeed be multi-threaded. This is why comparisons with other languages can be misleading. For instance, if you were to use Go, goroutines would operate differently. Each goroutine would represent a constructor, and assuming they all take equal time, they would run in parallel with each other.

In a theoretical scenario where you had all the CPU power, you could achieve this parallel processing. I apologize for getting a bit intense earlier and for calling anyone stupid; that was not my intention. I've had to ban too many people today, and it has made me a little frustrated. Thank you for your understanding.

=> 00:13:50

Even the best tools can struggle under specific conditions; understanding their strengths and weaknesses is key to effective performance.

In the discussion about CPU performance, it is acknowledged that there is some level of complexity being hidden. However, it is assumed that if one had all the processing power in the world, they would be able to overcome these complexities. The phrase "go Funk yourself" is mentioned, indicating a casual tone, and it is noted that application is welcome as well as pool requests, which are typically merged within a single day.

The speaker apologizes for their earlier intensity, stating, "sorry for calling you stupid," and admits to being a bit riled up due to the number of users claiming to be under 13 years old, resulting in multiple bans. They express their eagerness to start the evaluation process.

The first test involves evaluating how well each application can process HTTP requests and return hardcoded values in Json format to the client. The speaker mentions that the top right graph will measure throughput, but notes that the test is unfair to Go because it is limited to one CPU. They explain that one of Go's strengths is its exceptionally simple vertical scaling output, which is compromised in this scenario.

On the left-hand side of the evaluation, latency is measured, indicating how long it takes to respond from the start. It is observed that one application takes significantly more time to process each request. The speaker emphasizes the importance of measuring latency from the client side to ensure accuracy.

Next, the discussion shifts to saturation, which indicates how full the service is. The speaker notes a trend in CPU usage, where Go uses more CPU time and is the first to experience throttling. Since two replicas for each application were deployed, average CPU usage is measured. The right-hand side of the evaluation displays memory usage, which only becomes critical when both services are overloaded.

The speaker expresses confusion regarding the implications of Go using a lot of CPU, stating, "I think this is a good thing," as it suggests efficient use of the small amount of allocated CPU. They struggle to understand why this situation isn't favorable, especially since the requests per second are about the same. They remark that something seems off, as memory usage is low while CPU throttling is not present, yet availability remains high.

In this test, the average request takes less than 1 millisecond to complete, leading to a client timeout set at 100 milliseconds. When this timeout is exceeded, a drop in the availability graph is observed. Up until 60 to 70% CPU usage, throttling is not evident. At around 23,000 requests per second, the CPU usage differences between the two applications become more pronounced. At this point, Go starts losing its advantage in latency for this type of application.

Once CPU usage hits 40%, performance begins to degrade, and latency increases by around 40 milliseconds. The speaker notes that something must be wrong, suggesting that it should be a dead giveaway since Go is a compiled language. They point out that Go could still be very fast on a single CPU, as the standard library uses a goroutine for every request rather than a worker pool, which targets a specific weakness in the standard library and forces thrashing behavior.

The speaker expresses their confusion regarding the situation, stating, "I feel somewhat confused by this." They discuss the implications of using multicol on Go, mentioning that schedulers distribute work, which must have some overhead. They argue that the scheduling mechanism is more complex on a single CPU for Go compared to Bun, which simply adds tasks to a linked list, making it an O of one operation.

Ultimately, the speaker finds it strange to impose JavaScript limits on Go and is perplexed by the requests per second metric. They observe that Go starts to fall behind Bun in terms of the number of requests it can handle, reaching 61,000 requests per second. The differences become even more noticeable as Go's latency increases to 10 milliseconds, and Kubernetes begins to show signs of strain.

=> 00:18:17

When it comes to handling high request loads, sometimes simpler is better.

One could argue that the scheduling mechanism is more difficult on a single CPU for Go than it is for Bun. If it's just an event loop, Bun is simply adding tasks to a linked list, which makes it an O(1) operation. In contrast, when you have scheduling with Go routines, the performance may not be as efficient. This could be the issue that we are observing.

It’s quite perplexing to impose JavaScript limits on Go. I am very confused by the requests per second metric, as Go starts failing behind Bun in terms of the number of requests it can handle. At 61,000 requests per second, the differences become even more noticeable. Go's latency increases to 10 milliseconds, and Kubernetes begins throttling it, which affects performance. On the other hand, Bun maintains low latency, although some requests start to drop. It’s not many, but you will notice drops in the availability graph at 69,000 requests per second. At this point, it becomes clear that Go can't handle any more requests and begins caching some of them.

Continuing with the analysis, at around 90,000 requests per second, Bun also hits its limit and gets throttled by Kubernetes. Now, let me open each graph for the full test duration. As you can see, the test took around 2 hours to complete. I am very curious about what happens as you increase CPU allowance and all that. Perhaps the only plausible explanation has to be that the scheduler is very bad when you give it exceptionally limited resources, which is actually a pretty reasonable argument.

Why have a complex piece of scheduling that is meant to vertically scale on something that has absolutely no benefit? Yet, something still feels very fishy about that. There could also be some magic Zig optimization, but this optimization should also be "magically" available in Go. There is not some sort of magic thing; Bun's HTTP implementation is written in Zig, so they are only using JSON stringify that runs by JavaScript.

Are they doing the entire thing effectively in Zig utilities? Yes, because Zig should outperform Go by a good margin, just because it can do all sorts of really impressive things, like Arena allocations. This leads to one of the problems I face when discussing small world tests. These tests are very interesting because they involve a tiny amount of JavaScript being executed, but it doesn’t look like that in the original source code.

I am curious why this thing is performing so much better. There’s this histogram they are using, doing a string check, creating new UIDs, and performing JSON operations. It’s very interesting, right? It’s only calling GET; it’s not creating any new devices, so it’s just doing a GET request. Yet, it’s still doing something significant.

If Bun is better, then you should use Bun. That’s the reality. At the end of the day, whatever is really great should be adopted. However, I don’t want to replicate the results. What is Prometheus? It’s obviously some sort of time series database.

We are writing to Prometheus directly from Go. I might have missed the Prometheus part earlier. Prometheus is indeed a time series DB. Where is the main app.js? In this file, you don’t see any time series DB, but I’m sure it does exist. I must be missing something regarding the post register from the Prom client. There we go; it’s only on a metrics call. So, there must be some sort of middleware involved, one would assume.

=> 00:22:38

When choosing between Go and Bun for your application, remember: Go shines in performance and low latency under load, while Bun may struggle with complex requests. Always test with real-world scenarios, not just simple benchmarks.

In this discussion, we are writing to Prometheus directly from Go. It seems that I may have missed the part about Prometheus, which is a time series database.

Looking at the main application file, app.js, it appears that there is no visible reference to the time series database here. However, I’m sure it exists somewhere in the code. I must be missing something related to the post register from the Prom client. Upon further inspection, I found the register, which is only present during a metrics call. This suggests that there must be some middleware involved. If Bun is that fast, then Zig must be performing exceptionally well.

Speaking of Zig, it is indeed really good, achieving around 40% CPU usage while surpassing Bun's latency. However, prior to that, its latency was significantly lower. If Zig is so fast, it raises curiosity about its performance. We can observe memory spikes in Go when it struggles to process all requests, leading to caching issues. Eventually, memory usage can reach 100%, causing Kubernetes to kill the application multiple times due to out of memory errors.

Next, we have the availability graph. I examined the code for building client-facing applications where latency is crucial. In such cases, you might want to consider Go because it performs much better up until 40% CPU usage. On the other hand, if you prioritize throughput and latency is less critical, such as in internal microservices, Bun might be the better choice.

However, I don’t believe there are any significant takeaways from this analysis. The current approach seems to be hardcoding a single device, and no endpoint has ever looked like that. Therefore, it’s unwise to make decisions based on this. A more comprehensive approach is necessary, as typically, you would be making several requests within any other request, aggregating results, and then performing operations. The use of a single JSON stringify seems inadequate.

It’s possible that this could be copium; something about this doesn’t feel quite right. Most benchmarks utilize simple tasks and algorithms to measure performance, but in reality, applications rely heavily on external libraries, and their performance depends on how well those libraries are developed. Additionally, almost all applications require some kind of persistence layer, such as a database.

In the previous Bun versus Go benchmark, I used a Postgres relational database. In this test, I replaced it with a MongoDB document database. If you're interested in how Bun performed with Postgres, you can check out that video. I instrumented both applications with Prometheus metrics to measure the duration of each function call. When the application receives a post request, it generates a UUID and stores the complete object in the database. You can find a link to the source code in the video description.

I also utilized a Prometheus histogram to measure the time it takes to insert data into MongoDB. Based on the feedback I received, I am now measuring MongoDB CPU usage along with other standard metrics. In this test, Go performs significantly better than Bun, with the latency for the client and database insert being almost half of what Bun has. Additionally, Go has much lower CPU usage compared to Bun, at around 7.5 thousand requests per second. At this point, Bun starts to degrade and drops some requests.

I still believe it would be much more interesting to see more work done on the endpoints. There needs to be more complexity; I want to see an actual representation of the problem. The last time I conducted any sort of testing, I built a game that was played entirely on the server to test the differences between Bun and Go. The results were staggeringly different. It doesn’t feel as impactful when you’re not running a more complex application, as opposed to just a simple program that performs one task. Most endpoints aren’t as simple as merely storing a device; ideally, you should be working on something more intricate than just a one-for-one database operation per request.

=> 00:27:00

Choose the programming language that makes you feel excited and productive, not just the one that's the most efficient. Happiness in coding matters more than raw performance.

I want to see an actual representation of the problem. The last time I did any sort of testing, I built a game that was played completely on the server to test the difference between bun and go. The results were staggeringly different. It doesn't really feel that much more wild when you don't run with an actual, you know, actual goodness, versus just like, "Hey, here's a simple program that does one thing." Most endpoints aren't as simple as just taking a request; hopefully, you're not writing something as simple as a CRUD endpoint that just stores a device and returns. Usually, you have a bit more going on than just a one-for-one database operation per actual request going in and out, which causes the latency to increase.

I'm not sure if I can even believe this finding: go starts to degrade at around 24,000 requests per second, reaching nearly 100% CPU usage. As you can see, when the test involves more than just returning static responses, like in the previous test, go performs much better. In real-world scenarios, you will definitely need more than just static responses. Based on my experience and the benchmarks I've run, bun performs very well with static synthetic benchmarks, but when it comes time to do real work, such as interacting with a database, bun loses many of its advantages. Even Node.js performs better in these cases.

That would be more curious than anything else, if I mean, because that's actually a real test. I think Aiden, an amazing go engineer at Twitch, even said, "If we're going to agree that bun is better than go, then we also need to agree that PostgreSQL is better than React," which is just a wild statement. Now, if Node.js actually does outperform bun, at least we're comparing a very similar environment. We're comparing two environments that have effectively the same conditions.

Now we're comparing Legacy C++ plus a bunch of ifdefs plus all this stuff plus V8 versus Zig—none of the legacy, you know, JSC. Yeah, classic Aiden base take, right? Just why is he so based? Just based as it gets. This Aiden guy sounds smart; he sounds like he's pretty good-looking too, huh?

But I think Anti-Diluvian Apocalypse, I don't know where you went, but you said something that I think is probably the best: most of us don't write code that executes with 990,000 requests per second on a single machine. You're also only running the world's tiniest amount of CPU. You're going to probably scale up and use a lot more CPU, but regardless of all that, you're going to probably do a lot more work. You will not even be close to being able to perform that amount of operations per second, either in go or in bun.

On top of that, you should probably just choose the language that makes you happier. If you can choose the language that makes you feel more productive and excited, you are likely going to be a lot happier. You just will be a lot more happy than if you choose a language just for efficiency and you hate it. Personally, I just don't find a lot of love in Rust; that's all there is to it. I’m willing to give another Rust attempt a try, where I attempt to make sweet love with Rust one more time, but I just don’t like it. It’s not fun for me.

I don’t get languages that are really difficult—languages that are really difficult to refactor or where, when anything changes, I have to do a lot of changing. I find that to be very frustrating. Zig doesn't seem to have that problem, and go does not have that problem, whereas Rust does have that problem. I just don't like that.

Now, what the [ __ ] is a box pointer? It's not that bad; a box pointer is just a pointer. That's all it is. They just use the term "box" because you have to have something that's consistently sized on the stack. Typically, whenever you're using JavaScript, they call it a boxed value as well. A box is just a pointer; that's all it is.

And what is an arc? That's an atomic reference count pointer; okay, it's a shared pointer. If you've ever used C++ and you used a shared pointer, that's all an arc is. That's it. You know, that's it. A bck and a docks? Yeah, that's right too.

=> 00:31:05

Changeability in software development is key; choose your tools wisely to optimize performance.

The discussion revolves around the problems associated with Rust and its handling of pointers. The speaker expresses their dislike for Rust's approach, particularly regarding the concept of a box pointer. They clarify that a box pointer is simply a pointer, emphasizing that the term "box" is used because it represents something that is consistently sized on the stack. They draw a parallel to JavaScript, where a boxed value is also just a pointer.

The conversation then shifts to arc, which stands for an atomic reference count pointer. The speaker explains that an arc is essentially a shared pointer, similar to the shared pointer used in C++. They mention the existence of various types in programming languages, stating, "too many types" can complicate matters. They express a preference for languages like Go or Zig, citing their changeability as a significant advantage.

The speaker suggests that one should test their specific use case before choosing Bun, especially if the goal is to improve performance. They present a graph illustrating the entire test duration but express skepticism about whether this represents a significant win for Go. They note that the database insert appears to be the bottleneck, as indicated by the graph. The speaker points out that the latency is heavily dependent on the time it takes to insert into the database, with a 20-millisecond insert raising concerns about potential issues.

They speculate that there may be an underlying problem affecting performance, suggesting that the measurements might not accurately reflect the situation. The speaker attributes the issues to the limited CPU resources allocated to the machine, resulting in excessive context switching. They describe a scenario where a connection pool to a MongoDB is spawning numerous green threads, leading to an ever-growing process queue.

The speaker highlights that using one core for any application is typically challenging, especially for JavaScript, where it is common to use n minus one cores to leave one core available for other processes. They assert that having everything run on a single, limited CPU will not yield positive results. They propose that adding more CPUs would likely enhance Bun's performance significantly. However, they argue that Go's performance would see an astronomical increase, as it can effectively utilize all available CPU resources.

The discussion concludes with the speaker advocating for the ability to run multiple Bun instances or to implement multi-threading, whether through workers or several instances. They acknowledge the complexity involved in achieving this, stating that having a singular program capable of utilizing all CPU cores is significantly easier to write and manage, regardless of the programming language in use.

=> 00:34:53

Efficiency in coding is all about leveraging the right tools; Go's simplicity and lightweight concurrency make it a powerhouse for utilizing CPU resources without the overhead of complex setups.

The discussion revolves around the performance of Go in relation to MongoDB and its ability to handle database inserts on a single machine. The speaker emphasizes that Go's performance is significantly enhanced by its capability to utilize all available CPU resources effectively. They express a desire for the ability to run multiple instances of Bun, whether through workers or several instances, to achieve multi-threaded Bun functionality. However, they acknowledge that this approach comes with its own set of challenges.

To be honest, having a singular program that runs efficiently and utilizes all CPU cores is much easier to write and maintain compared to creating a service that needs to spawn multiple instances based on the number of available cores. This complexity requires careful planning and implementation, including writing a reverse proxy to manage the instances, which diverts focus from the primary task of writing the Bun application itself. The speaker notes that this adds to the engineering hours required for development.

While they recognize that Bun may have built-in clustering features, such as shared ports, the need to consider the reverse proxy as a problem remains. They mention a specific point about client latency and database insert latency, indicating that while multiple CPUs can help, they might not significantly alleviate the issue due to the inherent limitations of the database.

The speaker elaborates that Go naturally utilizes all CPUs without requiring much thought from the developer. If additional CPUs are added, Go will efficiently use them, which contrasts with the heavier overhead associated with Bun. They point out that a Go routine is a lightweight construct, whereas a worker or a new Bun instance is much heavier, requiring substantial amounts of RAM to run several instances effectively.

Despite these challenges, the speaker expresses that they appreciate the simplicity of Go, which has allowed them to maintain a straightforward model of the world and focus on solving problems. They assume that the standard library in Go employs a go routine per request, which is crucial for performance. The discussion concludes with an acknowledgment that if a Go server were designed to use worker pools, it would likely perform better in certain scenarios, although the speaker admits they have not experimented with worker pools in Go.

Overall, the conversation highlights the trade-offs between using Go and Bun, particularly in terms of performance, complexity, and resource management.

=> 00:39:04

Don't be fooled by micro benchmarks; real-world data is where the truth lies. Test in production, not in synthetic environments.

Language has allowed me to have a really simple model of the world, which has enabled me to think critically about the problem I want to solve. I would assume that the standard library does a go routine per request; otherwise, it could lead to performance issues. If you halt it, then it's going to suck. This type of test specifically targets the server implementation of unbounded go routines versus using a worker pool in Go. If you made a Go server that utilized worker pools, then it would perform better in this case. I'm sure it would, although I’ve never played around with worker pools in Go, so I have no idea.

This test is, frankly, not very useful. However, it is interesting in the sense that smaller synthetic tests can produce wildly weird outcomes. One should never take synthetic tests at face value. Did you see the difference between these two tests? They are not that much different, yet you can observe a vastly different performance. Think about how many times, especially in larger companies, someone comes in and shows you something, claiming, "Look at how much better this is!" Many people latch onto those claims, believing they are better, despite the absence of real production traffic or workloads. They present something that favors their argument, but how many people do you think will show the first test and say, "Look, this is better"? I bet there are tweets right now that only reference this part of the test, and that’s it. People are talking about it, and it’s not even real; they are being misled for some reason.

I think the big takeaway here is that slight variations can cause vastly different outcomes in these kinds of micro-benchmark environments. Aspects such as MongoDB CPU usage, application CPU usage, memory usage, availability graphs, and CPU throttling can all play significant roles. I have other benchmarks that you might find interesting, and if you know how to improve any of these applications, please feel free to submit a pull request. Thank you for watching, and I'll see you in the next video.

I also did something I call Shooter.js—the Primagen. I created something similar and worked with various different implementations. I have a whole view and everything involved in this, and I do a lot of fun stuff with it. The differences are significant. I even did one with Rust, and someone made a pull request in Rust, showing me a different way to do it that made it just insanely faster. There are all sorts of fun ways to approach these problems that simply aren't possible in the JavaScript world compared to these other environments, where you can achieve much faster performance.

The basic testing I was conducting involved seeing how many theoretical games that involve collision detection can run concurrently on a server while maintaining good performance, meaning that frames aren't dropped. The difference between any Node.js or JavaScript implementation versus Go is about 10x different. The difference between Go and Rust is significant, but it is not different at the same level; it's just better, but not by the same margin.

It's interesting to observe how people form these kinds of benchmarks. The big takeaway is: don't trust micro-benchmarks or small world benchmarks; trust real data. Go out, measure production, and make a change. Continue to measure production—does production's 99th percentile go up or down? If it goes down, then guess what? Good things happen; you probably solved some problems and created better solutions. So, test in production. Don't rely on synthetic tests or make decisions based on really small synthetic tests; you'll likely be disappointed. Measure your production, not mine or anyone else's, as we all have different production environments.

Anyways, the name of the game is that it was pretty interesting. I feel nerd sniped, but I'm refusing to let it happen because all I want to do is finish up my simulation tester for my game servers. I just want to complete that, so I'm resisting the urge to get sidetracked. I can feel the pull of being nerd sniped right now, but I’m determined not to let it happen.