Array.Sort performance issue

Started by
17 comments, last by frob 10 years, 5 months ago

1) What is the time difference in milliseconds? a delta of 5fps when its running at 200fps is not too much to worry about.


It's running at vsync 60 FPS so it is a significant performance hit.


Frames per second with vsync active means very little. When evaluating performance, disable vsync and use milliseconds as already suggested.

Disabling vsync will only worsen the issue. If this sort code runs at 55FPS, than vsync will not magically increase the performance - it will still run at 55FPS.

It will only affect the original code, which will probably run faster then 60FPS, which only increases the performance drop.

Advertisement
I never said disabling vsync solves the performance problem. I said that evaluating performance differences in frame per second with vsync is pointless. If you are really interested in evaluating the effect of some algorithm on performance you should disable vsync and start using better profiling methods and tools.

If you are really interested in evaluating the effect of some algorithm on performance you should disable vsync and start using better profiling methods and tools.

I totally agree.

When trying to fix performance issues, step 1 is profiling. Humans are bad at guessing where the problem is, so put some profiling in. If you don't have access to a profile library, you can write a little StopWatch class to time each line or function that you're interested in. That being said, I may have noticed some things.

Your first couple of comparisons: Layer and Translation.Z. Is Layer a class or a struct? If Layer is a class, they will only be == if they are pointing to the same instance.

[source]

Layer layer1 = new Layer(Layer.Bottom);

Layer layer2 = new Layer(Layer.Bottom);

if(layer1 == layer2)

Console.WriteLine("This line will not be hit if Layer is a class. They are NOT equal");

[/source]

Same goes for Translation.Z. Sometimes you might think they're the same, but it turns out they aren't.

For your TextureArray's. What that code is saying is this. If they both have a texture[0], see if they are different, otherwise they are equivalent. I suspect this is wrong (but might not be in your case). If one side is null, and the other is not null, that is probably a difference you care about.

[source]

// If we don't have a texture array
if (this.TextureArray[0] == null)
{
// but the other guy does
if (other.TextureArray[0] != null)
return -1; // we're smaller
// Otherwise, we both have a null texture and we're the same. move along.
}
// If we do have a texture array, and the other guy doesn't
else if (other.TextureArray[0] == null)
{
return 1; // we're bigger
}
// else we both have a texture, so we have to check
else if (this.TextureArray[0].Id != other.TextureArray[0].Id)
{
return this.TextureArray[0].Id.CompareTo(other.TextureArray[0].Id);
}
[/source]

Depending on the way you want to sort in this case a null texture might be bigger or smaller. Change the return values if this isn't the case.

And lastly, your BlendMode and ScissorRect seems wrong as well. What this says is: If our blend modes are different, then I'm bigger than the next guy. Things that are sorting on this will swap back and forth each time you Sort your list even if nothing changes. So, let's say you have two blend modes, Opaque and Transparent. The first pass you compare Opaque and Transparent, Opaque will be bigger than Transparent. But call Sort again and Transparent will be bigger than Opaque. Not only that, if you have multiple guys that are sorting on this criteria, Opaque and Transparent will be scattered about randomly.

That last one might be the cause of your performance depending on the sort algorithm in use as the list will never stay sorted.

And like I said before, keep in mind whether or not things are Reference Types (class) or Value Types (structs) as that makes a difference in what you're telling the code to do. For Reference Types you're saying am I referencing the same instance. For Value types you're saying: are the values of these objects equivalent.

http://www.gamedev.net/topic/649248-list-remove/#entry5103729

I hope this helps. But PROFILE IT! :)

- Eck

EckTech Games - Games and Unity Assets I'm working on
Still Flying - My GameDev journal
The Shilwulf Dynasty - Campaign notes for my Rogue Trader RPG

I understand about StopWatch and how to profile performance. I did state in my original post that having only one evaluation in CompareTo would not effect performance. The frame rate drop is not a perfect measurement but it's enough to see there is a substantial performance hit. So I don't need to profile to know where the issue lies. At this stage it's important that my engine maintains a solid 60 FPS.

