这个必须转。希望大家都看看啊。。
This technical entry will discuss how we optimize the memory footprint of our Unity-based Augmented Reality apps for iOS. We frequently use large models (500k+ vertices) exported from CAD programs like SolidWorks, and it can be tricky to avoid memory related crashes on the older iOS platforms. This is becoming less of an issue with more recent hardware, but we're still supporting the iPhone 4 and iPad 2, which both only have 512 mb of memory.
MEASURING MEMORY USE IN IOS AND XCODE
One of the hardest parts of starting to debug memory related crashes in iOS is figuring out where to look to measure them. We typically use Instruments from XCode (XCode top menu --> Open Developer Tool --> Instruments) to monitor a real hardware device running in debug mode. But within Instruments, there are a million different measures of memory usage, and every one of them seems to differ from all the others. You've got real memory, virtual memory, physical memory active, physical memory free, physical memory wired, physical memory used, leaks, allocations and back in XCode 5 itself, there's a tab called "Debug Naviator" that shows memory, CPU and FPS.
Running one of our apps, here's a collection of measurements:
Running one of our apps, here's a collection of measurements:
![Picture](http://www.equipcodes.com/uploads/1/2/0/5/12051540/365117.png?463)
Running Instruments "Allocations"
Live bytes: 201mb
Overal bytes: 547mb
Live bytes: 201mb
Overal bytes: 547mb
![Picture](http://www.equipcodes.com/uploads/1/2/0/5/12051540/4154954.png?425)
Running Instruments "Activity Monitor"
from the trace highlights pie graph, Real memory: 240 mb (and after clicking on the "Activity Monitor" row and sorting by real memory)
from the trace highlights pie graph, Real memory: 240 mb (and after clicking on the "Activity Monitor" row and sorting by real memory)
virtual memory: 254 mb
If you click on the timeline, it tells you details on the 4 physical memories:
Physical Memory Free: 35mb
Physical Memory Active: 183mb
Physical Memory Wired: 250mb
Physical Memory Used: 538mb
If you click on the timeline, it tells you details on the 4 physical memories:
Physical Memory Free: 35mb
Physical Memory Active: 183mb
Physical Memory Wired: 250mb
Physical Memory Used: 538mb
And finally, from the "Debug Navigator in XCode 5"
"Memory Utilized": 216mb
"Memory Utilized": 216mb
Overall, we have 9 different measures of memory usage. Which one matters? According to the
Instruments documentation, the physical memories are all non-process specific, relating to the OS and other apps. Even the free memory available is not a good indicator; in the chart above, it says 35 mb are free, but our app has been hundreds of mb larger and has still run fine.
In the end, we settled on using the "Real Memory" measure from the Instruments Activity Monitor for the specific process. You might also be able to use the memory gauge from the Debug Navigator within XCode, but this always reported lower memory use. I believe they key is watching one of them and seeing when you get memory warnings.
In the end, we settled on using the "Real Memory" measure from the Instruments Activity Monitor for the specific process. You might also be able to use the memory gauge from the Debug Navigator within XCode, but this always reported lower memory use. I believe they key is watching one of them and seeing when you get memory warnings.
HOW MUCH MEMORY IS AVAILABLE ON IOS DEVICES?
According to the
Session 242 - iOS App Performance: Memory provided by Apple, the new iPad has a hard limit of 650 mb, but for the iPad 2 and iPhone 4, memory warnings start popping up around 170 mb (real memory in Instruments Activity Monitor for our specific process). We've been able to successfully run apps in the 220 mb range, but there's no hard limit. In the above Apple video, they say that allocating memory too quickly can cause a crash since it doesn't give the OS time to free memory from other apps, so memory availability is not only a function of raw numbers, but also how you ask for memory. We had crashes happening while trying to unload and then immediately load a new model. By putting a delay between the two events, the crashes disappeared.
In the Unity forums when people have complained about crashes, I've seen Unity ask for example projects that run at under 200 mb. The real answer is that you have to test on real devices to be sure.
In the Unity forums when people have complained about crashes, I've seen Unity ask for example projects that run at under 200 mb. The real answer is that you have to test on real devices to be sure.
FINDING MEMORY USE CULPRITS IN UNITY
Unfortunately, there's no way that we know of to see in Unity how much any given object or chunk of code will use. We typically would do A-B trials with and without objects to see what impact they have on the Real memory use. Here's a list of what we've learned:
Turning an object in the view hierarchy inactive with SetActive(false) does not free that memory. I've noticed that sometimes an object that's inactive at the start of the app will only consume memory when it becomes active, but once that memory gets used, it won't go away just by setting it inactive again.
One tool we typically use to start hunting down large memory objects is the editor log after doing a build. To open it, right click on the console tab and select "Open Editor Log."
Turning an object in the view hierarchy inactive with SetActive(false) does not free that memory. I've noticed that sometimes an object that's inactive at the start of the app will only consume memory when it becomes active, but once that memory gets used, it won't go away just by setting it inactive again.
One tool we typically use to start hunting down large memory objects is the editor log after doing a build. To open it, right click on the console tab and select "Open Editor Log."
After a build, search through the log for the size-sorted list of "Used Assets" (use Ctrl-F to find it, the log is huge). You'll see the assets that actually get compiled into the build sorted by size. Note this is not memory use, but rather the size of the object in the binary. But it's a good place to find memory hogs that you didn't even know were in your scene.
In my log you can see that I have some fairly large fbx models that are taking up most of the space, but right after those are several images. A single full screen RGBA (rgb+alpha) png that covers a retina iPad display will use 32 megs. Just a few of those could easily kill your available memory if you're not freeing the space (more on that later).
COMPRESSING MODELS REDUCES BINARY SIZE (NOT IN-GAME MEMORY!)
One thing you may have noticed in the Inspector when you click on an imported model is an option labeled "Mesh Compression."
This has zero impact on the in-game memory, but it can significantly reduce the the binary size. In our very limited testing with engineering style models, all the compression levels above "off" look the same. We either have this off or at high. If the artifacts were acceptable, we could set it to high, but if they were not, we set it to off. We never noticed differences between low and high compression.
IMAGE COMPRESSION (IOS ONLY SUPPORT PVRCT, NO EFFECTIVE PVRTC COMPRESSION WITHIN UNITY)
If you select a png texture in the project view while your platform is set for iOS, the preview of the image shows some text below with the compression and size. In our experience, this text is not correct. Sometimes it shows the wrong compression or one that's not actually being used, and usually a size that's not right. Bear in mind that iOS only supports PVR compression, so any sizes corresponding to other compression methods are not relevant.
While working on an app that displayed catalog pages like a book, we needed to use large full-screen textures. The internal PVR compression never seemed to work or it looked horrible, so we ended up using a tool called
PVRTexToolGUI. It seems not all PVR compression tools are created equal, as most comments on forums recommend avoiding PVRCT compression on GUI graphics, but this tool worked ok for us. We were able to compress large 2048x2048 pixel images with text and graphics into 2 mb images (binary and in-game memory use) that looked good on retina displays. Be aware that there are many options in the PVRTexToolGUI program, and
only the following worked for us.
3. Select the PVRTC 4bpp (4 bits per pixel) from OpenGL ES 2, very high for quality, and vertical flip at the bottom. No other options worked for us.
4. Inspect the resulting image for defects.
5. Important: to save the PVR, select File --> Save as Legacy. Saving any other way didn't work with Unity for us.
5. Important: to save the PVR, select File --> Save as Legacy. Saving any other way didn't work with Unity for us.
DYNAMICALLY LOADING AND UNLOADING ASSETS IN UNITY
By putting assets in the Assets/Resources folder in the project hierarchy, you can dynamically load and unload them from memory. Unfortunately, this completely freezes the app during loading, so we display a loading graphic before initiating the load. There are also a few gotchas to successfully get this to work. Make sure to watch the real memory in Instruments to make sure memory is really being freed.
To load an object: (c#) (plumbing is a prefab in the Resources folder).
PlumbingModel = (GameObject)Instantiate(Resources.Load("plumbing", typeof(GameObject)));
To then free the object:
GameObject.Destroy(PlumbingModel);
PlumbingModel = null; // not sure if this is necessary, but we check elsewhere in the code for null so we wanted to make sure.
Resources.UnloadUnusedAssets(); // IMPORTANT: memory will not be freed without this line.
When you load models using this method, they show up at the top of the hierarchy. If you need to make one of the loaded models a child of something else, do:
MyLoadedObject.transform.parent = MyParentObject.transform;
Be aware that this could change the position and scale. When first creating the prefab, be sure to drag the object out to the highest level of the hierarchy to make sure its position remains where you want it when the model gets loaded.
You may want to use UnitySendMessage to free up memory when iOS sends a memory warning to your App via applicationDidReceiveMemoryWarning as indicated here.
To load an object: (c#) (plumbing is a prefab in the Resources folder).
PlumbingModel = (GameObject)Instantiate(Resources.Load("plumbing", typeof(GameObject)));
To then free the object:
GameObject.Destroy(PlumbingModel);
PlumbingModel = null; // not sure if this is necessary, but we check elsewhere in the code for null so we wanted to make sure.
Resources.UnloadUnusedAssets(); // IMPORTANT: memory will not be freed without this line.
When you load models using this method, they show up at the top of the hierarchy. If you need to make one of the loaded models a child of something else, do:
MyLoadedObject.transform.parent = MyParentObject.transform;
Be aware that this could change the position and scale. When first creating the prefab, be sure to drag the object out to the highest level of the hierarchy to make sure its position remains where you want it when the model gets loaded.
You may want to use UnitySendMessage to free up memory when iOS sends a memory warning to your App via applicationDidReceiveMemoryWarning as indicated here.
TIPS
Being forced to run tests on real iOS hardware dramatically slows down the iteration process. We needed to test on android and iOS and rapidly make changes, and switching platforms was a horrible drag on the process, taking over 10 minutes every time we swtiched to iOS. I highly recommend
Fast Platform Switch, which makes a cache of the platform files so switching takes less than a minute.
Use Unity's Profiler (Window menu --> Profiler) to tackle memory and performance issues with large models. Unity recommends keeping the vertex count below a few hundred k on mobile platforms, and several million on desktops.
Use Unity's Profiler (Window menu --> Profiler) to tackle memory and performance issues with large models. Unity recommends keeping the vertex count below a few hundred k on mobile platforms, and several million on desktops.
We typically use several cameras and lights, which can easily double the vert count if you forget to turn the cameras and lights off when you don't really need them. This helped us go from 9 FPS to 20 FPS!
Unity has a lot of CPU and graphics optimization tips on their website.
The "Debug Navigator" in XCode is a good place to watch the FPS, memory and CPU, although I have no idea what that memory gauge is showing, as it's always 30% less than the real memory reported in Instruments.
Unity has a lot of CPU and graphics optimization tips on their website.
The "Debug Navigator" in XCode is a good place to watch the FPS, memory and CPU, although I have no idea what that memory gauge is showing, as it's always 30% less than the real memory reported in Instruments.
0 Comments
Your comment will be posted after it is approved.