vzlomvl

Hi. In my game I have complex skeletons and animations. Sometimes I have memory allocation like this:
Pasted image at 2017_10_13 02_30 PM.png

As I understand every skeleton has a buffer and when you need a larger size old buffer becomes garbage. I have 15 skeletons and when every start allocate memory in runtime its leads to CG take frame time.
Is it possible to specify a larger buffer size to get rid garbage allocation? And if the way to calculate the maximum buffer based on the skeleton data?
Thanks!
Kullanıcı avatarı
vzlomvl
Mesajlar: 52

Pharan

It doesn't actually do this per frame. Just whenever you change animations.
If it's doing it per frame, that's a bug and do let us know. :p

The size of that buffer highly depends on the complexity of your skeleton's animations.
It's not always a good idea to set it to the animation with the largest number of timelines as there could be very large discrepancies.

But I guess you could calculate the largest by iterating through the animations and getting their timeline counts.
public static int GetMaximumTimelineCount (Spine.SkeletonData skeletonData) {
int maxTimelineCount = 0;

foreach (var animation in skeletonData.Animations) {
int timelineCount = animation.Timelines.Count;
if (maxTimelineCount < timelineCount) maxTimelineCount = timelineCount;
}

return maxTimelineCount;
}
Currently, there's currently no API. But internally, the class' field initializer can be modified here: spine-runtimes/AnimationState.cs at 3.6

It's probably a good idea to have a settable starting timeline buffer for TrackEntries. I'll ask Nate about it and post an update.
Kullanıcı avatarı
Pharan

Pharan
Mesajlar: 4552

Nate

I highly doubt this allocation will ever make a noticeable difference. It only allocates when the animation changes AND the buffer is not large enough. We could set the initial size larger, but the first time resize happens does not impact GC because the initial list size is empty, so the first time is allocation only (which is extremely fast in a managed language).

@pharan, peeking at ExposedList, this looks like a problem:
spine-runtimes/ExposedList.cs at 3.6
Eg, if I have 100 objects in a list, then Resize(50), Count is set to 50 but the items remain in the array at indices 50 to 99. Those items can never be GCed until the list size increases and the references are overwritten, which may never happen. This is why libgdx Array works as it does, references outside of 0 to Count must be nulled:
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils/Array.java#L341-L346
Note truncate is doing the nulling (C# land would use Array.Clear):
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils/Array.java#L434-L439
Exposing Resize is dangerous, as it doesn't clean up the references, and should be private/protected (or maybe just documented, since this list exposes its internals which is inherently unsafe, eg it allows Count to be set, which has the same problem). I suggest using the same implementation and method names as libgdx, which is thoroughly battle tested and doing so makes porting more accurate.

There is a chance for a small optimization: Resize uses Array.Resize which creates a new array and copies the old array to the new. This is not exactly what we need -- we only need to copy from 0 to Count - 1 to the new array, not 0 to Items.Length - 1. We'd be every so slightly better off allocating a new array and copying only up to Count - 1.

Sometimes when code calls setSize, it doesn't care about the current contents of the array, so there is no need to copy that data to the new array. This can be achieved like this:
list.Clear();
list.SetSize(123); // Count is 0 so nothing is copied from old to new array if a resize occurs.
Kullanıcı avatarı
Nate

Nate
Mesajlar: 7647

vzlomvl

I try to allow garbage allocation only in loading time. It would be nice to have API to set the starting buffer size for specific skeleton. But anyway thank for help :)
Kullanıcı avatarı
vzlomvl
Mesajlar: 52

Pharan

Thanks, Nate. That's a good point for ExposedList<T> where T is a class.
Though most of the point of ExposedList<T> is really for internal use anyway. A lot of it is dangerous and for use in hot code.
It really stands in the place of .net's own List<T>, and in the place of the old pattern of having separate array and count fields.
Unfortunately, our Unity Editor code also needs access to ExposedList stuff to work and that lives in a separate assembly so I can't just mark everything internal and call it a day.

In this AnimationState case, nulling doesn't really affect it since timelineData is a bunch of primitive ints.
Kullanıcı avatarı
Pharan

Pharan
Mesajlar: 4552

Nate

True, it only matters for object references. To mark Resize as private API you would still expose whatever is needed, but in a safe way. Since the class is quite unsafe anyway, it's probably fine as it is. I do worry we are holding references too long somewhere, since there isn't a SetSize method. You might audit every use of Resize to check if references are not nulled.
Kullanıcı avatarı
Nate

Nate
Mesajlar: 7647


Dön Unity