- Always use
stringor dynamic array parameters like in
MyFunc(const aString: String)to avoid allocating a temporary string per each call;
- Avoid using
s := s+'Blabla'+IntToStr(i)) , but rely on a buffered writing such as
TStringBuilderavailable in latest versions of Delphi;
TStringBuilderis not perfect either: for instance, it will create a lot of temporary strings for appending some numerical data, and will use the awfully slow
SysUtils.IntToStr()function when you add some integer value - I had to rewrite a lot of low-level functions to avoid most string allocation in our
TTextWriterclass as defined in SynCommons.pas unit;
- Don't abuse on critical sections, let them be as small as possible, but
rely on some atomic modifiers if you need some concurrent access - see e.g.
InterlockedIncrement / InterlockedExchangeAdd;
InterlockedExchange(from SysUtils.pas) is a good way of updating a buffer or a shared object. You create an updated version of of some content in your thread, then you exchange a shared pointer to the data (e.g. a
TObjectinstance) in one low-level CPU operation. It will notify the change to the other threads, with very good multi-thread scaling. You'll have to take care of the data integrity and memory release of the old item, but it works very well in practice.
- Don't share data between threads, but rather make your own private copy or rely on some read-only buffers (the RCU pattern is the better for scaling);
- Don't use indexed access to
stringcharacters, but rely on some optimized functions like
- Don't mix
AnsiString/UnicodeStringkind of variables/functions, and check the generated asm code via Alt-F2 to track any hidden unwanted conversion (e.g.
- Rather use
varparameters in a
procedureinstead of a
string(a function returning a string will add an
UStrAsg/LStrAsgcall which has a LOCK which will flush all CPU cores);
- If you can, for your data or text parsing, use pointers and some static stack-allocated buffers instead of temporary strings or dynamic arrays;
- Don't create a
TMemoryStreameach time you need one, but rely on a private instance in your class, already sized in enough memory, in which you will write data using
Positionto retrieve the end of data and not changing its
Sizeproperty (which will be the memory block allocated by the MM);
- Limit the number of
classinstances you create: try to reuse the same instance, and if you can, use some
record/object pointerson already allocated memory buffers, mapping the data without copying it into temporary memory;
- Always use test-driven development, with dedicated multi-threaded test, trying to reach the worse-case limit (increase number of threads, data content, add some incoherent data, pause at random, try to stress network or disk access, benchmark with timing on real data...);
- Never trust your instinct, we are no computer but men... use accurate timing on real data and process.
I tried to follow those rules in our Open Source framework, and if you take a look at our code, you'll find out a lot of real-world sample code.Here are some famous quotes about optimization - nice rules to add to my too detailed list:
- Make it right before you make it fast. Make it clear before you make it faster. Keep it right when you make it faster. — Kernighan and Plauger, Elements of Programming Style.
- Premature optimization is the root of all evil. — Donald Knuth, quoting C. A. R. Hoare
- The key to performance is elegance, not battalions of special cases. The terrible temptation to tweak should be resisted. — Jon Bentley and Doug McIlroy
- The rules boil down to: "1. Don't optimize early. 2. Don't optimize until you know that it's needed. 3. Even then, don't optimize until you know what's needed, and where." — Herb Sutter
As usual, feedback and comments are welcome on our forum!