Recent

Author Topic: IRC channel  (Read 11713 times)

Joanna

  • Hero Member
  • *****
  • Posts: 763
Re: IRC channel
« Reply #105 on: December 12, 2022, 08:38:21 am »
Memory indeed is a complex issue. I’m not sure what the history of computer design was that led to the memory becoming so easy to fragment. Stack memory seems so elegant in comparison but that won’t work for randomly allocated/ deallocated things.

Another idea which has probably already been tried is how about marking  addresses  with their size as reusable when finished with them and then when something new is needed just reuse existing piece of memory that is the same size?  This might be ok when there is not too much size variation..
I have no idea how this would be implemented though.

I used to think that freeing things as soon as they weren’t needed would be more efficient for using less memory but after this discussion I’m no longer so sure
✨ 🙋🏻‍♀️ More Pascal enthusiasts are needed on IRC .. https://libera.chat/guides/ IRC.LIBERA.CHAT  Ports [6667 plaintext ] or [6697 secure] channel #fpc  Please private Message me if you have any questions or need assistance. 💁🏻‍♀️

440bx

  • Hero Member
  • *****
  • Posts: 4030
Re: IRC channel
« Reply #106 on: December 12, 2022, 09:03:19 am »
Another idea which has probably already been tried is how about marking  addresses  with their size as reusable when finished with them and then when something new is needed just reuse existing piece of memory that is the same size?  This might be ok when there is not too much size variation..
I have no idea how this would be implemented though.
That's what the Windows Heap manager does and it imposes some overhead (which is always undesirable.)  The heap manager keeps track of a linked list of freed blocks sorted by size along with 4 pointers into the linked list where each guarantees the size of a free block being greater than or equal to some value (that's how Win9x did it - see Matt Pietrek's Windows Programming Secrets - the management algorithms have been improved in the NT branch of Windows but, the management concepts are still the same.)

The "problem" is, no matter how efficient and fast the management algorithms may be, they still add time to every allocation and deallocation.  The additional time can become a real problem for a multi-threaded program because heap management is inherently single threaded which means time will grow in proportion to contention. 

That's one of the many advantages of doing your own memory management.  Allocations are still single threaded but they usually consist of incrementing a single pointer value which is very fast and quite often, it is possible to structure the problem in such a way as giving each thread it's own block of memory, that way, there is no contention, no fragmentation and speed to burn.  Great stuff :)

I used to think that freeing things as soon as they weren’t needed would be more efficient for using less memory but after this discussion I’m no longer so sure
Actually, the majority of the problems come into existence when allocations and deallocations are mixed.  A reasonably decent solution is, create a heap, put all the data you need in there, don't delete/free anything, once you don't need the data, simply destroy the heap.  That way allocations will be fast because there are no chains of "free space" to search and, if the heap is dedicated to a single thread, it can be told not to use a synchronization object. 

Of course, doing your own memory management requires some work on your part but, in real programs, the benefits greatly outweigh the convenience of "automatic management" (what the compiler does for you.)
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

alpine

  • Hero Member
  • *****
  • Posts: 1064
Re: IRC channel
« Reply #107 on: December 12, 2022, 09:39:31 am »
Memory indeed is a complex issue. I’m not sure what the history of computer design was that led to the memory becoming so easy to fragment. Stack memory seems so elegant in comparison but that won’t work for randomly allocated/ deallocated things.
Yes, it is, without a doubt it can be quite complicated.

But all depends on what specific kind of application you're working on. Most of the time there is no need to bother much. But it is always good to have a good insight what is going on behind the scenes. Unfortunately, some of the good things about the OOP contribute in that fragmentation phenomena - namely the container objects grows their size in a logarithmic way, the object instances are always dynamically allocated, the managed objects such as dynarrays and strings are heap based. But there are workarounds if you need to.

Another idea which has probably already been tried is how about marking  addresses  with their size as reusable when finished with them and then when something new is needed just reuse existing piece of memory that is the same size?  This might be ok when there is not too much size variation..
I have no idea how this would be implemented though.
The existing heap manager is doing a decent "pooling" for you, at least for the small chunks. It is really a quite good at that (I had mentioned it before). But if you're writing a high-availability application it requires very, very careful  planning.

