As you have seen below, the time taken and GC Alloc is too much. Previously, I had my function at the ‘void start()’ and it was worse. So I made a coroutine for loading my datas to reduce the waiting time. May i know how can this be further improved?
We cannot see all of the source for that method. Sign unseen, my advice would be to refactor the hell out of it. Repeatedly extract method so you can get more granular feedback on which part of your algorithm is causing the problem.
You probably have to refactor your reading method.
I mean, 1.26 GB or garbage? What are you doing there?
And 2700 calls of FindObjectsOfType? What else is in the list?
Looks like you need to replace / improve a lot of things in order to fasten that up.
Also, consider using more yields, that would not improve the overall loading time, but allows you to spread the overhead even more to keep the application responsive. I’d suggest that because you’ve got a hell of overhead for that single frame.
I have thousands of objects in the game and each has their own info/data to it. So that’s probably where it came from.
using UnityEngine;
using System.Collections;
using System.IO;
public class scheduleParser : MonoBehaviour
{
// the internal file name (private)
private string fileToParse = "";
// some public variables, to configure this script
public string filePath = "filePath";
public string fileName = "fileName";
public string fileExtension = "txt";
public int headersLineNumber = 0;
public int valuesFromLine = 1;
// Use this for initialization
void Start()
{
StartCoroutine(coroutineA());
}
IEnumerator coroutineA()
{
yield return null;
fileToParse = filePath;
fileToParse = Path.Combine(fileToParse, fileName);
fileToParse = fileToParse + "." + fileExtension;
FileInfo theSourceFile = null;
TextReader reader = null; // NOTE: TextReader, superclass of StreamReader and StringReader
// Read from plain text file if it exists
theSourceFile = new FileInfo(Path.Combine(Application.dataPath, fileToParse));
if (theSourceFile != null && theSourceFile.Exists)
{
reader = theSourceFile.OpenText(); // returns StreamReader
//Debug.Log("Created Stream Reader for <b>" + fileToParse + "</b> (in Datapath)");
}
if (reader == null)
{
//Debug.Log(fileName + " not found or not readable");
}
else
{
// Read each line from the file/resource
bool goOn = true;
int lineCounter = 0;
string[] headers = new string[0];
while (goOn)
{
string buf = reader.ReadLine();
if (buf == null)
{
goOn = false;
yield return null;
}
else
{
//Debug.Log("Current Line : " + lineCounter + " : " + buf);
string[] values;
if (lineCounter == headersLineNumber)
{
headers = buf.Split('\t');
//Debug.Log("--> Found header " + headers[0]);
}
if (lineCounter >= valuesFromLine)
{
// now we get a , ; or <tab>-delimited string with data
// ID <tab> Library Part Name <tab> Element Classification <tab> Width <tab> Height
// ID <tab> ... <tab>
values = buf.Split('\t');
string ID = values[0];
//Debug.Log("--> Found values " + values[0]);
// Find object with this name
GameObject go;
// Attempt 1 - Assume the ID equals the full name
// This works for the ArchiCAD file as the ID is used as Object Name
go = GameObject.Find(ID);
// Attempt 2 - Assume the ID is part of the full name
// For the Revit schedule, the ID is part of the Object Name e.g. "Family Type [12345]"
if (go == null)
{
foreach (var gameObj in
FindObjectsOfType(typeof(GameObject)) as GameObject[])
{
if (gameObj.name.Contains(ID.ToString()))
{
go = gameObj;
}
}
}
if (go != null)
{
//Debug.Log(" Found ID : " + ID);
go.AddComponent<metadata>();
metadata meta = go.GetComponent<metadata>();
meta.values = values;
meta.keys = headers;
}
else
{
//Debug.Log(" No objects found with ID: " + ID);
}
}
lineCounter++;
}
}
}
}
}
/*using UnityEngine;
using System.Collections;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
public class scheduleParser : MonoBehaviour
{
// the internal file name (private)
private string fileToParse = "";
// some public variables, to configure this script
public string filePath = "filePath";
public string fileName = "fileName";
public string fileExtension = "txt";
public int headersLineNumber = 0;
public int valuesFromLine = 1;
// Use this for initialization
void Start()
{
fileToParse = filePath;
fileToParse = Path.Combine(fileToParse, fileName);
fileToParse = fileToParse + "." + fileExtension;
FileInfo theSourceFile = null;
TextReader reader = null; // NOTE: TextReader, superclass of StreamReader and StringReader
// Read from plain text file if it exists
theSourceFile = new FileInfo(Path.Combine(Application.dataPath, fileToParse));
if (theSourceFile != null && theSourceFile.Exists)
{
reader = theSourceFile.OpenText(); // returns StreamReader
//Debug.Log("Created Stream Reader for <b>" + fileToParse + "</b> (in Datapath)");
}
if (reader == null)
{
//Debug.Log(fileName + " not found or not readable");
}
else
{
// Read each line from the file/resource
bool goOn = true;
int lineCounter = 0;
string[] headers = new string[0];
while (goOn)
{
string buf = reader.ReadLine();
if (buf == null)
{
goOn = false;
return;
}
else
{
//Debug.Log("Current Line : " + lineCounter + " : " + buf);
string[] values;
if (lineCounter == headersLineNumber)
{
headers = buf.Split('\t');
//Debug.Log("--> Found header " + headers[0]);
}
if (lineCounter >= valuesFromLine)
{
// now we get a , ; or <tab>-delimited string with data
// ID <tab> Library Part Name <tab> Element Classification <tab> Width <tab> Height
// ID <tab> ... <tab>
values = buf.Split('\t');
string ID = values[0];
//Debug.Log("--> Found values " + values[0]);
// Find object with this name
GameObject go;
// Attempt 1 - Assume the ID equals the full name
// This works for the ArchiCAD file as the ID is used as Object Name
go = GameObject.Find(ID);
// Attempt 2 - Assume the ID is part of the full name
// For the Revit schedule, the ID is part of the Object Name e.g. "Family Type [12345]"
if (go == null)
{
foreach (var gameObj in
FindObjectsOfType(typeof(GameObject)) as GameObject[])
{
if (gameObj.name.Contains(ID.ToString()))
{
go = gameObj;
}
}
}
if (go != null)
{
//Debug.Log(" Found ID : " + ID);
go.AddComponent<metadata>();
metadata meta = go.GetComponent<metadata>();
meta.values = values;
meta.keys = headers;
}
else
{
//Debug.Log(" No objects found with ID: " + ID);
}
}
lineCounter++;
}
}
}
}
}*/
That can help. Especially if you have 2700 objects, therefore you iterate the while loop for 2700 times. You can even optimize that if you collect a list of all objects and remove all the objects which you already found, asuming that each object is individual.
As you don’t create new game objects here call FindObjectsOfType before you start the while loop.
And if each object is individual you could do this and remove the object from the cache as it is already found. Also break your iteration at Line 99.
List<GameObject> cachedObjects = (FindObjectsOfType(typeof(GameObject)) as GameObject[]).ToList();
while (...)
{
//...
var go = GameObject.Find(ID);
if (go == null)
{
foreach (var gameObj in cachedObjects)
{
if (gameObj.name.Contains(ID.ToString()))
{
go = gameObj;
break;
}
}
}
if (go != null)
{
//Debug.Log(" Found ID : " + ID);
//Remove object if found
cachedObjects.Remove(go);
go.AddComponent<metadata>();
metadata meta = go.GetComponent<metadata>();
meta.values = values;
meta.keys = headers;
}
}
The code you posted does not benefit alot from the nature of coroutines, because you don’t yield within your logic. Only once at the beginning and once (which is superfluous) when you end the while-loop.
In order to end the loop, just set the flag to false, no yield needed. It’ll finish immediately without waiting another frame and that should be fine, as it causes literally no overhead.
Instead, you’d benefit from yielding in between the expensive operations. Of course you don’t wanna keep resources locked for unecessary amounts of times, but in this case it’ll be for sure an advantage.
Like already stated, move expensive operations (if they always return the same results anyway) out of loops. You’d save the overhead of FindObjectsOfType and also avoid allocating thousands of arrays and “throw” them away in each step of the iteration. This should favor performance and less GC.
To further improve lookups in this particular situation (as you have thousands), you can try find all objects once, throw them into a dictionary and use the contains method on the dictionary.
This may turn out to be faster, as a search in an unordered list of N elements has a complexity of O(N), while a lookup in a dictionary has O(1). That is, if you use the list and happen to search for the last item in the list, you’d run 2700 comparison, whereas the dictionary can do an instant-lookup.
I’d try to split up the logic into several methods that do their own particular task. You’d not only benefit from clearer structure and other qualities, but also get a much ‘clearer’ overview in the profiler, especially when deep-profiling this.