12

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;
benok
  • 688
  • 1
  • 6
  • 21

3 Answers3

9

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.

Barry Kelly
  • 41,404
  • 5
  • 117
  • 189
  • +1 Fantastic! I'm now off to look at my code and see if I can do it this way! Roll your own copy on write! – David Heffernan Dec 07 '10 at 22:11
  • Ah, lazy copy! I see! Just inserting Check'nCopy routine before all self modifying code. I'll try to implement with this technique. Thank you so much! – benok Dec 08 '10 at 01:52
  • 1
    +1 for an idea. But interface for this is IMHO not necessary overhead (or I've missed sth. here?). Ref count of any dynamic array can be read directly like this (if dyn-array is shared then it will return value > 1): 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; – Krystian Bigaj Dec 09 '10 at 20:08
  • @kibab Thank you for your code! (It's simpler than I imagined.) I confirmed it works, and perhaps I'll use yours in this case. Barry's solution is more independent from internal impl, and perhaps works on upcoming 64bit compilers, but I like yours for it's efficiency. Thanks! – benok Dec 10 '10 at 04:36
  • I'd only add, measure efficiency before jumping to conclusions. – Barry Kelly Dec 10 '10 at 05:47
4

You can't override record assignment by Implicit or Explicit operators. The best you can do IMO is not to use direct assignment, using M.Assign method instead:

procedure TMat.Assign(const M: TMat);
begin
// "Copy" currently only copies the first dimension,
//  bug report is open - see comment by kibab 
//  _Elem:= Copy(M._Elem);
  ..
end;

ex

M1.Assign(M2);

instead of

M1:= M2;
kludg
  • 27,213
  • 5
  • 67
  • 118
  • 5
    Please note that copy doesn't work with multi-dim arrays, http://qc.embarcadero.com/wc/qcmain.aspx?d=20086 Only first dimension is copied, other are just referenced, and as dynamic arrays are not CopyOnWrite (like strings), so changing in one, changes also in other ('copied' arrays). Similar you will probably find with copying records. – Krystian Bigaj Dec 07 '10 at 16:32
  • 2
    There are no multidimensional dynamic arrays; only arrays of arrays, which are a different concept (allows a jagged shape). Why would Copy do a deep copy for elements of the array it's copying, but only if those elements are arrays? (Basically, I'm suggesting that this bug is very close to being "as designed".) – Barry Kelly Dec 07 '10 at 18:28
  • @Barry Kelly: I disagree. IMO the current implementation of "Copy" procedure should be improved to include deep copy of dynamic arrays or else the whole concept of dynamic arrays in Delphi should be changed (CopyOnWrite support). – kludg Dec 07 '10 at 19:29
  • @Barry: It's too late to implement copy on write for dynamic arrays other than strings (think of all the existing code), even if it was a good idea, which it isn't, in my view – David Heffernan Dec 07 '10 at 22:10
3

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.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Yes, I agree. Operator overloading is just a syntax sugar and not efficient. Fast prototyping with easy way and then optimizing with proper way is my favorite (but dangerous for runnnig out of time :-) strategy. – benok Dec 09 '10 at 18:14
  • 1
    @benok: Operator overloading is efficient for small value types. We've just converted complex number, 3-vector, 3x3 matrix types to operator overloading and lost nothing in performance - object code is actually identical. I just don't think it will work out well with reference types rather than value types. – David Heffernan Dec 09 '10 at 19:27