You are not logged in.
Pages: 1
I'm slicing mORMot2 on a bunch of packages, targeting the maximum division possible, at least at start. So far it worked fine, although a few lines must be changed by hand in one unit
If anyone is interested, please drop me a line
Offline
I don't know so far we haven't seen so far what @Javierus is doing.
As a raw thought: probably it might be useful in the case of having many similar packages (some plugin/module-based project for instance), which are using only a part/parts of mORMot (2.0 is separated properly, as I heard). Now, I'm putting all mORMot in one bpl, if I need so. Having an easy ability to choose specific small packages might be useful, I think. For sure they will not cross with each other in main packages. The problem of updating packages in production (unload-update-load) also might have more options for solving with such an approach.
But I'm speaking from myself. It easily can be that I completely misunderstood @Javierus idea Anyway, I always like to see new (for me) approaches and experiences, it expands the freedom of my future choices and brings more ideas.
Offline
Several microservices running on the same machine can benefit from packages or not?
I believe that the memory consumption - of the sum of all - will decrease.
But starting from a certain number of executables, because for few it will have the opposite effect.
Offline
Each MicroService should be properly stand-alone, with NO shared data nor shared code.
That is one rule of MicroServices.
So bpl are an anti-pattern for MicroServices. They make them MicroSevices.
Memory consumption reduction is a wrong idea here - mORMot samples are a few MB big. The code size is not what matters.
It had a sense in 1990s when memory was sparse, or within a plug-in system like the Delphi IDE (where it was designed for).
Otherwise, using packages will only induce problems. Premature optimization for sure.
Even within plug-in systems, saving a few hunderths KB by reusing some code is not worth it.
If the mORMot code was monolithic and almost untangible... Why not?
But in practice, mORmot code base is so much evolving that it is not yet in the state of being a general-purpose library like the libc or zlib.
What I have done on the contrary is to create a big executable which is able to be run as several services. Then it is faster to setup and update, and the OS will definitively share memory for code.
Also note that sharing code is somewhat broken in mORMot, since we patch some memory pages inline (for RTL redirection or classes vmtAutoTable) so part of the memory mapping of the executable is made private to each running process. I am speaking of a few pages made private.
Offline
Thanks @ab for all this detailed information,
So I can't imagine a good use of packages - specifically speaking for mormot.
Especially a new version where the need for updates tends to be more constant.
What I have done on the contrary is to create a big executable which is able to be run as several services. Then it is faster to setup and update, and the OS will definitively share memory for code.
Interesting. For "several", how many are you referring to approximately?
Last edited by macfly (2020-11-24 21:09:59)
Offline
I do need packages, as in no-go if it doesn't work inside one
Why? My only app is an ERP, where everything is in bpl's. It's heavily customizable via packages: the customers that need customizations, have a package,a bpl, wich integrates perfectly with the program, to the point you can't tell what is "native" and what is not. Form and datamodule inheritance allows to change standard behaviours, if that is needed. And so on...
In short: yes, packages have a place in this world. Perhaps not in the microservices world, but for now that's not my world anyway
Offline
So I can't imagine a good use of packages - specifically speaking for mormot.
I would not be so strict about packages and mORMot 'Classic' ways of using mORMot - probably, but imho it all depends on particular desires.
Sometimes I have to rewrite some parts of mORMot for complex projects. Just the last week we had to create our own TSynAutoCreateFields for supporting generic lists (VMT Autotable cache is a brilliant idea, btw thanks Arnaud!). And in the same project we have many other things rebuilt but based on similar technics and mORMot base classes and functions.
If we would imagine that someday I'll want to put those base mORMot only used stuff in packages, and ours in others - why would it be a bad idea? I'm asking not because I'm trying to prove smth - I really would like to understand the danger.
Offline
Perhaps one potential use of packages for mORMot may be to use them instead of source for the Delphi IDE.
The Delphi IDE is pretty much unstable with huge source code...
The Delphi compiler has no problem. Only the IDE just crashes or locks when working with mORMot source code.
Using packages may help circumventing the Delphi IDE bugs.
What is your feeback about it?
(note that recent Lazarus has no trouble with mORMot source code - it is now much stable than Delphi as IDE, with the only exception of the debugging experience which is still less good than Delphi)
Offline
Good point.
It will also make compilation faster.
Offline
Perhaps one potential use of packages for mORMot may be to use them instead of source for the Delphi IDE.
Besides, mORMot already has packages for Lazarus...
Offline
The Delphi compiler has no problem. Only the IDE just crashes or locks when working with mORMot source code.
Just for information - see my post from 2015 about refactoride160.bpl - https://synopse.info/forum/viewtopic.php?id=3014
Offline
@mpv
D2007: removing refactoride100.bpl does not change anything
@ab
placing a good part of mormot2 units in a single package fixes all for me. The remaining units will work no matter how you do it: in that same package, or in a bunch of other packages
Offline
@Javierus
Fair enough, in such a specific architecture.
So just put all needed mORMot units in every of your packages, and they will be fine.
It will be okay under one condition - if your application will not use two different BPLs and each of them will contain the same modules - for example, mORMot modules.
For this reason, you cannot do as you wrote.
That's why, in my code, mORMot has been moved to dedicated BPL, which is loaded when needed.
And I don't think that BPL is an anti-pattern or even better - it is very convenient solution, but you have to learn how to use it correctly.
I personally use an architecture that shares a large part of the code between the client and the service. One of such shared modules are mORMot, configuration, DAL, etc.
De-facto my exe file is a BPLi loader according to the configuration for the service.
And the configuration determines whether this application will be a server, a service or a desktop application.
BPLs have their advantages, closing reusable code in one library, faster compilation and even the possibility of incremental upgrades (just download and replace specific libraries and not one or more monolithic exe files) of the target solution.
And in Delphi, from a code usage point of view, it makes no difference whether you use BPL or not. Everything works exactly the same and does not require any additional actions or other coding, you just make uses and use the code that you need and is in a specific library - that's very convenient.
Offline
I don't understand well how you may update a "mORMot" package, which may change the methods signatures, without update the other packages using the mORMot types and definitions, i.e. the business code.
Sounds like another way of reinventing the https://en.wikipedia.org/wiki/DLL_Hell syndrome - even if packages are not dll.
A monolithic exe with all features seems safer, if you really want to reduce the disk footprint.
Note that the whole exe is not loaded into memory at startup: it is mapped into memory, and only when some part is needed, it is loaded from disk. So you could have a 100MB executable with a lot of resources or not used code which could consume only a few MB of RAM. And if the same exe is loaded several times, with several settings to change its behavior (i.e. a single exe could implement several MicroServices), the mapped RAM will be shared among the processes (minus the patched memory pages, used e.g. for the vmtTable hack or RTL rediction).
Last think to know is that packages are not the same in FPC (they are working on it, but only for part of it, and exact same feature set is not planned, because not easily feasible IIRC).
I personally use an architecture that shares a large part of the code between the client and the service. One of such shared modules are mORMot, configuration, DAL, etc.
You can share code between client and server. And, in fact, you should, and with mORMot, you need (e.g. interfaces, or mORMot core).
But sharing code is not the same as sharing binary. BPL is about sharing binary, not code.
Offline
That's why, in my code, mORMot has been moved to dedicated BPL, which is loaded when needed.
When you say you load the mORMOt package when needed, are you saying that load the mormot package dynamically, and not as runtime pakage?
Offline
When you say you load the mORMOt package when needed, are you saying that load the mormot package dynamically, and not as runtime pakage?
I'm also a heavy user of runtime packages, wich means the only job my exe has is loading the main bpl: in my case that bpl is just a framework wich will load the appropriate app bpl's
When you start the exe:
* it might have some runtime packages wich will load automatically
* it loads dynamically the main BPL
* loading the main BPL will load automatically the needed packages
* then it will decide wich packages it must load: there is no real applicacion loaded so fa
* Sort them based on package dependencies
* Load and initialize dynamically each package (wich will load more runtime packages automatically)
Guess that's what he refers to with "loaded when needed": if you load a package that uses mormot.bpl , and it was not already loaded, then will load it
Ofcourse you could go with a system that registers packaged stuff on a bpl, and automates loading it automatically when the bpl is used, but so far I've never needed it
Offline
What do you mean by "load a package"?
I guess you don't mean "downloading" a package from a central repository.
I guess you mean "link" the bpl and its dependencies from the path.
Then a single exe is even faster and safer, and will use less resources than a set of bpl.
Ensure you understand what I wrote above about exe being "mapped" into RAM on demand on modern OS.
Offline
What do you mean by "load a package"?
I guess you don't mean "downloading" a package from a central repository.
I guess you mean "link" the bpl and its dependencies from the path.
Delphi Help:
function LoadPackage(const Name: string): HMODULE; overload;
function LoadPackage(const Name: string; AValidatePackage: TValidatePackageProc): HMODULE; overload;
Then a single exe is even faster and safer, and will use less resources than a set of bpl.
Ensure you understand what I wrote above about exe being "mapped" into RAM on demand on modern OS.
Ofcourse, I know I would get more speed and less resource usage.
So, you should ensure you understand that I might have a reason to not removing the checkmark on "Build with Runtime packages"
Yes, I understand it absolutely. And I keep the checkmark because the value it gives me is much higher than the pros of the single exe
Edit: I never use packages on the path; I always install the auto-loaded packages in the exe folder
Last edited by Javierus (2020-11-26 16:01:18)
Offline
...
Ofcourse you could go with a system that registers packaged stuff on a bpl, and automates loading it automatically when the bpl is used, but so far I've never needed it
Now I understand what you mean.
I use packages in an application (which does not use mORMot).
I use both forms, at runtime (for core) and dimamically (plugins).
But the core packages are loaded by the exe, not by bpl.
To create a modular (GUI) application packages are an excellent option without a doubt.
Last edited by macfly (2020-11-26 16:31:39)
Offline
Sounds like another way of reinventing the https://en.wikipedia.org/wiki/DLL_Hell syndrome - even if packages are not dll.
Packages are nothing but DLLs. They just have a few special metadata exports that the RTL can use to correctly identify the package, but aside from this "compiler magic" related stuff they're just plain libraries which export all interface sections of the contained units.
Note that the whole exe is not loaded into memory at startup: it is mapped into memory, and only when some part is needed, it is loaded from disk.
This is wrong for multiple reasons:
- Accessing the disk is slow. And noticing that a page is missing is done using processor exceptions (page fault), thus especially loading code does not work in this way, because not only would an exception happen somewhere inside your code when the execution crosses page borders, but it would lead to the OS having to read from the backing storage before returning control to the application (which would be none the wiser regarding the exception). You can nicely notice this slowdown when your OS runs out of available physical memory and starts to swap pages from/to the pagefile or swap partition.
- Loading from the backing storage would also mean that the OS would need to do relocations again (in case of non-PIC code). But the OS does the relocation only at startup of the application.
- And last the binary on the backing storage might have been changed or the backing storage might have been removed. The former is less a problem on Windows, cause Windows keeps the handle to the file open, but on Linux that is not the case. Thus the file might have been changed which will lead to loading different data. Now imagine if the application runs with high privileges, but can be modified by a normal user...
Last think to know is that packages are not the same in FPC (they are working on it, but only for part of it, and exact same feature set is not planned, because not easily feasible IIRC).
The features of the dynamic packages are going to be fully compatible. What is going to be different is the partition of the units among the packages cause we'll orient ourselves on our own package structure. Thus for example instead of there being only a Rtl package there'll be the core Rtl, a package for the Unix units that need the C library (as Rtl.C) and then the RTL related packages from the packages directory: Rtl.ObjPas, Rtl.Unicode, Rtl.Generics, Rtl.Console and Rtl.Extra. This simplifies the maintainability for us.
A further difference is that the RTL will do more checks at runtime to decide whether a package is indeed compatible. Many of the instabilities I've witnessed in Delphi (back when I still used it at my old work location) was due to missing consistency checks.
I personally use an architecture that shares a large part of the code between the client and the service. One of such shared modules are mORMot, configuration, DAL, etc.
You can share code between client and server. And, in fact, you should, and with mORMot, you need (e.g. interfaces, or mORMot core).
But sharing code is not the same as sharing binary. BPL is about sharing binary, not code.
The difference is that dynamic packages allow you to share binary code instead of source code. Though it comes with the price that the packages can't be smartlinked as good as the static application can (all exported code as well as all code referenced by such code needs to be included in a package) and the sum of the size of packages and application can be larger than the size of the static application alone (thus it only really pays off if you really share the packages between applications or if you need a plugin system).
Here is an example for the chmls utility provided by FPC when compiled with packages for Win64 (sizes are in Byte):
Without packages
615936 chmls.exe
With packages
85131 chmls.exe
1419896 chm.dll
1187518 fcl.base.dll
568559 fcl.res.dll
1602915 fcl.xml.dll
8347 rtl.c.dll
89978 rtl.console.dll
3647275 rtl.dll
345340 rtl.extra.dll
459357 rtl.generics.dll
601446 rtl.objpas.dll
Edit: one thing I forgot: as long as a code change happens only in the implementation part of a unit (and the changed code isn't marked with inline) then a package library can indeed be replaced without rebuilding the application or other packages. If the interface section however changes then the application needs to be recompiled.
Last edited by PascalDragon (2020-12-02 21:41:12)
Free Pascal Compiler Core developer
Offline
@PascalDragon
Thanks for the detailed feedback.
The benefit of packages is indeed to be balanced.
It is good news that packages for FPC are on their way!
Great!
I am not so sure what you meant by "disk is slow" today - my laptop PCI4.0 nvme reaches 3GB/s with no problem, and the few MB of executable image is very likely already in disk ram cache.
From scratch, loading with packages will make more disk accesses, since several files need to be linked.
So for a single-shot exe, it will be slower to use packages.
Running a 600KB of chmls.exe will be faster and use less resources than using the 80KB one with all those dependency packages.
But if chmls.exe is called from the Lazarus IDE, which already did load most of the packages, then it may have a benefit. But I am not sure it would be measurable on a wall clock.
If an executable is more than a few MB, then it is because it has debugging information or resources involved, which are not mapped into memory until they are needed. Only code/got sections need relocation process.
On Windows, relocation are done at page level, not at startup. I was referring to this feature in my post - you seem to state that relocation is always done at startup which is not true on Windows.
On Linux/ELF it is done at startup - but only 20ms for the huge Chrome executable.
Nice reference: https://chromium.googlesource.com/chrom … cations.md
Note that with mORMot, on x86_64 we try to focus on using RIP instead of globals, so less relocation is involved here.
My point was that for an executable of a few MB - typical for mORMot servers with a lot of features, but including all its needs, e.g. the statically linked SQLite3 engine, packages won't help.
If you have a plug-in architecture, or one executable launching other sub-executable (as an IDE), then packages may have their advantages.
For server-side process, I still don't see many usecases.
In 2020, we have to compare memory and disk use with a .Net or Node.js app, to be fair.
And here, FPC is just amazing. When I launch Lazarus, and compare with VSCode, there is no mistake possible.
What I don't understand is how the Delphi IDE could be so bloated in comparison to Lazarus. Just the startup time is aweful.
Offline
I am not so sure what you meant by "disk is slow" today - my laptop PCI4.0 nvme reaches 3GB/s with no problem, and the few MB of executable image is very likely already in disk ram cache.
NVMe's might have a high bandwidth (though DDR4 has around 20GB/s), but compared to RAM the latency is very high and for executables (or libraries) we're talking page based accesses here that can essentially be considered as random accesses where NVMe are still terrible compared to RAM (here and here). And the cache only works as long as it isn't flushed which might happen if many filesystem operations are occurring on the system. Or imagine that your application runs for quite some time and an up to now not yet used path needs to be taken.
From scratch, loading with packages will make more disk accesses, since several files need to be linked.
So for a single-shot exe, it will be slower to use packages.
Running a 600KB of chmls.exe will be faster and use less resources than using the 80KB one with all those dependency packages.
But if chmls.exe is called from the Lazarus IDE, which already did load most of the packages, then it may have a benefit. But I am not sure it would be measurable on a wall clock.
The linked Chromium article has an article about their zygote process which is essentially a "cache" process that leads to loading the bulk of libraries once and then forking required processes from there leading to less startup time. So while it might not be measurable on a wall clock it is measurable.
If an executable is more than a few MB, then it is because it has debugging information or resources involved, which are not mapped into memory until they are needed. Only code/got sections need relocation process.
It's not only debugging information. The executable of our main application at work has around 50 MB and the majority of it are libraries and applications, cause it's essentially a self contained OS inside a User Mode Virtual Machine.
On Windows, relocation are done at page level, not at startup. I was referring to this feature in my post - you seem to state that relocation is always done at startup which is not true on Windows.
Gotta admit that I didn't know that Windows does it indeed on a by-demand system. Then again it probably partially explains why they keep the executable open...
Note that with mORMot, on x86_64 we try to focus on using RIP instead of globals, so less relocation is involved here.
Yes, less relocations are always good.
In 2020, we have to compare memory and disk use with a .Net or Node.js app, to be fair.
And here, FPC is just amazing. When I launch Lazarus, and compare with VSCode, there is no mistake possible.
What I don't understand is how the Delphi IDE could be so bloated in comparison to Lazarus. Just the startup time is aweful.
Yes, we're definitely spoiled here. And for Delphi a part might be the .Net framework they still use for parts of the IDE and also the lookup and loading of packages. We'll have to see how Lazarus will fare here once we're far enough...
Free Pascal Compiler Core developer
Offline
Pages: 1