I think the best thing to do is break the render object list up into separate lists for each layer as suggested.

Another thing I could try would be to use a different sorting algorithm. Array.Sort uses a QuickSort and does not maintain the original list order (unstable). Any suggestions there would be appreciated.

phil_t and Eck make some good points about some of issues with comparisons I've made. Layer and Translation.Z are just floats so there is no reference comparison there. But you're right about the Texture's and null values and I did make a mistake with the BlendMode comparison returning 0 instead of -1. For the ScissorRect I've changed it to compare using Rect.GetHashCode().

Thanks to everyone for all the suggestions.

I did state in my original post that having only one evaluation in CompareTo would not effect performance.

To clarify, you said having only one evaluation resulted in "no performance problem" (and so apparently has a large effect on performance compared to the logic you need in there).

To re-iterate what myself and others have said already (which you haven't addressed): (EDIT: ok, you edited your last post since I wrote this :-))

- when making perf measurements, turn v-sync off. Give performance numbers in seconds or ms per frame, not FPS.

- as pointed out by both me and Eck, your compare function is flawed (since it may claim both that "B comes before A" and "A comes before B"). In addition to not sorting properly, this could very likely be causing a lot more calls to CompareTo than are necessary. Before proceeding any further, you should address this. It may very well be the cause of your performance issues (in addition to being incorrect).

If after that, you still are having perf issues with the sorting, then investigate other avenues (separate lists for each layer, or a sorting algorithm that is efficient on already nearly sorted lists).

Here is the new CompareTo method

public int CompareTo(RenderObject other)
{
    if (this.Layer != other.Layer)
        return this.Layer.CompareTo(other.Layer);

    if (this.Translation.Z != other.Translation.Z)
        return this.Translation.Z.CompareTo(other.Translation.Z);

    for (int i = 0; i < TEXTURE_ARRAY_COUNT; i++)
    {
        if (this.TextureArray[i] == null)
        {
            if (other.TextureArray[i] != null)
                return -1;
        }
        else if (other.TextureArray[i] == null)
            return 1;
        else if (this.TextureArray[i].Id != other.TextureArray[i].Id)
            return this.TextureArray[i].Id.CompareTo(other.TextureArray[i].Id);
    }

    if (this.BlendMode != other.BlendMode)
        return this.BlendMode.CompareTo(other.BlendMode);

    if (this.ScissorRect != other.ScissorRect)
        return (this.ScissorRect.GetHashCode() - other.ScissorRect.GetHashCode());

    return this.Index.CompareTo(other.Index);
}

Unfortunately the performance has not improved.

I'm pretty sure there can't be a meaningful sort on ScissorRect.GetHashCode(), and I wasn't sure how you'd "sort" on ScissorRect to determine which order you should be drawing stuff in. I also have suspicions about sorting on Texture.Id. If those aren't meaningful sorts, then you can get rid of them. Just delete all those comparisons ScissorRect, TextureArray.

Another thing you might try is switching from List to SortedList. I have no idea if the gains there will help or hurt you.

And finally (or firstly rather), you could give profiling a whirl. :)

- Eck

EckTech Games - Games and Unity Assets I'm working on
Still Flying - My GameDev journal
The Shilwulf Dynasty - Campaign notes for my Rogue Trader RPG

I see a few suspects. A simple sort of 1000 items is extremely quick, but you have a complex comparison function.

A sort by itself is extremely quick. Did you accidentally put the sort inside a loop, or otherwise call it more frequently than necessary?

Sort requires "strict weak" ordering, but I'm not convinced you are following that. This ruins the sort algorithm so be absolutely certain you follow it.

Your comparison function includes a nested loop, how big is TEXTURE_ARRAY_COUNT?

Your comparison function uses many accessors to read values from other objects, are those complex functions?

Your comparison function calls other comparison functions, are those comparison functions complex functions?

As others mentioned, these are things that should be obvious once run through a profiler.

This topic is closed to new replies.

Advertisement