I used to think that freeing things as soon as they weren’t needed would be more efficient for using less memory but after this discussion I’m no longer so sure
That is always a good approach, and it has also additional benefits e.g. reduces the possibility of leaks, deadlocks, etc.
« Last Edit: December 12, 2022, 09:42:26 am by y.ivanov »
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Joanna

  • Hero Member
  • *****
  • Posts: 763
Re: IRC channel
« Reply #108 on: December 12, 2022, 12:45:29 pm »
I do tend to use classes a lot some of which contain strings. I am curious if I change the length of a string stored in the field of a class does that change the amount of memory being used? I think shortstring is limited to 255 chars but string has no fixed length ,so how much memory would be used? Just enough to store it in its current length? Supposed you have a a tmemo and paste a lot of text into it? What happens With memory? Does pasting text allocate memory? Or suppose you create a class containing a tmemo and retrieve the text from an inifile. The class needs less memory before the text is loaded? I’m curious how it all works. If you create another class before loading text to memo it seems like the memory Pertaining to different classes could be intertwined.

I’ve never done a multithreaded application. What are the circumstances which require multiple threads is it for being able to respond to different events while something else is working?
I vaguely remember a command that could be put into a loop to respond to messages . I can’t remember what it was...
✨ 🙋🏻‍♀️ More Pascal enthusiasts are needed on IRC .. https://libera.chat/guides/ IRC.LIBERA.CHAT  Ports [6667 plaintext ] or [6697 secure] channel #fpc  Please private Message me if you have any questions or need assistance. 💁🏻‍♀️

Marc

  • Administrator
  • Hero Member
  • *
  • Posts: 2584
Re: IRC channel
« Reply #109 on: December 12, 2022, 02:05:08 pm »
A string (not a short string) is just a pointer to a piece of memory, which is managed by fpc. The memory of your class doesn't change by this, since your class has just a pointer to it.

If you have a TMemo, and you paste text to it, nothing happens to your class, there is even no memory allocated by fpc/lcl. The text is part of the textwidget your os/window manager provides.
The moment you use the text property of a TMemo, the text is retrieved from that widget, by first allocating a string of enough length, and then ask the widget to copy the text into it. 
This text doesn't change the amount of memory your class needs, but it does change the amout of (heap) memory your application has allocated.

Marc
//--
{$I stdsig.inc}
//-I still can't read someones mind
//-Bugs reported here will be forgotten. Use the bug tracker

alpine

  • Hero Member
  • *****
  • Posts: 1064
Re: IRC channel
« Reply #110 on: December 12, 2022, 02:40:58 pm »
I do tend to use classes a lot some of which contain strings. I am curious if I change the length of a string stored in the field of a class does that change the amount of memory being used? I think shortstring is limited to 255 chars but string has no fixed length ,so how much memory would be used? Just enough to store it in its current length? Supposed you have a a tmemo and paste a lot of text into it? What happens With memory? Does pasting text allocate memory? Or suppose you create a class containing a tmemo and retrieve the text from an inifile. The class needs less memory before the text is loaded? I’m curious how it all works. If you create another class before loading text to memo it seems like the memory Pertaining to different classes could be intertwined.
Short strings (those with the square brackets [] at the end) are fully contained into the declared variable/record/class. From the memory perspective, string[N] can be viewed as array[0..N] of char with zero index being used as the current string length. They are just handled a bit differently than arrays by the compiler (you can concatenate them with + for example). On the other hand, strings declared without square brackets are actually pointers to an internal structure in heap, which holds the contents together with the code-page, current length, reference counter. In the trivial case when the string is empty ('') the pointer is just Nil. They are "managed",  i.e. the compiler internally allocates/reallocates/de-allocates them according to the operations applied. They have copy-on-write semantics.

Using shortstrings have no implications on heap, at least until you explicitly start to allocate memory via pointers for those strings. But they're copied in their entirety when passed as an argument or returned back from a function.

You can intermix both string types in source but that actually it will lead to more heap operations, since the compiler will automatically convert one type to another using temporaries.
If you're using a library with a functions and/or properties defined as a string (without []), the LCL is such a library, you can't do much about it. But since LCL is for UI, it is not expected the application to work for an indefinite amount of time i.e. it will be closed, restarted, etc. For example web browsers put each tab in a separate process in order to restart them if needed, without affecting the other tabs.

