Skip to content

Performance issue in IEnumerable .BeEquivalentTo on enumerables with different order when going from v5 to v6.0.0 #1657

@cjvanwyk3

Description

@cjvanwyk3

Description

The BeEquivalentTo method for IEnumerable, when not using strict ordering or any options, now performs very poorly on version 6.0.0. For example To compare 100 objects in version 5 it is a reasonable amount of time, ~1 second, but in version 6 it is > 10 seconds. The amount of time it takes depends on the number of properties as well.

Complete minimal example reproducing the issue

ExampleObject GetObject(int i)
{
    return new ExampleObject
    {
        Id = i.ToString(),
        Value1 = i.ToString(),
        Value2 = i.ToString(),
        Value3 = i.ToString(),
        Value4 = i.ToString(),
        Value5 = i.ToString(),
        Value6 = i.ToString(),
        Value7 = i.ToString(),
        Value8 = i.ToString(),
        Value9 = i.ToString(),
        Value10 = i.ToString(),
        Value11 = i.ToString(),
        Value12 = i.ToString(),
    };
}

var list1 = new List<ExampleObject>();
var list2 = new List<ExampleObject>();
var maxAmount = 100;
for (var i = 0; i < maxAmount; i++)
{
    list1.Add(GetObject(i));
    list2.Add(GetObject(maxAmount - 1 - i));
}

var timer = Stopwatch.StartNew();
list1.Should().BeEquivalentTo(list2);
timer.Stop();
Debug.WriteLine(timer.ElapsedMilliseconds); // 18,539 on version 6.0.0 1,512 on version 5.10.3

public class ExampleObject
{
    [Key]
    public string Id { get; set; }

    public string Value1 { get; set; }

    public string Value2 { get; set; }

    public string Value3 { get; set; }

    public string Value4 { get; set; }

    public string Value5 { get; set; }

    public string Value6 { get; set; }

    public string Value7 { get; set; }

    public string Value8 { get; set; }

    public string Value9 { get; set; }

    public string Value10 { get; set; }

    public string Value11 { get; set; }

    public string Value12 { get; set; }
}

Expected behavior:

Expected better performance

Actual behavior:

Poor performance

Versions

Version 6.0.0

Additional Information

One workaround is to perform the ordering and use the performant strict ordering.
So instead of this which performs poorly:
list1.Should().BeEquivalentTo(list2);
Something like this would be used which performs well:
list1.OrderBy(m => m.Id).Should().BeEquivalentTo(list2.OrderBy(m => m.Id), options => options.WithStrictOrdering());

The current generic workaround I am using, for non strict ordering, looks to see if there are any keys for the object type being compared. If there are keys, the enumerables are ordered by the key and then strict ordering is used which is very efficient :). The code below is using some of our custom code to determine keys, (which is determined using reflection or a registry or user defined keys for objects with multiple properties making up the key) and perform the ordering. But thought i would share it in case it was a helpful concept to use reflection to see if there is an attribute of key that the object could be ordered by for more efficient comparison when the assertion does not care about order.

public static void AssertEquivalent<TModel>(IEnumerable<TModel> first, IEnumerable<TModel> second)
{
    var context = new DataObjectContext<TModel>();
    if (context.IsKeyDefined())
    {
        var keyProperties = context.GetKeyMembers();
        var orderingConfig = new List<OrderingConfiguration<TModel>>();
        foreach (var keyProperty in keyProperties)
        {
            orderingConfig.Add(new OrderingConfiguration<TModel> { Expression = m => keyProperty.GetValue(m) });
        }

        first.OrderBy(orderingConfig).Should().BeEquivalentTo(second.OrderBy(orderingConfig), options => options.WithStrictOrdering());
    }
    else
    {
        first.Should().BeEquivalentTo(second);
    }
}

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions