Have an amazing solution built in RAD Studio? Let us know. Looking for discounts? Visit our Special Offers page!
DelphiNewsResearch

What Happens When Your Compiler Jumps 12 Years of LLVM Evolution?

One of the nice things about a compiler upgrade is that your application code doesn’t have to change. You simply rebuild the same codebase and see what the new toolchain can deliver.

Delphi 13.2 makes that comparison especially interesting on Linux. Delphi 13.1 (and previous versions) relied on LLVM 3.3, whereas Delphi 13.2 jumps over a decade of compiler evolution to LLVM 20.1.

I already had three WebBroker deployments from some internal testing, so I thought: What if I built each one with both compiler versions and put them through identical load tests? After doing it, I’ll tell you something: the results were not subtle. On the CPU-heavy workload, throughput increased by 254% to 390%, while single-request latency dropped by 71% to 78%.

But as it always happens, there is an important qualification behind those percentages, and it starts with the first test I ran.

The First Result So Easy It Was Almost Boring

I began with a thin JSON endpoint that I already have available in these test projects. It received a request, constructed a dummy response using a TJSONObject, and sent it back. Because there wasn’t much custom code for the compiler to optimize, the gains were modest, but still, Apache throughput went up by about 12%, while the standalone server gained roughly 9%.

Most of the request time was spent where you’d expect: framework plumbing, socket handling, and network I/O. Nevertheless, that baseline was useful. It set a realistic expectation that WebBroker wasn’t suddenly going to become four times faster across the board just by recompiling. But it already showed something promising: without touching a single line of code, it was already measurably faster.

Giving the Compiler Room to Work

To give the new toolchain actual work to do, I added a small CPU workload to every request. It fills an array of 8,192 integers, sums it up, and in my tests, repeats that process 400 times.

It’s true that in typical backend services, the database is usually the primary bottleneck. However, plenty of services still do substantial work in memory once the data arrives. For example:

  • Calculating totals or applying rules across dataset rows
  • Grouping, sorting, or validating incoming data payloads
  • Scanning audio/image buffers or processing telemetry samples
  • Populating in-memory caches and evaluating mathematical arrays

Code heavy on string allocations or unpredictable branching will behave differently than a tight loop, but math-heavy code generation or register allocation is where a modern compiler shines.

After thinking about different options, the method I decided to add to each request looked like this:

function BusyWork(Passes: Integer): Int64;
var
  I, R: Integer;
  A: TArray<Integer>;
  Sum: Int64;
begin
  if Passes <= 0 then
    Exit(0);

  SetLength(A, 8192);
  Sum := 0;
  for R := 1 to Passes do
  begin
    for I := 0 to 8191 do
      A[I] := I * 17 + (R and 255);
      
    for I := 0 to 8191 do
      Sum := Sum + A[I];
  end;
  Result := Sum;
end;

Then the Gap Widened – The New LLVM Optimizations in RAD Studio 13.2 Were Incredible!

With that CPU loop in place, the performance difference between 13.1 and 13.2 became huge. LLVM 20 is significantly smarter at loop vectorization, register allocation, and cutting out unneeded memory operations compared to LLVM 3.3.

I ran the comparison across an Apache WebBroker module, FastCGI behind nginx, and a standalone WebBroker server using Indy. The table below focuses exclusively on concurrency levels where both builds finished 100% of their requests cleanly.

StackClean Concurrency PointThroughput IncreaseLatency Reduction (1 concurrency)
Apache ModuleConcurrency 200+390%-78%
FastCGI + nginxConcurrency 200+254%-77%
StandaloneConcurrency 25+297%-71%

Apache gave the cleanest outcome. Across all test points from concurrency 1 to 200, every request succeeded on both versions and averaged a 371% throughput increase when using 13.2. FastCGI improved sharply as well, hitting its peak early before leveling off near its configured worker process ceiling.

Standalone carried more HTTP and Indy overhead per request, so its single-request gains were slightly smaller, but latency still dropped by 71% while throughput at concurrency 25 grew by 297%.

How I Set Up WebBroker and the Hardware Configuration I Used

I kept the test environment intentionally modest:

  • Hardware: Proxmox LXC: i5-13500 4 cores, 4 GB DDR5 RAM
  • OS: Ubuntu – Kernel 7.0.14
  • Traffic Driver: Bombardier running on a Windows machine over local LAN
  • Test Load: 10,000 requests per test endpoint across a concurrency ladder of 1, 5, 10, 25, and 50 to 500 (in steps of 50). Three complete passes were run with warmups and 30-second cooldowns.

One detail to keep in mind is that the 13.1 binaries ran on RTL 37.1 while 13.2 used RTL 37.2. Because the runtime libraries have been updated alongside the compiler, we can’t attribute 100% of these gains strictly to LLVM. Some of the improvement comes naturally from ongoing optimizations inside the Delphi RTL itself.

What Happens When Things Break

High concurrency load also exposed a noticeable difference in stability.

When pushing the standalone server past concurrency 50 on this modest VM, both builds eventually got overwhelmed and started returning 5xx errors. But the failure modes weren’t the same. Under heavy overload, Delphi 13.1 was only succeeding on 20% to 28% of its requests, whereas Delphi 13.2 managed to keep success rates between 83% and 99%. Apache on 13.1 started dropping a few connections around concurrency 250, while 13.2 handled every single request. 

Note: Failed requests don’t count toward throughput, so none of those broken runs were included in the table above.

How To Try the LLVM Benchmarks Yourself

If you want to run these tests in your own environment, I put together a simplified version of the project files in a GitHub repo.

The repository includes:

  • linux-work-bench: A console timer for the CPU routine that strips out HTTP overhead entirely to measure raw execution speed.
  • webbroker-standalone: A Standalone WebBroker server with an included PowerShell script to automate the Bombardier test runs.

Build both projects in Release mode for Linux64 using Delphi 13.1 and 13.2 through PAServer. The code automatically prints (or returns) a checksum so you can verify both builds are producing identical output before comparing timings.

On this test env, moving to 13.2 shaved over 70% off request latency for CPU-heavy tasks without a single line of code changing. If you’re running Delphi services on Linux, upgrading to 13.2 is probably the fastest performance win you’ll get all year.

One Last Thing: What About rpmalloc?

It’s probably helpful if I mention that RAD Studio Delphi 13.2 also introduced optional support for rpmalloc on Linux and Windows on Arm: a memory manager specifically tuned for heavy multithreaded allocations.

Since our method BusyWork allocates array memory on every request, my intuition says that rpmalloc could potentially push these numbers even higher under load, although it comes with higher memory usage and no built-in leak reporting.

For this test, I decided to stick to the default memory manager here to keep a clean baseline, but I’m considering a follow-up test.

Is an rpmalloc benchmark something you’d like to see next? Let me know in the comments below if you’re interested!

Kai 1.1 Now Available! Kai 1.1 Now Available! What's Coming in RAD Studio 13.2 Florence

Reduce development time and get to market faster with RAD Studio, Delphi, or C++Builder.
Design. Code. Compile. Deploy.

Start Free Trial   Upgrade Today

   Free Delphi Community Edition   Free C++Builder Community Edition

About author

Pre-sales consultant engineer at Embarcadero inc.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

IN THE ARTICLES