I’ve never done a multithreaded application. What are the circumstances which require multiple threads is it for being able to respond to different events while something else is working?
I vaguely remember a command that could be put into a loop to respond to messages . I can’t remember what it was...
Multithreading is a vast subject which has also close relationships with the memory management, but IMO it shouldn't be involved into the discussion, it will be too much for now.

Edit: A word about the classes: they have a virtual class method newinstance and a virtual method FreeInstance which allocates, resp. de-allocates the instance in heap. They can be overridden in order to implement different means of allocation for each class when needed, i.e. to make pools or use a pre-allocated chunks or whatever convenient.
« Last Edit: December 12, 2022, 02:57:32 pm by y.ivanov »
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Joanna

  • Hero Member
  • *****
  • Posts: 763
Re: IRC channel
« Reply #111 on: December 13, 2022, 12:45:44 pm »
That Is interesting how pascal handles strings of indetermined length as a pointer to memory in such a way as  to keep allocating more memory as the string is appended to. I’ve heard that other languages are not so smart with string management and have overflows?
It also seems like a program with a lot of strings that come and go could get easily fragmented in memory.

Quote
But since LCL is for UI, it is not expected the application to work for an indefinite amount of time i.e. it will be closed, restarted, etc.
While that might be true in most cases I think that gui programs should be designed to run indefinitely. Restarting things is annoying and sometimes loses data.
✨ 🙋🏻‍♀️ More Pascal enthusiasts are needed on IRC .. https://libera.chat/guides/ IRC.LIBERA.CHAT  Ports [6667 plaintext ] or [6697 secure] channel #fpc  Please private Message me if you have any questions or need assistance. 💁🏻‍♀️

Marc

  • Administrator
  • Hero Member
  • *
  • Posts: 2584
Re: IRC channel
« Reply #112 on: December 13, 2022, 02:06:17 pm »
A good memory manager can handle fragmentation pretty well.
We run D6 services with a lot of IO and string manipulation 24/7 at work. The old D6 manager can't handle that and suffers from fragmentation after several 1M+ operations. Replacing that by FastMM (D7+ standard manager) solves it and has no problem.

Strings are allocated with some extra room, so adding one character won't require a new allocation.

Marc
//--
{$I stdsig.inc}
//-I still can't read someones mind
//-Bugs reported here will be forgotten. Use the bug tracker

alpine

  • Hero Member
  • *****
  • Posts: 1064
Re: IRC channel
« Reply #113 on: December 13, 2022, 03:04:14 pm »
That Is interesting how pascal handles strings of indetermined length as a pointer to memory in such a way as  to keep allocating more memory as the string is appended to. I’ve heard that other languages are not so smart with string management and have overflows?
It also seems like a program with a lot of strings that come and go could get easily fragmented in memory.
Not so easily as some measures were taken already to mitigate the effect. I already pointed out that the current FPC heap manager do pooling and as Marc noted, the strings allocation is made accordingly. 

Quote
But since LCL is for UI, it is not expected the application to work for an indefinite amount of time i.e. it will be closed, restarted, etc.
While that might be true in most cases I think that gui programs should be designed to run indefinitely. Restarting things is annoying and sometimes loses data.
I did not mean that they should be restarted every hour or two, rather only when necessary and it can be done in a way that is not so obvious, e.g. for maintenance, for updating, for improving the service, archiving data, you name it.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Joanna

  • Hero Member
  • *****
  • Posts: 763
Re: IRC channel
« Reply #114 on: December 14, 2022, 03:13:23 am »
Strings are allocated with some extra room, so adding one character won't require a new allocation.
I’m curious if shortstring reserves a full 255 bytes even if what is being stored is only 3 letter string.
I suppose this is why the ability to define shorter strings like string [5] exists.
Also I wonder is there are any differences in the way unicode strings are stored in memory compared to ascii Strings.
✨ 🙋🏻‍♀️ More Pascal enthusiasts are needed on IRC .. https://libera.chat/guides/ IRC.LIBERA.CHAT  Ports [6667 plaintext ] or [6697 secure] channel #fpc  Please private Message me if you have any questions or need assistance. 💁🏻‍♀️

