Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 13 Next capture
2021 2022 2023
1 capture
13 Jun 22 - 13 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
1
Question by Ackior · Apr 20, 2015 at 03:38 PM · c#arraydictionary

Multidimensional array vs dictionary performance

I am building a voxel based pathfinding system. One of the important parts is being able to mark blocks as being clear to walk on.

I made an array to hold the bool walkable using the x,y,z integer coordinates as a key:

 bool[,,] worldWalkable = new bool[1000,1000,1000];

The thing is, there tends to be a lot of wasted space(non walkable blocks) and it uses a ton of memory as the map size goes up.

I thought a dictionary might be a better choice, so I made this:

 Dictionary<Vector3Int, bool> worldTrav = new Dictionary<Vector3Int, bool>();

I only store true values to save memory. Everything else is assumed false.

Vector3Int is a struct that holds the x,y,z integers so I can use it as a key for the dictionary.

Like this:

 using UnityEngine;
 using System.Collections;
 
 //makes a Vector3 of integers
 public struct Vector3Int
 {
     public int x,y,z;
     
     public Vector3Int(int x, int y, int z)
     {
         this.x =x;
         this.y=y;
         this.z=z;
     }
             
     //checks for equality
     public static bool operator ==(Vector3Int a, Vector3Int b)
     {
         return a.x==b.x && a.y ==b.y && a.z ==b.z;
     }
 
     public override bool Equals(object obj)
     {
         return obj is Vector3Int && this == (Vector3Int)obj;
     }
 
     public override int GetHashCode ()
     {
         return x.GetHashCode() ^ y.GetHashCode() ^ z.GetHashCode();
     }
 
     
     public static bool operator !=(Vector3Int a, Vector3Int b)
     {
         return !(a==b);
     }
 }

The thing is, when I use the dictionary, it is slooooow. Even though my array has about a billion indexes and the dictionary has 7000, the dictionary access with Vector3Int as key is much slower. Like 30-100 times slower.

Is there a better way to contain the boolean information? I will be loading and unloading chunks, so a dictionary seems much more flexible. The lookup needs to be much faster than the dictionary seems to be.

Thanks.

Edit: Turns out that I needed to implement IEquatable. Good link on how to do it is here.

After implementing, time to calculate dropped from 225+ms to 2-8ms. The speed increase appears to be from preventing boxing/unboxing while using the dictionary.

New code:

 using UnityEngine;
 using System.Collections;
 using System;
 using System.Collections.Generic;
 
 //makes a Vector3 of integers
 public struct Vector3Int : IEquatable<Vector3Int>
 {
     public int x,y,z;
     
     public Vector3Int(int x, int y, int z)
     {
         this.x =x;
         this.y=y;
         this.z=z;
     }
     
     public static Vector3Int operator +(Vector3Int a, Vector3Int b)
     {
         Vector3Int temp = new Vector3Int();
         temp.x = a.x +b.x;
         temp.y = a.y +b.y;
         temp.z = a.z +b.z;
         return temp;
     }
 
     public bool Equals(Vector3Int other)
     {
         return other.x==this.x && other.y==this.y && other.z==this.z;
     }
     
     public static bool operator ==(Vector3Int a, Vector3Int b)
     {
         return a.Equals(b);
     }
 
     public override bool Equals(object obj)
     {
         if(obj==null || !(obj is Vector3Int))
             return false;
 
         return Equals((Vector3Int)obj);
     }
 
     public override int GetHashCode ()
     {
         return x ^ (y<<10) ^ (z<<10);
     }
 
     public override string ToString ()
     {
         return string.Format ("("+x+", "+y+", "+z+")");
     }
     
     public static bool operator !=(Vector3Int a, Vector3Int b)
     {
         return !(a==b);
     }
 }

Comment
Add comment · Show 3
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image maccabbe · Apr 20, 2015 at 04:53 PM 1
Share

Dictionaries are basically hashTables so you should override GetHashCode() in Vector3I.

avatar image Ackior · Apr 20, 2015 at 05:06 PM 0
Share

Sorry, had old code for Vector3Int; edited for new code

avatar image Bunny83 · Apr 21, 2015 at 12:10 PM 0
Share

@Ackior: The way you calculate the Hashcode is actually very bad. It has tons of collisions with other values in your dictionary. Ins$$anonymous$$d of "xor" it would be way better to just multiply the 3 hashes together:

 return x.GetHashCode() * y.GetHashCode() * z.GetHashCode();

A better implementation would be something like that (found somewhere on the net). I had to convert the int to an uint to avoid automatic type conversion into long.

 uint seed = (uint)x.GetHashCode() + 0x9e3779b9;
 seed ^= (uint)y.GetHashCode() + 0x9e3779b9 + (seed << 6) + (seed >> 2);
 seed ^= (uint)z.GetHashCode() + 0x9e3779b9 + (seed << 6) + (seed >> 2);
 return (int)seed;

