Jump to content

Stefan Glienke

Members
  • Content Count

    1428
  • Joined

  • Last visited

  • Days Won

    141

Everything posted by Stefan Glienke

  1. @Ugochukwu Mmaduekwe I am probably doing it wrong but using either TMurmurHash3_x86_32 or TXXHash32 in a dictionary equality comparer for integer performs super poor compared to either the THashBobJenkins.GetHashValue from the RTL or the FNV1A_Hash_Meiyan from FastCompare. That is obviously because of the rather heavy OOP architecture which causes several (virtual) calls and allocations for every Compute call. This is what I put into my GetHashCode function for the equality comparer: hash.ComputeUntyped(value, SizeOf(Integer)).GetHashCode; // hash is an IHash instance where I created a TXXHash32 for.
  2. Post the benchmark code please or we are talking apples and oranges.
  3. You missed my point (I blame the language barrier): I was saying that comparing different implementations for some algorithms (such as hash tables) are not trivial to benchmark and compare - I have seen (and written before I knew better) too many biased benchmarks testing only fractions, ignoring certain aspects, using specific data, simply comparing apples and oranges or last but not least being fooled by hardware effects like branch predictors or cache behavior. And that is regardless the programming language.
  4. Implementing a fast hash table has way more and more important implications than picking the proper size and grow factor. Oh and proving that something is faster than another thing is far from trivial (as with most benchmarks). Interesting read: https://probablydance.com/2017/02/26/i-wrote-the-fastest-hashtable/
  5. Worthless, they give a crap about win32 codegen. There are way more severe issues open for ages...
  6. The capacity implementation of the RTL dictionary is a bit stupid. Typically the capacity is the number of items you can store in a collection without a reallocation/grow happening. However that is not the case here (in Spring4D we actually implemented it that way - so if you want to store 100 items without a grow happening you pass capacity 100 - the internal mechanism handles overallocating to satisfy the max fill factor). The RTL implementation takes the passed value, calculates the next power of 2 greater than the passed value, allocates as many buckets and sets the grow threshold to 75% of that value. In the benchmark code above that means from the passed 200 it calculates 256 and sets the threshold to 192 which means you can store 192 items before it will grow.
  7. I am not stopping anyone from exploring that area and I would be happy if someone comes up with a solution. However the way that Delphi, the IDE and the common UI frameworks work imo stands in the way of something that I would consider a good solution. Imho two key parts of MVVM are the painless bindings and the declarative approach to specify the wiring. In WPF most of the wiring goes into the xaml and much of that can be validated and does not rely on magic strings as it does with current solutions in Delphi. MVVM frameworks/libraries in JS can go further and elegantly be integrated into the html code (such as Knockout.js or Aurelia). Then if that is not enough reactive programming pushes such approaches even further as you can see with ReactiveUI and others. While we have event handlers in Delphi the implementation of them is typically done very differently from that reactive does (treating events as a kind of stream of events where you can apply all kinds of operators and behavior)
  8. Especially with the non highly optimized hash functions used in System.Generics.Defaults.
  9. Actually especially for a hashtable that algo is really terrible. It makes it super vulnerable against a non optimal hash function or heavy occurency of hash/bucket collisions. Mod or anding with capacity-1 provides a better distribution.
  10. I have been guilty of doing that myself but given this and other examples it shows why calling virtual methods from a ctor is considered a code smell by some people. And from what I just learned C++ even refuses to do a virtual call as per https://isocpp.org/wiki/faq/strange-inheritance#calling-virtuals-from-ctors And also "Injection Constructors should be simple"
  11. Stefan Glienke

    class designation question

    Using generics where class reference suffice is overkill and unnecessary bloat
  12. Good comments are not about *what* the code does but *why* because that is typically the question a future reader will have when they read the code. And experience teaches you to anticipate such questions and put comments where appropriate.
  13. Even Chuck Norris refactors his code (with a roundhouse kick though but still...)
  14. Stefan Glienke

    array of weak references

    Nope, that would be an array of unsafe references - an important characteristic of a weak reference is that it will be cleared when the referenced instance gets destroyed. Since you cannot declare TArray<[weak]IInterface> or something like that you need to make a record type with a weak reference field. Since Object ARC will be history with 10.4 I don't care - but weak reference for interfaces will still be a thing: {$APPTYPE CONSOLE} uses System.SysUtils; type weakref = record [weak] ref: IInterface; class operator Implicit(const value: IInterface): weakref; inline; class operator Implicit(const value: weakref): IInterface; end; class operator weakref.Implicit(const value: IInterface): weakref; begin Result.ref := value; end; class operator weakref.Implicit(const value: weakref): IInterface; begin Result := value.ref; end; procedure Test; var refs: TArray<IInterface>; weaks: Tarray<weakref>; intf: IInterface; begin SetLength(refs, 3); refs[0] := TInterfacedObject.Create; refs[1] := TInterfacedObject.Create; refs[2] := TInterfacedObject.Create; SetLength(weaks, 3); weaks[0] := refs[0]; weaks[1] := refs[1]; weaks[2] := refs[2]; refs := nil; Writeln(Assigned(IInterface(weaks[0]))); Writeln(Assigned(IInterface(weaks[1]))); Writeln(Assigned(IInterface(weaks[2]))); end; begin Test; end. FWIW Spring4D has Weak<T> that works for interfaces and objects and has the added feature that when used for objects on non ARC platform it also clears then and thus protects against dangling references.
  15. How about fixing the issues that FastMM4 has rather than inventing yet another newly mostly untested and unproven memory manager?
  16. Official one - shipped one does not have the ability to report the exact allocation callstacks or detect use after free. It's nice to see *that* you have a memory leak with the shipped one but not knowing where exactly it comes from (unless the class name gives a direct hint) is pretty useless.
  17. Stefan Glienke

    What are your compiler settings for debug builds?

    Just saying... RSP-16751
  18. Stefan Glienke

    What are your compiler settings for debug builds?

    Unfortunately there is not - iterating any RTL list in a proper way (for i := 0 to list.count-1) will execute range checking code (not the compiler inserted but explicitly coded) and even for x in list code does (check System.Classes.TStringsEnumerator.GetCurrent as example)
  19. Stefan Glienke

    What are your compiler settings for debug builds?

    Range checking is an interesting thing - lets look at the compiler output for the following code with range checking: procedure Test; var numbers: array of Integer; i: Integer; begin SetLength(numbers, 10000000); for i := Low(numbers) to High(numbers) do numbers[i] := i; end; Project376.dpr.18: for i := Low(numbers) to High(numbers) do 004CF162 8B45FC mov eax,[ebp-$04] 004CF165 85C0 test eax,eax 004CF167 7405 jz $004cf16e 004CF169 83E804 sub eax,$04 004CF16C 8B00 mov eax,[eax] 004CF16E 8BD0 mov edx,eax 004CF170 4A dec edx 004CF171 85D2 test edx,edx 004CF173 7C1B jl $004cf190 004CF175 42 inc edx 004CF176 33C0 xor eax,eax Project376.dpr.19: numbers[i] := i; 004CF178 8B4DFC mov ecx,[ebp-$04] 004CF17B 85C9 test ecx,ecx 004CF17D 7405 jz $004cf184 004CF17F 3B41FC cmp eax,[ecx-$04] 004CF182 7205 jb $004cf189 004CF184 E86F96F3FF call @BoundErr 004CF189 890481 mov [ecx+eax*4],eax 004CF18C 40 inc eax Project376.dpr.18: for i := Low(numbers) to High(numbers) do 004CF18D 4A dec edx 004CF18E 75E8 jnz $004cf178 Now without range checking: Project376.dpr.18: for i := Low(numbers) to High(numbers) do 004CF162 8B45FC mov eax,[ebp-$04] 004CF165 85C0 test eax,eax 004CF167 7405 jz $004cf16e 004CF169 83E804 sub eax,$04 004CF16C 8B00 mov eax,[eax] 004CF16E 8BD0 mov edx,eax 004CF170 4A dec edx 004CF171 85D2 test edx,edx 004CF173 7C0D jl $004cf182 004CF175 42 inc edx 004CF176 33C0 xor eax,eax Project376.dpr.19: numbers[i] := i; 004CF178 8B4DFC mov ecx,[ebp-$04] 004CF17B 890481 mov [ecx+eax*4],eax 004CF17E 40 inc eax Project376.dpr.18: for i := Low(numbers) to High(numbers) do 004CF17F 4A dec edx 004CF180 75F6 jnz $004cf178 The question is: does this code even need range checking? The answer is no - the loop range itself guarantees it. What if the compiler would notice if I mistakenly would write: for i := Low(numbers) to Length(numbers) do numbers[i] := i; instead of putting some code that slows things down and can optionally be turned off. Of course this is a simple example of an out of range error and there are others that are not easily noticeable at compiletime - but those that are the compiler (or static code analysis) could do a whole lot of potential error detection.
  20. Stefan Glienke

    Overloaded generic methods

    Overloads based on generic type constraint are not possible. Personally I would just make one method without constraint and use GetTypeKind(T) internally to decide if its tkInterface or tkClass and simply raise an exception if anyone put anything but a class or interface into T.
  21. Stefan Glienke

    Vcl Test Runner -> group by category

    There is - the second button from the left (the "fast forward" one) does that - it actually has two usages - if no tests are selected at all it does "Discover tests" - when any tests are selected it only runs those. However TestInsight does not display tests hierarchical but only grouped by type or fixture and I have no plans to change that.
  22. Stefan Glienke

    Dynamic Test creation possible?

    Yes but the real question is: why do you want to switch to DUnitX when you have working DUnit tests already?
  23. Stefan Glienke

    Memory Management with many objects

    I just scribbled something together that should be good enough to start with and get some basic information about how many objects are created at a given time - needs to be first or directly after the memory manager unit in the dpr: Replace the dictionary with a Spring4D one and you have some easy sorting and filtering on top 🙂 unit ObjectCounter; interface uses Generics.Collections; function GetObjectCounts: TDictionary<TClass,Integer>; implementation uses madCodeHook; type TObjectCounter = record class var ObjectCounts: TDictionary<TClass,Integer>; class var OldInitInstance: function (Self: TClass; Instance: Pointer): TObject; class var OldCleanupInstance: procedure (Self: TObject); class function InitInstance(Self: TClass; Instance: Pointer): TObject; static; class procedure CleanupInstance(Self: TObject); static; class procedure Init; static; class procedure Finalize; static; end; function GetObjectCounts: TDictionary<TClass,Integer>; begin Result := TObjectCounter.ObjectCounts; end; { TObjectCounter } class function TObjectCounter.InitInstance(Self: TClass; Instance: Pointer): TObject; var count: Integer; begin if ObjectCounts.TryGetValue(Self, count) then ObjectCounts[Self] := count + 1 else ObjectCounts.Add(Self, 1); Result := OldInitInstance(Self, Instance); end; class procedure TObjectCounter.CleanupInstance(Self: TObject); var count: Integer; begin if ObjectCounts.TryGetValue(Self.ClassType, count) then if count = 1 then ObjectCounts.Remove(Self.ClassType) else ObjectCounts[Self.ClassType] := count - 1; OldCleanupInstance(Self); end; class procedure TObjectCounter.Init; begin ObjectCounts := TDictionary<TClass,Integer>.Create; HookCode(@TObject.InitInstance, @InitInstance, @OldInitInstance); HookCode(@TObject.CleanupInstance, @CleanupInstance, @OldCleanupInstance); end; class procedure TObjectCounter.Finalize; begin UnhookCode(@OldInitInstance); UnhookCode(@OldCleanupInstance); ObjectCounts.Free; end; initialization TObjectCounter.Init; finalization TObjectCounter.Finalize; end.
  24. Stefan Glienke

    This implementation is Thread-safe?

    What? Assignment of these three types consist of multiple operations - just look into _UStrAsg, _IntfCopy or _DynArrayAsg - hence they are not atomic.
×