1

Ok, so In one of my projects, im trying to remake the way it stores certain variables, i have a simple array of objects. The class that these objects refer to is:

class Blocks
{
    public byte type = Block.Empty;
    byte lastblock = Block.Zero;
}

I plan to add more to it, in the current class type is what the objects current value is, and lastblock is what the object used to be.

i create the array like this:

blocks = new Blocks[width * depth * length];

for (int i = 0; i < ((width * length) * depth); i++)
{
    blocks[i] = new Blocks();
}

The problem that I'm having, is that when i create an array that's very large (512,512,512 or 134217728 for those of you whom don't like math), the array gets huge, upwards of 3.5 gigs.

The old way that this array was created was a little simpler, but much harder to expand, it simple created an array of bytes representing the current block, and seems to only use 2 megs of ram when its actually loaded (which i dont get since 134217728 bytes should be around 134 megs... right?). It just baffles me that object references could generate that much more ram usage.

Am i doing something wrong, or should i just go back to the old way it was done? I would like the object references simply because it means that all my variables are in 1 array rather then in 4 separate arrays, which seems as though it would be better for the system.

EDIT:

After working through a few different ways of doing this, i found that changing

class Blocks

to

struct Blocks

Made a world of difference, Thank you community for that welcome tip for future use, unfortunately i didn't want to just add two bytes in a struct and call it done, that was where i stopped to test my design and wound up with the original problem. After adding anything else to the struct (anything else that's on my list at least, meaning either a player object reference or a player name string.) It causes an out-of-memory exception that means I'm not going to be able to use this system after all.

But the knowledge of how to do it will be quite helpful in the future. For that, thank you again.

RyanTimmons91
  • 460
  • 1
  • 5
  • 17
  • How do you get you mem usage numbers? They seem way of. – H H Feb 15 '11 at 08:49
  • 2
    Is there any reason Blocks is a reference type? It seems like a good candidate for being a struct to me (Immutable or not). – MattDavey Feb 15 '11 at 08:51
  • 2
    *It just baffles me that object references could generate that much more ram usage.* - Well, there is an associated overhead when using a reference type. It's not much, but when you are talking about hundreds of millions of objects, well, it adds up. Structs are the size of their members. – Ed S. Feb 15 '11 at 08:53
  • I used taskmanager (yes i know its not the best thing ever) and the program usage jumped from 20 megs to ~3.6 gigs when i ran the origional code (each object had a few move variables) - Ill take a look at structs :D – RyanTimmons91 Feb 15 '11 at 09:37
  • With regards to your update: Why would every single block need a reference to the player name, or worse still, the player object embedded in their data structure? That's massive overkill! – MattDavey Feb 15 '11 at 11:52
  • For history, a great many people like to screw up what others have done, and its best to keep a history of whom was the last person that edited a block, currently we use a database, and each time a change is made, we save it, and then pull it again when a query needs to be made. – RyanTimmons91 Feb 15 '11 at 14:15

6 Answers6

4

Here's what I tried with a structure type instead of a class type:

public struct Block
{
    public byte _current;
    public byte _last;
}

public static void RunSnippet()
{
    Block[] blocks = new Block[512 * 512 * 512];

    for (int i = 0; i < ((512 * 512) * 512); i++)
    {
        blocks[i] = new Block();
    }
}

The snippet ran almost instantly and ate around 267 Mb of RAM.

So give struct a try if that's possible.

tzup
  • 3,566
  • 3
  • 26
  • 34
  • 2
    There's no need to call 'new Block()' since they're value types and will already be zero'd to their default value. Perhaps that explains why your memory usage is exactly twice what OP was ideally expecting? Since essentially you're creating each block twice... – MattDavey Feb 15 '11 at 09:01
  • @MattDavey In the for loop, replacing the new Block() statement with statements initializing the structure fields (with 0 values) still yeilds 267MB of RAM. The actual allocation of memory is done in the for loop, and it is NOT doing anything twice, but only what it is told, ie create this many structures and init to 0. – tzup Feb 15 '11 at 09:18
  • You're right, 512*512*512*2 is ~267MB, OP was wrong with his original estimate. However I'd like to clarify one thing - the actual memory allocation is not done in the for loop, it's done when you instantiate the array. – MattDavey Feb 15 '11 at 09:28
  • @MattDavey Actually commenting the for loop results in about 6Mb mem allocation which means that the compiler only allocates enough memory to hold a pointer to an array of type Block. (so mem consumption IS in the for loop) – tzup Feb 15 '11 at 09:32
  • When i did my original count, i forgot to double it :P (for 2 bytes), but yes, the struct works WORLDS better at memory usage, and should provide the functionality i need. I forgot to even try structs (having only read of them before and not needing to create one myself) – RyanTimmons91 Feb 15 '11 at 09:56
  • adding PDB editor; and DateTime timestamp; to my struct cause an out-of-memory exception and so does adding string editor; string timestamp; so apparently the only things i would be able to do with my struct are current and last block. Which makes it useless to what im trying to do, likely because of the sizes of those objects. Im going to go ahead and stick with the old way i guess. But i thank you guys for helping me out :D – RyanTimmons91 Feb 15 '11 at 10:18
