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

Comments

  1. +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.

    ReplyDelete
  2. And yes, FastMM is a usual suspect if you do not get 99% percent CPU load in Parallel For.

    Personally 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.

    ReplyDelete
  3. Don't use anonymous methods at all in perf critical code

    ReplyDelete
  4. Yes, great article! I'm sure I've already linked to it, a month out so ago :)

    ReplyDelete
  5. An 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...

    ReplyDelete
  6. I just realized this is your article, heh! Can you explain what you meant in those two places?

    ReplyDelete
  7. Javier 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

    ReplyDelete
  8. Brandon Staggs PosEx is an optimized FastCode version, which performs better than a naive "for" loop.

    ReplyDelete
  9. Javier Hernández you have a wrong perception of utf8. BOTH formats are variable length encoding. See http://www.utf8everywhere.org

    ReplyDelete
  10. Javier Hernández difference of var or const is at calling, not within the function.

    ReplyDelete
  11. A. 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.

    For 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.

    ReplyDelete
  12. David Heffernan I meant the "caller", for sure. See Eric Grange blog link I quoted above.

    ReplyDelete
  13. David Heffernan Whatever, the link shows the stuff I'm talking about... ;)

    ReplyDelete
  14. A. Bouchez You said:

    difference 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.

    ReplyDelete
  15. 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.

    ReplyDelete
  16. Brandon Staggs 

    I 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.

    ReplyDelete
  17. 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
  18. +Javier for a string [ref] is pointless. Strings are always passed by reference.

    ReplyDelete
  19. Brandon 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.
    In 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.

    ReplyDelete
  20. A. Bouchez _I  like to make the implicit as explicit as possible._ Completely agree with that!

    ReplyDelete

Post a Comment