2022-11-26

Modern Pascal is Still in the Race

A recent poll on the Lazarus/FPC forum highlighted a fact: pascal coders are older than most coders. Usually, at our age, we should be managers, not developers. But we like coding in pascal. It is still fun after decades!
But does it mean that you should not use pascal for any new project? Are the language/compilers/libraries outdated?
In the company I currently work for, we have young coders, just out-of-school or still-in-school, which joined the team and write great code!

And a recent thread in this very same forum was about comparing languages to implement a REST server, in C#, Go, Scala, TypeScript, Elixir and Rust.
Several pascal versions are about to be contributed, one in which mORMot shines.

The Challenge and the Algorithms

The original challenge is available at transit-lang-cmp with the original source code, of all those fancy languages and libraries.

In practice, the goal of this test program is to load two big CSVs into memory (80MB + 2MB), then serve over HTTP some JSON generated by route identifiers, joining both CSVs.
The resulting JSON could be of 30KB up to 2MB. And all data is generated on the fly from the CSV in memory.

To be fair, a regular/business coder would have used a database for this. Not silly memory structures. And asked for money to setup a huge set of cloud machines with load balancing. :-)

Reference Implementations in Today Languages

The "modern" / "school" approach, as implemented in the reference project in Go/Rust/C#/... is using two lists for the CSVs data, then two maps/dictionaries between route ID and lists indexes.

  • The Golang version has a good expressiveness, and is nice to read, even if you don't know the language.
  • The C# version is also readable, but making a webserver is still confusing because it is not built from code, but from config files.
  • Elixir is a bit over-complicated to my taste.
  • Scala and TypeScript/Deno versions, are fine to read, but really slow. You may better use a database instead.
  • Just for fun, check the Rust version - do you think Rust is good for big maintainable projects with junior developers?

There was a first attempt to write a FPC version of it, by Leledumbo.
His Source Code repository is a nice pascal conversion of above code. But performance was disappointing. Especially because the standard JSON library can not work directly with high level structures like collections or arrays.

So is Pascal out of the race?
Let's call the mORMot to the rescue!

Following the mORMot Way

For the mORMot version in FPC, I used another approach, with two diverse algorithms:

  • I ensured the lists were sorted in memory, then made a O(log(n)) binary lookup in it;
  • All stored strings were "interned", i.e. the same text was sharing a single string instance, and FPC reference counting did its magic.

There is no low-level tricks like generating the JSON by hand or using complex data structures - data structures are still are high-level, with readable field names and such. The logic and the intent are clearly readable.
We just leveraged the pascal language, and mORMot features. For instance, string interning is part of the framework, if needed.

Please check the source code in our repository.

As a result:

  • Code is still readable, short and efficient (most of the process is done by mORMot, i.e. CSV, searching, JSON);
  • It uses much less memory - 10 times less memory than Go when holding the data, 5 times less memory than Go when serving the data;
  • Performance is as fast as Go, and its very tuned/optimized compiler and RTL.

Algorithms Matters

Main idea was to let the algorithms match the input data and the expected resultset.
As programmers do when programming games. Not as coders do when pissing out business software. ;-)

  • The source code is still pretty readable, thanks to using mORMot efficient TDynArray to map the dynamic array storage, and its CSV and JSON abilities.
  • I guess source is still understandable for out-of-school programmers - much more readable than Rust for instance.

To by fair, I used typed pointers in TScheduler.BuildTripResponse but it is not so hard getting their purpose, and FPC compiles this function into very efficient assembly. I could have used regular dynamic array access with indexes, it would have been slightly slower, but not really easier to follow, nor safer (if we compile with no range checking).

Worth noting that we did not make any specific tuning, like pre-allocating the results with constants, as other frameworks did. We just specified the data, then let mORMot play with it - that's all.
The mORMot RTTI level matches what we expect for modern frameworks: not only some classes to store JSON, but convenient serialization/unserialization using structures like class or record.
Using modern Pascal dynamic arrays and records to define the data structures let the compiler leverage the memory for us, with no need to write any try..finally..Free blocks, and use interfaces. "Manual memory management" with Pascal is not mandatory and can easily be bypassed. Only for the WebServer, we have a Free, which is expected to close it.

Give Me Some Numbers

Here are a performance comparison with Go (FPC on the left, Go on the right):

parsed 1790905 stop times in 968.43ms                         | parsed 1790905 stop times in 3.245251432s
parsed 71091 trips in 39.54ms                                 | parsed 71091 trips in 85.747852ms

running (0m33.4s), 00/50 VUs, 348 complete and 0 interrupted  | running (0m32.3s), 00/50 VUs, 320 complete and 0 interrupted
default ✓ [======================================] 50 VUs  30   default ✓ [======================================] 50 VUs  30

     data_received..................: 31 GB  933 MB/s         |      data_received..................: 31 GB  971 MB/s
     data_sent......................: 3.2 MB 97 kB/s          |      data_sent......................: 3.0 MB 92 kB/s
     http_req_blocked...............: avg=9µs     min=1.09µs  |      http_req_blocked...............: avg=6.77µs  min=1.09µs
     http_req_connecting............: avg=2.95µs  min=0s      |      http_req_connecting............: avg=1.73µs  min=0s     
     http_req_duration..............: avg=47.59ms min=97.28µs |      http_req_duration..............: avg=49.02ms min=123.81µ
       { expected_response:true }...: avg=47.59ms min=97.28µs |        { expected_response:true }...: avg=49.02ms min=123.81µ
     http_req_failed................: 0.00%  ✓ 0           ✗  |      http_req_failed................: 0.00%  ✓ 0          ✗ 3
     http_req_receiving.............: avg=9.66ms  min=15.35µs |      http_req_receiving.............: avg=5.92ms  min=14.76µs
     http_req_sending...............: avg=87.24µs min=5.2µs   |      http_req_sending...............: avg=70.71µs min=5.2µs 
     http_req_tls_handshaking.......: avg=0s      min=0s      |      http_req_tls_handshaking.......: avg=0s      min=0s     
     http_req_waiting...............: avg=37.83ms min=54.74µs |      http_req_waiting...............: avg=43.02ms min=91.84µs
     http_reqs......................: 34452  1032.205528/s    |      http_reqs......................: 31680  981.949476/s
     iteration_duration.............: avg=4.72s   min=3.54s   |      iteration_duration.............: avg=4.86s   min=2.19s 
     iterations.....................: 348    10.426318/s      |      iterations.....................: 320    9.918682/s
     vus............................: 30     min=30        ma |      vus............................: 15     min=15       max
     vus_max........................: 50     min=50        ma |      vus_max........................: 50     min=50       max

So CSV loading was much faster, then the HTTP server performance was almost the same.

No Alzheimer

Here are some numbers about memory consumption:

Upon finished loading the CSV, mORMot only eats 80MB, heck so little. Sounds a bit magical. But during load test, it fluctuates between 250-350MB, upon which it returns to 80MB at the end. The Go version eats 925MB upon finished loading the CSV. During load test, it tops at 1.5GB, returning to 925MB afterwards.

Nice to read. :)

Pascal has a Modern and Capable Ecosystem

This article was not only about Pascal, but about algorithms and libraries.
The challenge was initially about comparing them. Not only as unrealistic micro-benchmarks, or "computer language benchmark games", but as data processing abilities on a real usecase.

And... Pascal is still in the race for sure!
Not only for "old" people like me - I just got 50 years old. ;-)

The more we spread such kind of information, the less people would make jokes about pascal programmers.
Delphi and FPC are as old as Java, so it is time to get the big picture, not following marketing trends.

2022-08-12

New Client for MongoDB 5.1/6 Support

Starting with its version 5.1, MongoDB disabled the legacy protocol used for communication since its beginning.
As a consequence, our mORMot client was not able to communicate any more with the latest versions of MongoDB instances.

Last week, we made a deep rewrite of mormot.db.nosql.mongodb.pas, which changed the default protocol to use the new layout on the wire. Now messages use regular MongoDB Database Commands, with automated compression if needed.

No change is needed in your end-user MongoDB or ORM/ODM code. The upgrade is as simple as update your mORMot 2 source, then recompile.

Continue reading

2022-07-09

Native TLS Support for mORMot 2 REST or WebSockets Servers

