Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deep copy of a record with R1:=R2, or Is there good way to implement NxM matrix with record?

I'm implementing a N x M matrix (class) with a record and an internal dynamic array like below.

TMat = record
public     
  // contents
  _Elem: array of array of Double;

  //
  procedure SetSize(Row, Col: Integer);

  procedure Add(const M: TMat);
  procedure Subtract(const M: TMat);
  function Multiply(const M: TMat): TMat;
  //..
  class operator Add(A, B: TMat): TMat;
  class operator Subtract(A, B: TMat): TMat;
  //..
  class operator Implicit(A: TMat): TMat; // call assign inside proc.
                                          // <--Self Implicit(which isn't be used in D2007, got compilation error in DelphiXE)

  procedure Assign(const M: TMat); // copy _Elem inside proc.
                                   // <-- I don't want to use it explicitly.
end;

I choose a record, because I don't want to Create/Free/Assign to use it.

But with dynamic array, values can't be (deep-)copied with M1 := M2, instead of M1.Assign(M2).

I tried to declare self-Implicit conversion method, but it can't be used for M1:=M2.

(Implicit(const pA: PMat): TMat and M1:=@M2 works, but it's pretty ugly and unreadable..)

Is there any way to hook assignment of record ?

Or is there any suggestion to implement N x M matrix with records ?

Thanks in advance.

Edit:

I implemented like below with Barry's method and confirmed working properly.

type
  TDDArray = array of array of Double;

  TMat = record
  private
     procedure CopyElementsIfOthersRefer;
  public
    _Elem: TDDArray;
    _FRefCounter: IInterface;
   ..
  end;

procedure TMat.SetSize(const RowSize, ColSize: Integer);
begin
  SetLength(_Elem, RowSize, ColSize);

  if not Assigned(_FRefCounter) then
    _FRefCounter := TInterfacedObject.Create;
end;

procedure TMat.Assign(const Source: TMat);
var
  I: Integer;
  SrcElem: TDDArray;
begin
  SrcElem := Source._Elem; // Allows self assign

  SetLength(Self._Elem, 0, 0);
  SetLength(Self._Elem, Length(SrcElem));

  for I := 0 to Length(SrcElem) - 1 do
  begin
    SetLength(Self._Elem[I], Length(SrcElem[I]));
    Self._Elem[I] := Copy(SrcElem[I]);
  end;
end;

procedure TMat.CopyElementsIfOthersRefer;
begin
  if (_FRefCounter as TInterfacedObject).RefCount > 1 then
  begin
    Self.Assign(Self); // Self Copy
  end;
end;

I agree it's not efficient. Just using Assign with pure record is absolutely faster.

But it's pretty handy and more readable.(and interesting. :-)

I think it's useful for light calculation or pre-production prototyping. Isn't it ?

Edit2:

kibab gives function getting reference count of dynamic array itself.

Barry's solution is more independent from internal impl, and perhaps works on upcoming 64bit compilers without any modification, but in this case, I prefer kibab's for it's simplicity & efficiency. Thanks.

  TMat = record
  private
     procedure CopyElementsIfOthersRefer;
  public
    _Elem: TDDArray;
   ..
  end;

procedure TMat.SetSize(const RowSize, ColSize: Integer);
begin
  SetLength(_Elem, RowSize, ColSize);
end;    

function GetDynArrayRefCnt(const ADynArray): Longword;
begin
  if Pointer(ADynArray) = nil then
    Result := 1 {or 0, depending what you need}
  else
    Result := PLongword(Longword(ADynArray) - 8)^;
end;

procedure TMat.CopyElementsIfOthersRefer;
begin
  if GetDynArrayRefCnt(_Elem) > 1 then
    Self.Assign(Self);
end;
like image 562
benok Avatar asked Dec 07 '10 11:12

benok


2 Answers

I've just realised a reason why this may not be such a great idea. It's true that the calling code becomes much simpler with operator overloading. But you may have performance problems.

Consider, for example, the simple code A := A+B; and suppose you use the idea in Barry's accepted answer. Using operator overloading this simple operation will result in a new dynamic array being allocated. In reality you would want to perform this operation in place.

Such in-place operations are very common in linear algebra matrix algorithms for the simple reason that you don't want to hit the heap if you can avoid it - it's expensive.

For small value types (e.g. complex numbers, 3x3 matrices etc.) then operator overloading inside records is efficient, but I think, if performance matters, then for large matrices operator overloading is not the best solution.

like image 138
David Heffernan Avatar answered Oct 02 '22 13:10

David Heffernan


You can use an interface field reference inside your record to figure out if your array is shared by more than one record: simply check the reference count on the object behind the interface, and you'll know that the data in the arrays is shared. That way, you can lazily copy on modification, but still use data sharing when the matrices aren't being modified.

like image 22
Barry Kelly Avatar answered Oct 02 '22 13:10

Barry Kelly