Bogen85

  • Hero Member
  • *****
  • Posts: 595
Re: IRC channel
« Reply #115 on: December 14, 2022, 04:03:49 am »
Strings are allocated with some extra room, so adding one character won't require a new allocation.
I’m curious if shortstring reserves a full 255 bytes even if what is being stored is only 3 letter string.
I suppose this is why the ability to define shorter strings like string [5] exists.
Also I wonder is there are any differences in the way unicode strings are stored in memory compared to ascii Strings.

https://wiki.freepascal.org/Shortstring

A Short string is always going to be 256 bytes.
First field is the length.


Bogen85

  • Hero Member
  • *****
  • Posts: 595
Re: IRC channel
« Reply #116 on: December 14, 2022, 04:22:48 am »
Strings are allocated with some extra room, so adding one character won't require a new allocation.
I’m curious if shortstring reserves a full 255 bytes even if what is being stored is only 3 letter string.
I suppose this is why the ability to define shorter strings like string [5] exists.
Also I wonder is there are any differences in the way unicode strings are stored in memory compared to ascii Strings.

https://wiki.freepascal.org/Ansistring
https://wiki.freepascal.org/Unicodestring

See definition of both.
Definition of AnsiString shows the layout in memory.

Dynamically allocated...
The are managed types.  Where the pointer points, when memory is allocated and freed is not something the programmer has to deal with.



Bogen85

  • Hero Member
  • *****
  • Posts: 595
Re: IRC channel
« Reply #117 on: December 14, 2022, 04:30:51 am »
I’m curious if shortstring reserves a full 255 bytes even if what is being stored is only 3 letter string.
I suppose this is why the ability to define shorter strings like string [5] exists.
Also I wonder is there are any differences in the way unicode strings are stored in memory compared to ascii Strings.

I still fail to grasp why for simple questions like these it seems you want or expect others to look up the answers for you, when you could have just as easily looked them up yourself.

Maybe that is not your intent (that you expect others to look things up up for you), but it sure comes across that way.

Handoko

  • Hero Member
  • *****
  • Posts: 5154
  • My goal: build my own game engine using Lazarus
Re: IRC channel
« Reply #118 on: December 14, 2022, 05:00:55 am »
A Short string is always going to be 256 bytes.

Not fully correct.

Try this code:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. Type
  3.   TName = string[10];
  4. var
  5.   Names: array[1..1000] of TName;
  6. begin
  7.   ShowMessage(SizeOf(Names).ToString);
  8. end;

The size of a shortstring can range from 1 byte to 256 bytes.
Read more:
https://wiki.freepascal.org/Character_and_string_types#ShortString

Bogen85

  • Hero Member
  • *****
  • Posts: 595
Re: IRC channel
« Reply #119 on: December 14, 2022, 06:41:55 am »
A Short string is always going to be 256 bytes.

Not fully correct.

Interesting. But one can't do
Quote
shortstring[10];

This only works when string is shortstring?

Not for {$H+} when string is ansistring? Let me see...

This compiles:
Code: Pascal  [Select][+][-]
  1. program shorts;
  2.  
  3. {$mode objfpc}
  4. {$H+}
  5.  
  6. procedure main;
  7. Type
  8.   TName = shortstring;
  9. var
  10.   Names: array[1..1000] of TName;
  11. begin
  12.   writeln(SizeOf(Names));
  13. end;
  14.  
  15. begin
  16.   main;
  17. end.
  18.  

Code: Text  [Select][+][-]
  1. $ fpc shorts
  2. Free Pascal Compiler version 3.2.2 [2022/08/17] for x86_64
  3. Copyright (c) 1993-2021 by Florian Klaempfl and others
  4. Target OS: Linux for x86-64
  5. Compiling shorts.pas
  6. Linking shorts
  7. 17 lines compiled, 0.1 sec
  8.  
  9. $ ./shorts
  10. 256000
  11.  

This does not compile:

Code: Pascal  [Select][+][-]
  1. program shorts;
  2.  
  3. {$mode objfpc}
  4. {$H+}
  5.  
  6. procedure main;
  7. Type
  8.   TName = shortstring[100];
  9. var
  10.   Names: array[1..1000] of TName;
  11. begin
  12.   writeln(SizeOf(Names));
  13. end;
  14.  
  15. begin
  16.   main;
  17. end.
  18.  