Since the beginning, we delegated the TLS encryption support to a reverse proxy server, mainly Nginx. Under Windows, you could setup the http.sys HTTPS layer as usual, as a native - even a bit complicated - solution.
Nginx has several advantages, the first being a proven and efficient technology, with plenty of documentation and configuration tips. It interfaces nicely with Let's Encrypt, and is very good for any regular website, using static content and PHP. This very blog and the Synopse web site is hosted via Ngnix on a small Linux server.

But in mORMot 2, we introduced a new set of asynchronous web server classes. So stability and performance are not a problem any more. Some benchmarks even consider this server to be faster than nginx (the stability issue mentioned in this post has been fixed in-between).
We just introduced TLS support of our socket-based servers, both the blocking and asynchronous classes. It would use OpenSSL if available, or the SChannel API layer of Windows. Serving HTTPS or WSS with a self-signed certificate is just a matter of a single parameter now, and performance seems pretty good, especially with OpenSSL.

Continue reading

2022-05-21

New Async HTTP/WebSocket Server on mORMot 2

The HTTP server is one main part of any SOA/REST service, by design.
It is the main entry point of all incoming requests. So it should better be stable and efficient. And should be able to scale in the future, if needed.

There have always been several HTTP servers in mORMot. You can use the HTTP server class you need.
In mORMot 2, we added two new server classes, one for publishing over HTTP, another able to upgrade to WebSockets. The main difference is that they are fully event-driven, so their thread pool is able to scale with thousands of concurrent connections, with a fixed number of threads. They are a response to the limitations of our previous socket server.

Continue reading

2022-02-15

mORMot 2 ORM Performance

The official release of mORMot 2 is around the edge. It may be the occasion to show some data persistence performance numbers, in respect to mORMot 1.

For the version 2 of our framework, its ORM feature has been enhanced and tuned in several aspects: REST routing optimization, ORM/JSON serialization, and in-memory and SQL engines tuning. Numbers are talking. You could compare with any other solution, and compile and run the tests by yourself for both framework, and see how it goes on your own computer or server.
In a nutshell, we almost reach 1 million inserts per second on SQLite3, and are above the million inserts in our in-memory engine. Reading speed is 1.2 million and 1.7 million respectively. From the object to the storage, and back. And forcing AES-CTR encryption on disk almost don't change anything. Now we are talking. ;)

Continue reading

2022-01-22

Three Locks To Rule Them All

To ensure thread-safety, especially on server side, we usually protect code with critical sections, or locks. In recent Delphi revisions, we have the TMonitor feature, but I would rather trust the OS for locks, which are implemented using Windows Critical Sections, or POSIX futex/mutex.

But all locks are not born equal. Most of the time, the overhead of a Critical Section WinAPI or the pthread library is not needed.
So, in mORMot 2, we introduced several native locks in addition to those OS locks, with multi-read/single-write abilities, or re-entrancy.

Continue reading

2021-12-19

mORMot 2 Generics and Collections

Generics are a clever way of writing some code once, then reuse it for several types.
They are like templates, or compiler-time shortcuts for type definitions.

In the last weeks, we added a new mormot.core.collections.pas unit, which features:

  • JSON-aware IList<> List Storage;
  • JSON-aware IKeyValue<> Dictionary Storage.

In respect to Delphi or FPC RTL generics.collections, this unit uses interfaces as variable holders, and leverage them to reduce the generated code as much as possible, as the Spring4D 2.0 framework does, but for both Delphi and FPC. It publishes TDynArray and TSynDictionary high-level features like indexing, sorting, JSON/binary serialization or thread safety as Generics strong typing.

Resulting performance is great, especially for its enumerators, and your resulting executable size won't blow up as with the regular RTL unit.

Continue reading

2021-11-16

EKON 25 Slides

EKON 25 at Düsseldorf was a great conference (konference?).

At last, a physical gathering of Delphi developers, mostly from Germany, but also from Europe - and even some from USA! No more virtual meetings, which may trigger the well known 'Abstract Error' on modern pascal coders.
There were some happy FPC users too - as I am now. :)

I have published the slides of my conferences, mostly about mORMot 2.
By the way, I wish we would be able to release officially mORMot 2 in December, before Christmas. I think it starts to be stabilized and already known to be used on production. We expect no more breaking change in the next weeks.

Continue reading

- page 1 of 49