However i'm not sure if a better hash will give you a seed boost. It always depends on the implementation of the Dict. -> benchmarking is the only way to find out which is faster in which cases ^^. It's hard to impossible to predict how a certain method will perform. It depends on so many things (CPU cache size for example).

2 Replies

· Add your reply
  • Sort: 
avatar image
3

Answer by JinJin · Apr 21, 2015 at 11:34 AM

When accessing the dictionary, you are searching for one object out of 7000

when accessing the array, there is no searching at all:

  • from a 3 dimensional array you select one 2 dimensional array from 1000 arrays

  • from the selected 2 dimensional array you select a 1 dimensional array from 1000 arrays

  • you select one object from 1000 objects in the 1 dimensional array

getting an element from an array is very, VERY fast - much faster than getting a hashcode and accessing the dictionary

if your main problem is memory usage, I would suggest splitting your 3D array

make a tree with sub-trees, like when you make a "k-d tree"

 public class SmallNode
 {
    public bool[,,] walkable = new bool[10,10,10];
 }
 
 public class AverageNode
 {
    public SmallNode[,,] small = new SmallNode[10,10,10];
 }
 
 public class LargeNode
 {
    public AverageNode[,,] average = new AverageNode[10,10,10];
 }
 
 public class MyGame
 {
    LargeNode large = new LargeNode();
 
    void Setup()
    {
       // initialize large - some of the elements in average[,,] can be "null", saving memory
 
       // initialize all elements in average - some of the elements in small[,,] can be "null", saving memory
 
       // initialize all elements in small - here you must assign all bool values
    }
 
    bool isWorldWalkable(int x, int y, int z)
    {
       // change x from [0,1000] to [0,10]
       int averagex = x / 100;
       int averagey = y / 100;
       int averagez = z / 100;
 
       AverageNode average = large.average[averagex,averagey,averagez];
 
       if(average != null)
       {
          // get the x interval between 0-100, 100-200, etc...
          int smallx = (x - averagex * 100) / 10;
          int smally = (y - averagey * 100) / 10;
          int smallz = (z - averagez * 100) / 10;
 
          SmallNode small = average.small[smallx,smally,smallz];
 
          if(small != null)
          {
             int walkablex = (x - averagex * 100 - smallx * 10);
             int walkabley = (y - averagey * 100 - smally * 10);
             int walkablez = (z - averagez * 100 - smallz * 10);
 
             return small.walkable[walkablex,walkabley,walkablez];
          }
       }
 
       return false;
    }
 }
Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Bunny83 · Apr 21, 2015 at 12:33 PM 0
Share

Just want to add here that a multidimensional array access doesn't involve those 3 steps you've mentioned. The system directly calculates the index of the element, out of the 3 indices. Internally a multi-dim-array is more like a single flattened array.

Also your chunking of world fragments introduce a similar problem the Dictionary has but in a more complicated way ^^. If you have a good hash a Dictionary shouldn't have to search many elements as the dictionary does this "chunking" internally. Basically it groups certain hashes into "buckets" and each bucket is an ordinary array. It just caluclates the index for the correct bucket and searches that bucket for the required element.

The average case (with a good hash) for 7000 elements is that each bucket contains about 27 elements (256 buckets).

Nonetheless if it's going to be a huge world (maybe almost infinite like $$anonymous$$inecraft ^^) chunking is the only viable way of storing / handeling that amount of data.

$$anonymous$$eep in $$anonymous$$d that chunk sizes which uses a power of 2 are way more cpu friendly since you can simply right / left shift the index or bit-mask the lower index part:

 int index;
 int chunkIndex = index >> 4; // chunksize=16 (2 to the power of 4 == 16)
 int subIndex = index & (1<<4-1); // use 15 as bit mask 0x0F or 00001111b

For a chunksize of 32 you would use 5 ins$$anonymous$$d of 4 and so on...

avatar image
0

Answer by mezzostatic · Jan 10, 2017 at 07:32 AM

I'd agree, an array of 1bn booleans is 1gb, 2.47bn is the upper limit for a single array, it's instant lookup of memory address, whereas other methods are a loop through thousands of array entries to compare one of them.

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

5 People are following this question.

avatar image avatar image avatar image avatar image avatar image

Related Questions

Development of the shop? 1 Answer

Assigning scriptable objects to dictionary through array? 0 Answers

How to send/copy array from dictionary to class and further to list of class 0 Answers

How to check if the string is on a text file 0 Answers

Array member treated as null even though it is not. 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges