8

I need to have undo+redo stack for a limited number of classes under my control that has to be very very very fast and using RTTI and XML or streams is not feasible as the count of instances can be as high as 2000+ in nested object lists. The objects need to be copied into and out of via memento pattern and reloaded instantly.

Is there a way to clone objects by copying the memory and re-instantiating the objects from that memory?

MX4399
  • 1,519
  • 1
  • 15
  • 27
  • 1
    So you can't implement the memento pattern without cloning objects? Are all your objects inheriting from TPersistent, and thus supporting TPersistent.Assign is your best bet? – Warren P Oct 17 '11 at 18:40
  • You could do that so long as your objects did not contain references. – David Heffernan Oct 17 '11 at 18:41
  • 1
    Problem is, object is more than its memory. What about open files, other handles, synchronization primitives, interfaces with refcounting, etc.? Only object itself knows how to copy itself properly. – haimg Oct 17 '11 at 18:41
  • 2
    How much speed up did your profiling experiments indicate that you need? What was your reference implementation for profiling? – David Heffernan Oct 17 '11 at 18:44
  • Assign is a candidate - and maybe the safest. – MX4399 Oct 17 '11 at 18:50
  • If there are significantly more edits to the objects then deletes or inserts of the objects, ánd if the classes aren't light, then you could also consider maintaining a undo/redo stack with separate objects which point to the revised objects and carry only the changes. – NGLN Oct 18 '11 at 20:09

5 Answers5

15

Hardly. You can easily copy the memory of an object, but part of that memory will be pointers in which case you only copy the reference. Those pointers can include strings and other objects as well.

I think the best way is to inherit these classes from TPersistent (or any decendant) en implement the Assign method for each of them. That way, you can create a second instance and assign the object to that new instance. In the Assign implementation you can decide for yourself what data should be copied and what not.

GolezTrol
  • 114,394
  • 18
  • 182
  • 210
11

2000+ nested objects is not so huge, and won't be so slow, even with RTTI (disk access or compression will be much slower). With a direct SaveToStream manual serialization, it is very fast, if you use FastMM4 (the default memory manager since Delphi 2006).

Perhaps you may change your algorithm and use dynamic arrays instead (there is an open source serializer here). But your data may not fit this kind of records.

Or never/seldom release memory, and only use objects or record references. You can have an in-memory pool of objects, with some kind of manual garbage collector, and only handle reference to objects (or records).

If you have string inside the objects, you may not reallocate them and maintain a global list of used strings: reference counting and copy-on-write will make it much faster than standard serialization and allocation (even with FastMM4).

In all cases, it is worth making a real profiling of your application. General human guess about performance bottlenecks are most of the time wrong. Only trust a profiler, and a wall clock. Perhaps your current implementation is not so slow, and the real bottleneck is not the object process, but somewhere else.

Do not optimize too early. "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.

Arnaud Bouchez
  • 42,305
  • 3
  • 71
  • 159
  • Thanks for the insight. Using the clone and assign approach does rely directly on the memory manager which is very fast as you pointed out - the key part being the focus on the memory manager and levering it in an intelligent manner by selectively populating the clones AND flagging the clones as clones internally and avoid RTTI. – MX4399 Oct 18 '11 at 11:05
  • @MX4399 It may also worth trying the global `string` list: using reference counting may definitively be faster than a stream-approach. Of course, the `Assign()` approach will use the advantages of the very same reference-counting mechanism. – Arnaud Bouchez Oct 18 '11 at 11:38
3

A simple way to clone an object is :

  • Derive your clonable class from TPersistent
  • Implement the Assign procedure

See Memento design pattern example here : http://sourcemaking.com/design_patterns/memento/delphi/1

TridenT
  • 4,879
  • 1
  • 32
  • 56
1

An approach I have used for this situation in the past is to declare a record with the part of the object I need to retain, and use that record within a class. See my old answer at Optimizing Class Size in Delphi. Is there something like "packed classes"? for an example.

As records are copied on assignment, it is then easy to copy the fields from one object type to another.

e.g.

TMyFields = record
  ID: Integer;
  Name: string;
end;

TMyClass = class(TPersistent)    
 private
  FFields: TMyFields;
  FStack: TStack;
  class TStackItem = class(TObject)
  public
    Fields: TMyFields;
  end;

protected
   property ID: integer read FFields.ID;
   property Name: string read FFields.Name;
   procedure Push;
   // etc...
end;


procedure TMyClass.Push;
var
  NewItem: TStackItem;
begin
  NewItem := TStackItem.Create;
  NewItem.Fields := FFields;
  FStack.Push(NewItem);
end;

Of course, as you are using XE, you could use Generics.Collections.TObjectStack instead.

Community
  • 1
  • 1
Gerry Coll
  • 5,867
  • 1
  • 27
  • 36
0

Undo/Redo could be implemented with TMemoryStream - just save object's data to stream and load it when redo/undo appears. It uses an object's saveToStream/loadfromstream mechanics and allows to reconstruct references correctly.

Update:

TMyObject = class
    procedure SaveToStream(AStream : TStream);
    procedure LoadFromStream(AStream : TStream);
end;

Then :

Before change :

myobject.SaveToStream(MemoryStream);
undoManager.AddItem(myobject.Identifier, MemoryStream);
Do change object...

By restore

myobject := FindObject(undomanager.LastItem.Identifier)
myobject.LoadFromStream(undomanager.LastItem.MemoryStream);

I use similar approach for my CAD Software, it works pretty good. It can be also used to store multiply objects, changed in one operation.

George
  • 84
  • 3
  • 2
    TObject doesn't have SaveToStream. – Rob Kennedy Oct 17 '11 at 20:02
  • I didn't mean TObject, I know it has no persistence functions. – George Oct 18 '11 at 04:59
  • 1
    Then what *do* you mean? Please edit your answer to be more precise. You capitalized *Object* in the middle of a sentence, suggesting you meant `TObject`. If you didn't mean for it to be a proper noun, you also need an article (i.e., *an* or *the*) in front of it. – Rob Kennedy Oct 18 '11 at 05:25
  • Sorry, Rob, I mainly speak and write German and in this language noun must be capitalized. My fault. I'll correct my answer. – George Oct 18 '11 at 18:55