Code: Text  [Select][+][-]
  1. Free Pascal Compiler version 3.2.2 [2022/08/17] for x86_64
  2. Copyright (c) 1993-2021 by Florian Klaempfl and others
  3. Target OS: Linux for x86-64
  4. Compiling shorts.pas
  5. shorts.pas(8,27) Error: Error in type definition
  6. shorts.pas(10,28) Error: Illegal expression
  7. shorts.pas(18) Fatal: There were 2 errors compiling module, stopping
  8. Fatal: Compilation aborted
  9. Error: /usr/bin/ppcx64 returned an error exitcode
  10.  


This does compile and run:
Code: Pascal  [Select][+][-]
  1. program shorts;
  2.  
  3. {$mode objfpc}
  4. {$H+}
  5.  
  6. procedure main;
  7. Type
  8. {$H-}
  9.   TName = string[100];
  10. {$H+}
  11. var
  12.   Names: array[1..1000] of TName;
  13. begin
  14.   writeln(SizeOf(Names));
  15. end;
  16.  
  17. begin
  18.   main;
  19. end.
  20.  


Code: Text  [Select][+][-]
  1.  
  2. $ fpc shorts
  3. Free Pascal Compiler version 3.2.2 [2022/08/17] for x86_64
  4. Copyright (c) 1993-2021 by Florian Klaempfl and others
  5. Target OS: Linux for x86-64
  6. Compiling shorts.pas
  7. Linking shorts
  8. 19 lines compiled, 0.1 sec
  9.  
  10. $ ./shorts
  11. 101000
  12.  

Hmm.... What kind of string is that?

Code: Pascal  [Select][+][-]
  1. program shorts;
  2.  
  3. {$mode objfpc}
  4. {$H+}
  5.  
  6. procedure main;
  7. Type
  8. {$H-}
  9.   TName0 = string[100];
  10. {$H+}
  11.   TName1 = string[100];
  12.   TName2 = shortstring;
  13.   TName3 = string;
  14. var
  15.   Names0: array[1..1000] of TName0;
  16.   Names1: array[1..1000] of TName1;
  17.   Names2: array[1..1000] of TName2;
  18.   Names3: array[1..1000] of TName3;
  19. begin
  20.   writeln(SizeOf(Names0), ' ', GetTypeKind(TName0));
  21.   writeln(SizeOf(Names1), ' ', GetTypeKind(TName1));
  22.   writeln(SizeOf(Names2), ' ', GetTypeKind(TName2));
  23.   writeln(SizeOf(Names3), ' ', GetTypeKind(TName3));
  24. end;
  25.  
  26. begin
  27.   main;
  28. end.
  29.  

Code: Text  [Select][+][-]
  1. $ fpc shorts
  2. Free Pascal Compiler version 3.2.2 [2022/08/17] for x86_64
  3. Copyright (c) 1993-2021 by Florian Klaempfl and others
  4. Target OS: Linux for x86-64
  5. Compiling shorts.pas
  6. Linking shorts
  7. 28 lines compiled, 0.1 sec
  8.  
  9. $ ./shorts
  10. 101000 tkSString
  11. 101000 tkSString
  12. 256000 tkSString
  13. 8000 tkAString
  14.  

And yeah, I know the 101 is 100 + 1 for the size.

So a sized string is a shortstring, even if string is ansistring...

But shortstring is always 256 bytes unless sized.

One can't do a sized string over 255...

Code: Pascal  [Select][+][-]
  1. program shorts;
  2.  
  3. {$mode objfpc}
  4. {$H+}
  5.  
  6. procedure main;
  7. Type
  8. {$H-}
  9.   TName0 = string[100];
  10. {$H+}
  11.   TName1 = string[1000];
  12.   TName2 = shortstring;
  13.   TName3 = string;
  14. var
  15.   Names0: array[1..1000] of TName0;
  16.   Names1: array[1..1000] of TName1;
  17.   Names2: array[1..1000] of TName2;
  18.   Names3: array[1..1000] of TName3;
  19. begin
  20.   writeln(SizeOf(Names0), ' ', GetTypeKind(TName0));
  21.   writeln(SizeOf(Names1), ' ', GetTypeKind(TName1));
  22.   writeln(SizeOf(Names2), ' ', GetTypeKind(TName2));
  23.   writeln(SizeOf(Names3), ' ', GetTypeKind(TName3));
  24. end;
  25.  
  26. begin
  27.   main;
  28. end.
  29.  

