Enum.Equals - Análise de desempenho
Enum.Equals - Análise de desempenho
Imagine que temos este mesmo enum:
public enum Color
{
Red = 0,
Green = 1,
}
Nada realmente extravagante, mas para nós é o suficiente. Temos várias maneiras de comparar se duas instâncias de um enum são iguais. Mas antes de mergulhar em alguma explicação, mostrarei os resultados antecipadamente com o código de referência:
public class Benchmark
{
private readonly Color _colorRed = Color.Red;
private readonly Color _colorGreen = Color.Green;
[Benchmark(Baseline = true)]
public bool ObjectEquals() => Equals(_colorRed, _colorGreen);
[Benchmark]
public bool EnumEquals() => Enum.Equals(_colorRed, _colorGreen);
[Benchmark]
public bool InstanceEquals() => _colorRed.Equals(_colorGreen);
[Benchmark]
public bool ComparisonOperator() => _colorRed == _colorGreen;
}
Temos 4 opções para comparar:
- object.Equals
- Enum.Equals
- Chamar Equals de no método de instância
- Use o operador de comparação ==
Agora traga os resultados:
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1348 (21H1/May2021Update)
Intel Core i7-7820HQ CPU 2.90GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
.NET SDK=6.0.101
[Host] : .NET 6.0.1 (6.0.121.56705), X64 RyuJIT
DefaultJob : .NET 6.0.1 (6.0.121.56705), X64 RyuJIT
| Método| Quer dizer| Erro | StdDev | Mediana| Razão| RatioSD |
|------------------- |---------------:|----------:|----------:|----------:|------:|--------:|
| ObjectEquals | 9.1618 ns | 0.2113 ns | 0.1765 ns | 9.1957 ns | 1.000 | 0.00 |
| EnumEquals | 9.1438 ns | 0.1075 ns | 0.0839 ns | 9.1245 ns | 0.997 | 0.02 |
| InstanceEquals | 9.9292 ns | 0.2370 ns | 0.5858 ns | 9.7626 ns | 1.086 | 0.10 |
| ComparisonOperator | 0.0445 ns | 0.0353 ns | 0.0571 ns | 0.0250 ns | 0.008 | 0.01 |
Que pato! Basicamente apenas o operador de comparação leva apenas 1/100 do tempo que as outras variações!
Agora vamos descobrir o porquê!
Boxing and unboxing
Agora as duas primeiras abordagens têm o mesmo tempo de execução (considerando a taxa de erro). Por que é que? Bem EnumEquals e ObjectEquals são os mesmos. Até nosso IDE nos dará uma dica aqui:
Podemos ver que o qualificador Enum é redundante. Mas por que isso. Bom vamos lá:
Quando passamos o mouse sobre Enum.Equals, bem como Equals, obtemos a dica de que isso é invocado do objeto. Então, o que object.Equals faz aqui? É bem simples:
public static bool Equals(object? objA, object? objB)
{
if (objA == objB)
{
return true;
}
if (objA == null || objB == null)
{
return false;
}
return objA.Equals(objB);
}
Segure na primeira linha parece nossa última abordagem, que é a mais rápida! Então eu manipulei o teste porque meus enums não são os mesmos e, portanto, o tempo de execução é muito maior. Não. Mesmo se fizéssemos isso:
[Benchmark]
public bool InstanceEqualsWhenTheSame() => _colorRed.Equals(_colorRed);
Teríamos isso:
| Método| Quer dizer| Erro | StdDev |
|-------------------------- |--------------:|----------:|----------:|
| InstanceEqualsWhenTheSame | 9.821 ns | 0.1284 ns | 0.1138 ns |
O mesmo tempo de execução! Então vemos que todas as abordagens com Equals são iguais e apenas a última é de alguma forma diferente. Vamos dar uma olhada no código IL.
Esse Equals(_colorRed, _colorGreen); assim como este _colorRed.Equals(_colorGreen); irá traduzir aproximadamente para o mesmo código IL:
IL_0005: ldloc.0
IL_0006: box C/Color
IL_000b: ldloc.1
IL_000c: box C/Color
IL_0011: call bool [System.Private.CoreLib]System.Object::Equals(object, object)
IL_0016: pop
O exemplo mostrado acima é de Equals(_colorRed, _colorGreen);
Agora IL_0006: box C/Color e IL_000c: box C/Color são interessantes! Temos que encaixotar nossos enums!
Como fica nosso caso ==?
IL_002b: ldloc.0
IL_002c: ldloc.1
IL_002d: ceq
IL_002f: stloc.2
Sim você vê certo. Não há boxe. Apenas uma simples comparação de igualdade ceq. A propósito, se você quiser brincar sozinho: sharplab.io
"Boxing é o processo de conversão de um tipo de valor para o objeto de tipo ou para qualquer tipo de interface implementado por esse tipo de valor. Quando o Common Language Runtime (CLR) enquadra um tipo de valor, ele encapsula o valor dentro de uma instância de System.Object e o armazena no heap gerenciado. O unboxing extrai o tipo de valor do objeto. O boxe é implícito; unboxing é explícito."
Da documentação oficial da Microsoft
Boxing um tipo de valor em um tipo de referência custa um pouco de tempo. E é exatamente por isso que é tão mais caro. Se você quiser saber mais sobre boxing/ unboxing, sugiro que leia o link acima.
O repositório pode ser encontrado: aqui