1

You can use List class to manage unlimited number of objects. Please take a look at the link that I provided. You can add infinite (well, theoretically) number of objects into a list.

Using lists, you can easily access any item by their index. It also has methods to search, sort and manipulate objects contained in it.

If you use list, your code will look somewhat like below -

List<Blocks> blocks = new List<Blocks>();

for (int i = 0; i < ((width * length) * depth); i++)  // The amount of items that you want to add
{
    Blocks b = new Blocks();
    blocks.Add(b);
}

You can access every item in this list as follows -

foreach(Blocks b in blocks)
{
    // You have the object, do whatever you want
}

You can find any particular index of an object contained in the list. See this method example.

So using a list, you will be able to easily manage a large number of objects in an uniform way.

To learn more, go here.

MD Sayem Ahmed
  • 28,628
  • 27
  • 111
  • 178
  • I use lists quite often, but the size of the group is always fixed, based on the size of the map in question, and from what i have read, its better to use an array if you don't *need* the functionality that comes from a list. – RyanTimmons91 Feb 15 '11 at 09:43
0

Read this: Object Overhead: The Hidden .NET Memory Allocation Cost.

The total cost of an object of yours is like 16 bytes (on a 32 bits system). (8 bytes for "header", 4 bytes for your fields, 4 for the reference) And 512 * 512 * 512 * 16 = 2.1gb :-)

But you are probably on a 64 bits system, so it's 16 + 8 + 8 = 28, so 4.29gb.

xanatos
  • 109,618
  • 12
  • 197
  • 280
0

You should consider using "struct" instead of "class".

http://msdn.microsoft.com/en-us/library/ah19swz4(v=vs.71).aspx

"The struct type is suitable for representing lightweight objects such as Point, Rectangle, and Color. Although it is possible to represent a point as a class, a struct is more efficient in some scenarios. For example, if you declare an array of 1000 Point objects, you will allocate additional memory for referencing each object. In this case, the struct is less expensive."

Please publish your results if you try so.

Eden
  • 3,696
  • 2
  • 24
  • 24
0

When you create the array, you also instantiate a Blocks on each cell. Do you really need to do that?

blocks[i] = new Blocks();

When you don't instantiate a Blocks, you'd just have an array of empty references. In the access code you could check for null and return a default value. Something along these lines:

if(blocks[i,j] == null)  return new  Blocks();
else return blocks[i,j];

When writing also check if there is one, if not, create it first. That should save a lot of memory.

Using jagged arrays or nested lists should also help a lot.

Regards GJ

gjvdkamp
  • 9,929
  • 3
  • 38
  • 46
  • If i done instantiate the blocks i run into errors when i load the files that fill the blocks, at all times the blocks have to have a value (even if the value is 0, which is less then half of the time) – RyanTimmons91 Feb 15 '11 at 09:40
  • I went ahead and changed the code a little, to see if it would work without the line, and it does, but with the same results, i might be able to rework it so that if it doesn't have a value, it returns 0 for empty, but it would likely mean reworking the entire system, ill leave that line out so i have the availability of it, thank you. – RyanTimmons91 Feb 15 '11 at 09:50
  • THis all depends on how sparse the matrices are. Less than half the time could still save considerable memory, but you'd have to change the code that accesses the array, or better encapsulate the array in a class. Depending on how many unique values the blocks can have you may also want to look at the flyweight pattern. If there are a lot of duplicates where the blocks have the same value you wouldn;t have to create seperate instances, muliple cells in the array could point to the same instance. This would require some plumbing code to keep this organized. – gjvdkamp Feb 15 '11 at 09:58
0

Structs are the way forward here, and would open up the possibility of optimising with unsafe code/pointer arithmetic

struct Block
{
    byte Current;
    byte Last;
}

Block[] blocks = new Block[512 * 512 * 512];

unsafe
{
    Block* currentBlock = &blocks;

    for (int i = 0; i < (512 * 512) * 512; i++)
    {
        currentBlock->Current = 0xff;
        currentBlock->Last = 0x00;

        currentBlock++;
    }
}

Of course someone will come along and say mutable structs are evil! (Only if you dont know how to use them)

MattDavey
  • 8,897
  • 3
  • 31
  • 54
  • i have never used "unsafe" code, and have no idea where and why it would be applicable, it doesn't seem like a good idea though, just from the name. but a lot of people have suggested structs, i will look into it. – RyanTimmons91 Feb 15 '11 at 09:42
  • This is bad advice. Not just bacuse of the mutable structs. – H H Feb 15 '11 at 10:37
  • Then why henk? Please elaborate. I wasn't saying he HAD to do it this way, but speed seems like an issue for him and this would be the fastest way to do it. – MattDavey Feb 15 '11 at 11:50
  • Speed isn't really an issue, unless the person running the server has a slowish CPU, I wanted to change the structure of the program to increase speed and ram usage yes, but it doesn't mean I want to go crazy in doing so. – RyanTimmons91 Feb 15 '11 at 14:17
  • Fair enough, the pros and cons of unsafe code and immutable structs are far outside the scope of this question :) – MattDavey Feb 15 '11 at 14:33