An old article, but still accurate on most of its content. Performance is not an easy task, unless you follow some simples rules... And know a little bit what is happening behind the scene.
An old article, but still accurate on most of its content. Performance is not an easy task, unless you follow some simples rules... And know a little bit what is happening behind the scene.
http://blog.synopse.info/post/2011/05/20/How-to-write-fast-multi-thread-Delphi-applications
http://blog.synopse.info/post/2011/05/20/How-to-write-fast-multi-thread-Delphi-applications
/sub
ReplyDeleteA. Bouchez I loved that :D
ReplyDelete/sub
ReplyDeleteThat's a great article! Thank you!
ReplyDelete+Do not use anonymous functions in multithreaded code unless your memory manager is ScaleMM. Edit. You can use anonymous functions inside multithreaded code, but you'd better avoid using functions taking reference to procedure, reference to function as parameters inside multithreaded code.
ReplyDeleteAnd yes, FastMM is a usual suspect if you do not get 99% percent CPU load in Parallel For.
ReplyDeletePersonally I got into this situation several times and every time FastMM was the bottle neck. Actually the first time it happened, it was a big mystery for me. 'What the hell, why the quad-core CPU is loaded only 50%?', I was wondering. And I've spent a lot of time trying to find the reason. And it turned out, you can not simply allocate memory inside multithreaded code using getmem: you can not create objects, can not use dynamic arrays, strings. When GetMem is called inside a thread, it creates a Lock, and all the other threads are not able to allocate memory until the first thread finishes doing it.
Another big surprise were the reference to procedure, reference to function. It turned out that when you call a function that calls another function that takes reference to procedure as a parameter, than automatically an object is created by the compiler. And creating an object means calling GetMem, and calling GetMem means a huge Lock.
We tried to switch to ScaleMM, but it turned to be not stable enough: some random exceptions started to occur, so we swtiched back to FastMM.
So what do we do?
1. We try to avoid creating objects, dynamic arrays and strings inside multithreaded code.
2. We use our own memory manager to dynamically allocate memory for records. It is called TPageStorage. It allocates memory in pages and uses VirtualAlloc instead of GetMem. Here is the link to my blog post about it:
https://vitaliburkov.wordpress.com/2012/04/14/efficient-memory-management-with-delphi/
And if you want you can also check out my Parallel For implementation:
https://vitaliburkov.wordpress.com/2011/10/15/parallel-programming-in-delphi/
Sorry for self-promoting.
Don't use anonymous methods at all in perf critical code
ReplyDeleteYes, great article! I'm sure I've already linked to it, a month out so ago :)
ReplyDeleteAn old article I studied before. Some of the things they suggest should be explained, because they don't make any sense to me. For example: "Don't use indexed access to string characters, but rely on some optimized functions like PosEx() for instance;" -- huh? I am sure in some specific case this made sense, but there is no information there that makes that understandable. And this: "Always use const for string or dynamic array parameters like in MyFunc(const aString: String) to avoid allocating a temporary string per each call;" -- what? There is no "temporary string allocation" when putting a string as a parameter. I think I know what they mean...
ReplyDeleteI just realized this is your article, heh! Can you explain what you meant in those two places?
ReplyDeleteJavier Hernández The list purports to be a general rule list, but many of the items seem like they can only possibly make sense in the context of one particular project. Indexing any kind of Delphi string is not "worse" that using PosEx. It just doesn't make sense -- PosEx is for finding the position of something in the string, and its result is an index! smh
ReplyDeleteBrandon Staggs PosEx is an optimized FastCode version, which performs better than a naive "for" loop.
ReplyDeleteJavier Hernández you have a wrong perception of utf8. BOTH formats are variable length encoding. See http://www.utf8everywhere.org
ReplyDeleteJavier Hernández difference of var or const is at calling, not within the function.
ReplyDeleteBrandon Staggs this is a classic https://www.delphitools.info/2010/07/28/all-hail-the-const-parameters
ReplyDelete/sub
ReplyDeleteA. Bouchez At the call site, there's no difference between const and var parameters for managed types. In both cases, a reference to the variable is passed. No new variable is created, so no extra references need to be claimed.
ReplyDeleteFor const and value parameters, again there is no difference at the call site, but there is a difference at the callee. A copy of the variable is made, and that requires a try/finally block, reference counting etc.
David Heffernan I meant the "caller", for sure. See Eric Grange blog link I quoted above.
ReplyDeleteA. Bouchez Caller is wrong too.
ReplyDeleteDavid Heffernan Whatever, the link shows the stuff I'm talking about... ;)
ReplyDeleteA. Bouchez You said:
ReplyDeletedifference of var or const is at calling, not within the function.
For const, var or value, with a managed type, the calling code, the code on the outside of the function, is identical. Any differences are within the function.
The article implies that passing a string without const creates a copy of the string. It never does. Strings are COW; passing it only adds a reference. I have not looked at whether or not compiler optimization omits the reference bump if the string is not modified in the function though. At any rate, there appear to be a number of technical errors, or at least a lot of things that are not clear, in the article. Again with the PosEx: the article makes a blanket statement about not using "index" on strings that just doesn't make any sense linguistically. If you are comparing string search methods that should be made more clear.
ReplyDeleteBrandon Staggs
ReplyDeleteI have not looked at whether or not compiler optimization omits the reference bump if the string is not modified in the function though.
There is no such optimisation, which is a shame.
Javier Hernández Of course, but it would be nice if the compiler omitted the reference count on strings if there is no modification of the string in the function.
ReplyDelete+Javier for a string [ref] is pointless. Strings are always passed by reference.
ReplyDeleteBrandon Staggs You are right about the fact in the article "allocating a temporary string per each call" is misleading. But in fact, it MAY allocate a string, if the string is a constant, AFAIR. If the string is a variable, or a constant assigned to a local variable, it would use reference counting (calling UStrLAsg). But if the string value is a constant, and assigned outside the local scope (e.g. to a global object property), it would call UStrAsg RTL function, which allocates a temporary in-memory string. It is how it used to work in Delphi: since the constant may come from a package, and the initial memory buffer of a string constant may be unloaded from memory at any time, a copy of the constant string is allocated on heap.
ReplyDeleteIn all cases, not using "const" or "var" do have a (slight) performance cost, and as Eric states in its article, it is always better to identify constant parameters as such. I like to make the implicit as explicit as possible.
A. Bouchez _I like to make the implicit as explicit as possible._ Completely agree with that!
ReplyDelete