Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-devel / 2014-09

Support needed in understanding some heap.inc related stuff

4 messages · 2 people ·

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Lag Programming

Hi! I need some help in understanding three things viewed in heap.inc.

1) Why is variable "heap_lock_use" declared as "integer"? Apparently this variable never gets a negative value. Also, this variable never gets used as a parameter to a function/procedure call that would require a transformation of variable content(longint to byte as example). If this variable never gets a negative value then using an unsigned integer might improve in certain circumstances a little bit the code produced because apparently fpc trunk has improved zero and near zero integer comparisons at high optimization levels. If this is the case then comparing actual "signed_integer>0" should not be faster than "unsigned_integer0" automatically produced code at high optimization levels. Am I wrong here? As a side note, I would prefer manually changing those ">0" with "0" inside "InitHeapThread", "RelocateHeap" and "FinalizeHeap" but...whatever.

2) I'm not interested in the importance of the presented function. Why the "existing" code is preferred over "alternative" code?

"Existing" code:
Function SysFreeMemSize(p: pointer; size: ptruint):ptruint;
begin
  if size=0 then
    exit(0);
  { can't free partial blocks, ignore size }
  result := SysFreeMem(p);
end;
"Alternative" code:
Function SysFreeMemSize(p: pointer; size: ptruint):ptruint;
begin
if size0 then SysFreeMem(p) else Result:=nil; { can't free partial blocks, ignore size }
end;

There are two things that puzzle me:
a) The alternative code appears better to me because I think this function will have size0 more often than size=0. Why the existing code design was preferred? b) As found out working at the "slow if...then...else" report, the existing of an "exit" procedure, starting with level 2 optimization, leads to different registers used within assembly code. Is this a reason why main developers use so often the "exit" procedure? Is there something that I'm missing at the moment, something like: "starting with level2 optimizations, existence of "exit" procedure in a pascal code requires additional analysis that, as a side effect, usually improve register allocation "...or whatever reason to use this procedure?

3) I'm not interested in the importance of the presented function. Why the "existing" code design is preferred over "alternative" code?

Existing code:
function SysAllocMem(size: ptruint): pointer;
begin
  result := MemoryManager.GetMem(size);
  if resultnil then
    FillChar(result^,MemoryManager.MemSize(result),0);
end;
Alternative code:
function SysAllocMem(size: ptruint): pointer;
begin
result:=MemoryManager.GetMem(size);
if resultnil then FillChar(result^,size,0);
end;
The only things I could find to support the "existing" code over "alternative" code are that(now or in the future):
a) Maybe there is a possibility that the value of variable size gets changed after "result:=MemoryManager.GetMem(size);"
b) Maybe there is a possibility that following "result:=MemoryManager.GetMem(size);" variable result would point to a memory block that has allocated a size different than variable "size"'s content.
   I would feel confortable if these things would be explained to me straight from the horse's mouth. Meaning people that understand the importance of the existing code, not people that would send me to find out using "try&error" techniques, as those "try&error" results would mean nothing to me regarding this subject for too many reasons to be written here.

Reply by email

Nikolay Nikolov
to Lag Programming
Lag Programming wrote 25 lines
Hi! I need some help in understanding three things viewed in heap.inc. ... 3) I'm not interested in the importance of the presented function. Why the "existing" code design is preferred over "alternative" code? Existing code: function SysAllocMem(size: ptruint): pointer; begin result := MemoryManager.GetMem(size); if resultnil then FillChar(result^,MemoryManager.MemSize(result),0); end; Alternative code: function SysAllocMem(size: ptruint): pointer; begin result:=MemoryManager.GetMem(size); if resultnil then FillChar(result^,size,0); end; The only things I could find to support the "existing" code over "alternative" code are that(now or in the future): a) Maybe there is a possibility that the value of variable size gets changed after "result:=MemoryManager.GetMem(size);"

This cannot happen, because the 'size' parameter to MemoryManager.GetMem is passed by value, not by reference.

b) Maybe there is a possibility that following "result:=MemoryManager.GetMem(size);" variable result would point to a memory block that has allocated a size different than variable "size"'s content.

This could happen, at least in theory. Memory managers usually align memory block size up to some given granularity, so they often allocate more memory than you requested. The question is whether they save and return the original size in MemSize or the aligned (i.e. bigger) one. If they store the aligned size, it's a good idea, just in case, to clear all the memory up to that size.

Nikolay

Reply by email

Lag Programming

@Nikolay
Thank you for your answer. :) Now that you've answered, can you further respond to the following question: why is it needed to zero fill the memory allocated that exceeds the requested value? a) "pointer:=allocmem(initialsize); reallocmem(pointer,increasedsize);". I don't see the benefits of the zero filled bytes that exceed the "initialsize" value at "pointer". Once you "reallocmem" to increase size you don't expect to have the additional space filled with zeros. This means that an attempt to somehow optimize code because of initial more than enough zero filling is something hard to believe in. b) if your affirmation is right then for "bytepointer:=allocmem(sizeof(byte));" we would zero fill at least 3 or 7 times the necessary amount. At the moment I don't see nothing closest thing that got into my mind was the arrays. For an unknown reason to me, yet(somebody please enlighten me) dynamic arrays are always zero filled. But even these arrays don't use allocmem. It was something that got me curious. Instead of allocmem, getmem&fillchar are used separately :) (see dynarr.inc fpc_dynarray_setlength). Somebody saw the bottleneck(waste, whatever you want to call it) with this allocmem situation. So. Can you please further develop you answer according to this message.

Reply by email

Nikolay Nikolov
to Lag Programming
Lag Programming wrote 11 lines
@Nikolay Thank you for your answer. :) Now that you've answered, can you further respond to the following question: why is it needed to zero fill the memory allocated that exceeds the requested value? a) "pointer:=allocmem(initialsize); reallocmem(pointer,increasedsize);". I don't see the benefits of the zero filled bytes that exceed the "initialsize" value at "pointer". Once you "reallocmem" to increase size you don't expect to have the additional space filled with zeros. This means that an attempt to somehow optimize code because of initial more than enough zero filling is something hard to believe in.

To be honest, I don't know. It's probably for some extra safety, e.g. to minimize the damage from the undefined behaviour of reading beyond the memory block you allocated or to be able to detect heap corruption if you overwrite these zeros.

Show quoted text 12 lines
b) if your affirmation is right then for "bytepointer:=allocmem(sizeof(byte));" we would zero fill at least 3 or 7 times the necessary amount. At the moment I don't see nothing closest thing that got into my mind was the arrays. For an unknown reason to me, yet(somebody please enlighten me) dynamic arrays are always zero filled. But even these arrays don't use allocmem. It was something that got me curious. Instead of allocmem, getmem&fillchar are used separately :) (see dynarr.inc fpc_dynarray_setlength). Somebody saw the bottleneck(waste, whatever you want to call it) with this allocmem situation. So. Can you please further develop you answer according to this message.

I seriously doubt it makes any difference on 32-bit and 64-bit architectures. AFAIK, on a modern CPU, it takes exactly the same amount of time to write 32-bits to a 32-bit aligned address as to write a single byte. But the extra call to MemSize probably does add some extra overhead, yes. As for why are dynarrays zero filled, I can only guess, but these things come to mind:

1) delphi compatibility
2) zero filling ensures that magic reference counted types like ansistrings, interfaces and other dynarrays are properly initialized. The same reason class instances are initialized with zeros before the constructor is called.

Nikolay

Reply by email