Code: Text  [Select][+][-]
  1. $ fpc shorts
  2. Free Pascal Compiler version 3.2.2 [2022/08/17] for x86_64
  3. Copyright (c) 1993-2021 by Florian Klaempfl and others
  4. Target OS: Linux for x86-64
  5. Compiling shorts.pas
  6. shorts.pas(11,23) Error: string length must be a value from 1 to 255
  7. shorts.pas(29) Fatal: There were 1 errors compiling module, stopping
  8. Fatal: Compilation aborted
  9. Error: /usr/bin/ppcx64 returned an error exitcode
  10.  

And to verify that the string type switching is working...

Code: Pascal  [Select][+][-]
  1. program shorts;
  2.  
  3. {$mode objfpc}
  4. {$H+}
  5.  
  6. procedure main;
  7. Type
  8. {$H-}
  9.   TName0 = string[100];
  10. {$H+}
  11.   TName1 = string[100];
  12.   TName2 = shortstring;
  13.   TName3 = string;
  14. {$H-}
  15.   TName4 = string;
  16. {$H+}
  17. var
  18.   Names0: array[1..1000] of TName0;
  19.   Names1: array[1..1000] of TName1;
  20.   Names2: array[1..1000] of TName2;
  21.   Names3: array[1..1000] of TName3;
  22.   Names4: array[1..1000] of TName4;
  23. begin
  24.   writeln(SizeOf(Names0), ' ', GetTypeKind(TName0));
  25.   writeln(SizeOf(Names1), ' ', GetTypeKind(TName1));
  26.   writeln(SizeOf(Names2), ' ', GetTypeKind(TName2));
  27.   writeln(SizeOf(Names3), ' ', GetTypeKind(TName3));
  28.   writeln(SizeOf(Names4), ' ', GetTypeKind(TName4));
  29. end;
  30.  
  31. begin
  32.   main;
  33. end.
  34.  

Code: Text  [Select][+][-]
  1. $ fpc shorts
  2. Free Pascal Compiler version 3.2.2 [2022/08/17] for x86_64
  3. Copyright (c) 1993-2021 by Florian Klaempfl and others
  4. Target OS: Linux for x86-64
  5. Compiling shorts.pas
  6. Linking shorts
  7. 33 lines compiled, 0.1 sec
  8.  
  9. $ ./shorts
  10. 101000 tkSString
  11. 101000 tkSString
  12. 256000 tkSString
  13. 8000 tkAString
  14. 256000 tkSString
  15.  

The thing is, one can still store UTF-8 in short strings, so the size limit can be confusing.

Code: Pascal  [Select][+][-]
  1. program shorts8;
  2.  
  3. {$mode objfpc}
  4. {$H+}
  5.  
  6. var
  7.   s8 : string[10];
  8.  
  9. begin
  10.   s8 := '123';
  11.   writeln(s8, ' ', length(s8));
  12.  
  13.   s8 := 'íéä123';
  14.   writeln(s8, ' ', length(s8));
  15.  
  16.   s8 := 'íéä123ó';
  17.   writeln(s8, ' ', length(s8));
  18. end.
  19.  

Code: Text  [Select][+][-]
  1. $ fpc shorts8
  2. Free Pascal Compiler version 3.2.2 [2022/08/17] for x86_64
  3. Copyright (c) 1993-2021 by Florian Klaempfl and others
  4. Target OS: Linux for x86-64
  5. Compiling shorts8.pas
  6. shorts8.pas(16,6) Warning: String literal has more characters than short string length
  7. Linking shorts8
  8. 18 lines compiled, 0.1 sec
  9. 1 warning(s) issued
  10.  
  11. $ ./shorts8
  12. 123 3
  13. íéä123 9
  14. íéä123�10
  15.  

« Last Edit: December 30, 2022, 07:29:54 am by Bogen85 »

 

TinyPortal © 2005-2018