Stable 60fps in Editor but lag spikes (20fps) in Build

I’m having an issue where the game runs smooth in the editor, but has bad lag spikes in the actual build.

In the editor, it’s a pretty stable 60 or so fps. A dip here and there, but overall looks smooth. In the build, it runs like crap. It has really patchy lag spikes that seem to be happening every couple of seconds. The fps graph shows it going to a max of 388 fps but going down to consistently under 24fps every few seconds.

This is really stumping me for a few reasons;

  1. Why would it be more stable in the editor? I thought it would be the other way around
  2. Nothing is happening every few seconds as far as I know. I disabled everything so it’s just a scene sitting there- no moving parts or AI or what not.

Now I have Vsync on set to every V blank. Is that supposed to cap the framerate to 60fps? Something in the editor seems to be capping it at the 60 fps (hence why it’s stable at it) but the build has a max of 388, minimum of around 20, and so the graph says the average is 88 fps (which sounds good, but when you look around and play, the constant stuttering is too much to take).

Where do I start to get to the bottom of this crap?

For more info, I’m using Unity 2019.1 and have deleted all of the quality profiles except Ultra. This should mean the editor is previewing in the same settings as the build plays in, right?

You should just profile the build. If it’s a dev build, you can attach the Unity Profiler and see what’s slowing things down. That’s probably the only straightforward way to know for sure.

OK yes, maybe that will help get to what is slowing things down (I’ve never done it before but will learn I guess), but I’m really actually wanting to know why it’s different in the editor and the build. What is seemingly capping the FPS in the editor to 60, but not carrying on to cap it in the build? I have a feeling that knowing the answer to that is going to be the key to understanding other things, including any investigation using the Unity Profiler.

Well, the idea behind VSync is that is aligns the framerate with the refresh rate of your monitor. As long as the game runs smoothly enough, and your hardware is good enough, that will usually means it will run at 60FPS on a 60FPS monitor. However, it also means that if things start to slow down, the game will dramatically jump from 60 FPS down to 30 FPS.

VSync isn’t really a tool for improving performance; it can severely worsen performance depending on the situation, since it will result in skipping frames if the hardware can’t quite keep up. Consider that if you’re able too run at 62 FPS, VSync will align that to 60 FPS, and that will seem fine. The “bad” thing about VSync occurs when the scene gets just a little more complex, or your system gets busy temporarily. Lets say things slow down just a little, to the point where you’d now be getting 58 FPS normally. As soon as you dip below 60 FPS with VSync enabled, you won’t drop to 58 FPS. You’ll drop to 30 FPS. At least for some number of frames. VSync literally skips entire frames. I think the next step down from there would be 20 FPS. Anyway, VSync is mainly for avoiding screen tearing. The ironic thing about using it to improve performance is that it only really improves performance for those who don’t need the improvement. For those who do need the performance boost, those on mid-range hardware, VSync will take a game that was getting you ~50FPS and now give you 30 FPS instead. (I may have oversimplified stuff, or gotten it a bit wrong here, but that’s the gist.)

Anyway, to your specific question of why things would behave differently in the build: No idea, but I can tell you that things behaving different in a build is a relatively common thing that I deal with fairly often, and it requires some investigation. There’s just a lot of difference between running in the editor, and running in a build. For example, in the editor, Static isn’t really static, while in a build things are much more highly optimized. Typically, though, you’d expect better performance in a build because of this. I think you just need to profile it, and see what it’s specifically telling you, and tackle it from there.

Also, how exactly are you setting VSync? Just the dropdown in Quality settings? Or do you set it manually by some other means, like QualitySettings.vSyncCount ?

Yeah I know about Vsync originally from playing games. I hate screen tearing, so I generally turn it on to start with for games, but consider that if there are performance issues I could turn it off.

I have set the Vsync in the Unity preferences, not via script. I think I’ll try it to see if I spot any difference at all. I didn’t see tearing in the build, but it does seem contradictory that the frame rate could apparently go above 60 fps (on my 60hz monitor) in the build.

Yeah, I don’t know why it wouldn’t be honoring VSync in the build. Maybe you can add some logging to print out QualitySettings.vSyncCount to log, and see what it’s writing?

I tried turning Vsync on and off in the editor, both in combination with Vsync on and off with a script. Although it has weirdness in itself (being higher and lower frames in build), I’m now doubting it has anything to do with my real issue, which is frequent stutters. I think the only thing Vsync off is doing is making the stutters harder to feel because they are higher frame rate.

I don’t really know what I’m doing with the profiler, but the only spikes I see on it seem to be attributed to Garbage Collection… I think? I mean that’s what the CPU usage graph says the spike is. But the Main Thread section has a bunch of things which seem bigger than all of the other positions on the timeline.

One image shows the spike in CPU usage. That brownish colour is GarbageCollector. I think that corresponds with the lowest frame spikes I could see in my other FPS overlay on the game.

Another image is what can be seen on the main thread and render area where the timeline is positioned on the spike. These two are from previewing in the editor.

The last image is taken from the build and shows both parts at the same time.It’s position on the spike so you can’t see it for the CPU usage, but looks the same.

Well, garbage collection is a common cause of framerate drops and stuttering. There are typically two approaches to eliminating/reducing the impact that garbage collection has on framerates: 1) Stop making garbage, and/or 2) Collect garbage differently.

In the Profiler, you can switch the lower half of the screen from Timeline to Hierarchy view, which shows you metrics for how much garbage is produced by the various function calls. If you have some code that producing a ton of garbage, you can try to reduce it. However, eventually garbage collection will occur, and that tends to be expensive. Unity 2019.1 has a new experimental garbage collector that attempts to collect garbage over many frames, and reduce the impact of garbage collection on any single frame. You could try enabling that, to see if it resolves your issue. But even if it does, it’s probably a good idea to try to reduce garbage generation if it’s this bad. (Basically, you create garbage when you create instances of classes (including strings). Often that kind of code can be replaced by structs instead of classes, which can significantly lower garbage generation.)

Anyway, looking at these graphs, it’s not immediately clear to me whether MadGoatSSAA is really the culprit, or whether the GC.Collect that occurs under it is just a general garbage collection call that happened to occur while the MadGoat code was running. But maybe this package is generating a lot of garbage. If you consistently see the GC Collect occurring within one of their method calls, I’d be suspicious of some poorly optimized code in that package. Try turning off that asset/component, and see if it things improve, just to test out whether it’s actually responsible.

In any case, so far all I see is garbage collection problems. So, try out the experimental GC in 2019.1, if possible, or start using the profiler to track down what is producing so much garbage, and try to get that under control.