Como posso fazer isso rápido?
Claro que posso fazer isso:
static bool ByteArrayCompare(byte[] a1, byte[] a2)
{
if (a1.Length != a2.Length)
return false;
for (int i=0; i<a1.Length; i++)
if (a1[i]!=a2[i])
return false;
return true;
}
Mas estou procurando uma função BCL ou alguma maneira comprovada altamente otimizada de fazer isso.
java.util.Arrays.equals((sbyte[])(Array)a1, (sbyte[])(Array)a2);
funciona bem, mas não parece que isso funcionaria para x64.
Observe minha resposta super rápida aqui .
IStructuralEquatable
Respostas:
Você pode usar o método Enumerable.SequenceEqual .
Se você não puder usar o .NET 3.5 por algum motivo, seu método está OK.
O ambiente do compilador \ tempo de execução otimizará seu loop para que você não precise se preocupar com o desempenho.
fonte
Os poderes P / Invoke são ativados!
fonte
Há uma nova solução interna para isso no .NET 4 - IStructuralEquatable
fonte
StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2)
. NãoNullReferenceException
está aqui.O usuário Gil sugeriu um código não seguro que gerou esta solução:
que faz uma comparação baseada em 64 bits para o máximo possível da matriz. Isso conta com o fato de que as matrizes iniciam o qword alinhado. Funcionará se não estiver alinhado com o qword, apenas não tão rápido como se fosse.
Ele executa cerca de sete temporizadores mais rápido que o
for
loop simples . O uso da biblioteca J # foi executado de forma equivalente aofor
loop original . Usar .SequenceEqual é executado cerca de sete vezes mais devagar; Eu acho que só porque ele está usando IEnumerator.MoveNext. Imagino que as soluções baseadas em LINQ sejam pelo menos lentas ou piores.fonte
Span<T>
oferece uma alternativa extremamente competitiva sem ter que lançar fluff confuso e / ou não portátil na base de código do seu próprio aplicativo:A implementação (essencial) do .NET Core 3.1.0 pode ser encontrada aqui .
Eu já revisto @ essência de EliArbel para adicionar este método como
SpansEqual
, gota a maioria dos artistas menos interessantes em benchmarks dos outros, executá-lo com diferentes tamanhos de matriz, gráficos de saída, e marcaSpansEqual
como linha de base para que ele relata como os diferentes métodos de comparar aSpansEqual
.Os números abaixo são dos resultados, levemente editados para remover a coluna "Erro".
Fiquei surpreso ao ver
SpansEqual
não sair por cima para os métodos de tamanho máximo de matriz, mas a diferença é tão pequena que acho que nunca importará.Informações do meu sistema:
fonte
{ReadOnly,}Span<T>
tem sua própria versão deSequenceEqual
(mesmo nome porque tem o mesmo contrato que oIEnumerable<T>
método de extensão correspondente , é apenas mais rápido). Observe que{ReadOnly,}Span<T>
não é possível usarIEnumerable<T>
métodos de extensão devido às restrições deref struct
tipos.Span<T>
paranetstandard1.1
cima e para cima (então brinque com este gráfico interativo para ver quais são). "Rápido"Span<T>
está disponível apenas no .NET Core 2.1, no momento, mas observe queSequenceEqual<T>
, especificamente, deve haver muito pouca diferença entre "rápido" e "lento" / "portátil" (embora osnetstandard2.0
destinos tenham uma leve melhora porque eles tem o caminho do código vetorizado).Se você não se opuser a isso, poderá importar o assembly J # "vjslib.dll" e usar seu método Arrays.equals (byte [], byte []) ...
Não me culpe se alguém ri de você embora ...
EDIT: Por quanto pouco vale a pena, usei o Reflector para desmontar o código e aqui está o que parece:
fonte
O .NET 3.5 e mais recentes têm um novo tipo público,
System.Data.Linq.Binary
que encapsulabyte[]
. Ele implementaIEquatable<Binary>
que (com efeito) compara matrizes de dois bytes. Observe queSystem.Data.Linq.Binary
também possui um operador de conversão implícito debyte[]
.Documentação do MSDN: System.Data.Linq.Binary
Descompilador do refletor do método Equals:
O interessante é que eles só procedem ao loop de comparação byte a byte se os hashes dos dois objetos binários forem iguais. Isso, no entanto, tem o custo de calcular o hash no construtor de
Binary
objetos (atravessando a matriz comfor
loop :-)).A implementação acima significa que, na pior das hipóteses, você pode ter que percorrer as matrizes três vezes: primeiro para calcular o hash da matriz1, depois para calcular o hash da matriz2 e finalmente (porque esse é o pior cenário, comprimentos e hashes iguais) para comparar bytes na matriz1 com bytes na matriz 2.
No geral, embora
System.Data.Linq.Binary
esteja integrado ao BCL, não acho que seja a maneira mais rápida de comparar matrizes de dois bytes: - |.fonte
Eu postei uma pergunta semelhante sobre como verificar se o byte [] está cheio de zeros. (O código SIMD foi batido, removi-o desta resposta.) Aqui está o código mais rápido das minhas comparações:
Medido em duas matrizes de 256 MB de bytes:
fonte
fonte
Vamos adicionar mais um!
Recentemente, a Microsoft lançou um pacote NuGet especial, System.Runtime.CompilerServices.Unsafe . É especial porque está escrito em IL e fornece funcionalidade de baixo nível que não está disponível diretamente em C #.
Um de seus métodos
Unsafe.As<T>(object)
permite converter qualquer tipo de referência para outro tipo de referência, ignorando as verificações de segurança. Isso geralmente é uma muito má ideia, mas se ambos os tipos têm a mesma estrutura, ele pode trabalhar. Portanto, podemos usar isso para converter umbyte[]
em umlong[]
:Observe que
long1.Length
ainda retornaria o comprimento da matriz original, pois ela é armazenada em um campo na estrutura de memória da matriz.Esse método não é tão rápido quanto os outros métodos demonstrados aqui, mas é muito mais rápido que o método ingênuo, não usa código inseguro ou P / Invoke ou fixação, e a implementação é bastante direta (IMO). Aqui estão alguns resultados do BenchmarkDotNet da minha máquina:
Eu também criei uma essência com todos os testes .
fonte
NewMemCmp
resposta para usar o AVX-2Eu desenvolvi um método que bate ligeiramente
memcmp()
(resposta de plinth) e muito ligeiramenteEqualBytesLongUnrolled()
(resposta de Arek Bulski) no meu PC. Basicamente, ele desenrola o loop por 4 em vez de 8.Atualização 30 de março de 2019 :
A partir do .NET core 3.0, temos suporte a SIMD!
Esta solução é mais rápida por uma margem considerável no meu PC:
fonte
null
.Span
ememcpy
?Eu usaria código inseguro e executaria o
for
loop comparando ponteiros Int32.Talvez você também deva considerar a verificação das matrizes como não nulas.
fonte
Se você observar como o .NET executa string.Equals, verá que ele usa um método particular chamado EqualsHelper que possui uma implementação de ponteiro "não segura". O .NET Reflector é seu amigo para ver como as coisas são feitas internamente.
Isso pode ser usado como um modelo para comparação de matriz de bytes, no qual eu fiz uma implementação na postagem do blog Comparação rápida de matriz de bytes em C # . Também fiz alguns benchmarks rudimentares para ver quando uma implementação segura é mais rápida que a insegura.
Dito isto, a menos que você realmente precise de desempenho matador, eu faria uma comparação simples do loop fr.
fonte
Não foi possível encontrar uma solução com a qual eu esteja completamente feliz (desempenho razoável, mas nenhum código / pinvoke inseguro), por isso vim com isso, nada realmente original, mas funciona:
Desempenho em comparação com algumas das outras soluções nesta página:
Loop simples: 19837 ticks, 1,00
* BitConverter: 4886 ticks, 4,06
Comparação: 1636 ticks, 12.12
EqualBytesLongUnrolled: 637 ticks, 31,09
P / Invocar memcmp: 369 ticks, 53.67
Testado no linqpad, 1000000 bytes de matrizes idênticas (pior cenário), 500 iterações cada.
fonte
Parece que EqualBytesLongUnrolled é o melhor das sugestões acima.
Os métodos ignorados (Enumerable.SequenceEqual, StructuralComparisons.StructuralEqualityComparer.Equals) não eram pacientes com lentidão. Em matrizes de 265 MB, eu medi isso:
fonte
NewMemCmp
resposta para usar o AVX-2Eu não vi muitas soluções linq aqui.
Não tenho certeza das implicações de desempenho, no entanto, geralmente mantenho
linq
como regra geral e otimizo mais tarde, se necessário.Observe que isso só funciona se forem do mesmo tamanho de matrizes. uma extensão poderia parecer tão
fonte
Fiz algumas medições usando o programa anexado .net 4.7 release build sem o depurador conectado. Acho que as pessoas estão usando a métrica errada, já que você se preocupa com a velocidade aqui. Quanto tempo leva para descobrir se a matriz de dois bytes é igual. ou seja, taxa de transferência em bytes.
Como você pode ver, não há maneira melhor do que
memcmp
e é ordens de magnitude mais rápidas. Umfor
loop simples é a segunda melhor opção. E ainda me surpreende por que a Microsoft não pode simplesmente incluir umBuffer.Compare
método.[Program.cs]:
fonte
Para comparar matrizes de bytes curtos, o seguinte é um truque interessante:
Provavelmente, eu cairia na solução listada na pergunta.
Seria interessante fazer uma análise de desempenho desse código.
fonte
Para aqueles que se preocupam com a ordem (ou seja, desejam que você
memcmp
retorneint
como deveria, em vez de nada), o .NET Core 3.0 (e presumivelmente o .NET Standard 2.1, também conhecido como .NET 5.0) incluirá umSpan.SequenceCompareTo(...)
método de extensão (mais aSpan.SequenceEqualTo
) que pode ser usado para comparar duasReadOnlySpan<T>
instâncias (where T: IComparable<T>
).Na proposta original do GitHub , a discussão incluiu comparações de abordagens com cálculos de tabelas de salto, leitura de
byte[]
aslong[]
, uso de SIMD e p / invocação para a implementação de CLRmemcmp
.A partir de agora, esse deve ser o seu método principal para comparar matrizes de bytes ou intervalos de bytes (como deve ser usado em
Span<byte>
vez dasbyte[]
APIs do .NET Standard 2.1) e é suficientemente rápido o suficiente para que você não precise mais otimizá-lo (e não, apesar das semelhanças no nome, ele não é tão abismal quanto o horrívelEnumerable.SequenceEqual
).fonte
Pensei nos métodos de aceleração de transferência de bloco incorporados a muitas placas gráficas. Mas você teria que copiar todos os dados em bytes, para que isso não lhe ajude muito se você não quiser implementar uma parte inteira de sua lógica em código não gerenciado e dependente de hardware ...
Outra maneira de otimização semelhante à abordagem mostrada acima seria armazenar o máximo de dados possível em um longo [] em vez de um byte [] desde o início, por exemplo, se você estiver lendo sequencialmente em um arquivo binário, ou se você usar um arquivo mapeado na memória, leia os dados com valores longos [] ou únicos longos. Então, seu loop de comparação precisará apenas de 1/8 do número de iterações necessárias para um byte [] que contenha a mesma quantidade de dados. É uma questão de quando e com que frequência você precisa comparar versus quando e com que frequência precisa acessar os dados de forma a byte a byte, por exemplo, para usá-los em uma chamada de API como parâmetro em um método que espera um byte []. No final, você só pode saber se realmente conhece o caso de uso ...
fonte
Isso é quase certamente muito mais lento do que qualquer outra versão apresentada aqui, mas foi divertido de escrever.
fonte
Eu decidi por uma solução inspirada no método EqualBytesLongUnrolled publicado por ArekBulski com uma otimização adicional. No meu exemplo, diferenças de matriz em matrizes tendem a estar próximas à cauda das matrizes. Nos testes, descobri que, quando esse é o caso de matrizes grandes, a possibilidade de comparar os elementos da matriz na ordem inversa fornece a esta solução um enorme ganho de desempenho em relação à solução baseada em memcmp. Aqui está essa solução:
fonte
Desculpe, se você está procurando uma maneira gerenciada, já está fazendo isso corretamente e, pelo que sei, não há método incorporado na BCL para fazer isso.
Você deve adicionar algumas verificações nulas iniciais e apenas reutilizá-las como se estivessem na BCL.
fonte
Use
SequenceEquals
para isso para comparação.fonte
Se você estiver procurando por um comparador de igualdade de matriz de bytes muito rápido, sugiro que você dê uma olhada neste artigo do STSdb Labs: Comparador de igualdade de matriz de bytes. Ele apresenta algumas das implementações mais rápidas para comparação de igualdade de matriz de bytes [], que são apresentadas, desempenho testado e resumido.
Você também pode se concentrar nessas implementações:
BigEndianByteArrayComparer - byte rápido [] array comparer da esquerda para a direita (bigEndian) BigEndianByteArrayEqualityComparer - - byte rápido [] igualdade comparer da esquerda para a direita (bigEndian) LittleEndianByteArrayComparer - byte rápido [] array comparer da direita para a esquerda (littleEndian) LittleEndianByteArrayEqualityComparer - byte rápido [] comparador de igualdade da direita para a esquerda (LittleEndian)
fonte
A resposta curta é esta:
Dessa forma, você pode usar a comparação otimizada da string .NET para comparar uma matriz de bytes sem a necessidade de escrever código não seguro. É assim que é feito em segundo plano :
fonte
Compare(new byte[]{128}, new byte[]{ 255 }) == true
não buggy em tudo ...Como muitas das soluções sofisticadas acima não funcionam com o UWP e porque eu amo o Linq e as abordagens funcionais, pressiono minha versão para esse problema. Para escapar da comparação quando a primeira diferença ocorre, escolhi .FirstOrDefault ()
fonte
IndexOutOfRangeException
ao comparar matrizes não vazios porque você está acessando elementos1
atravésba0.Length
quando deveria ser0
atravésba0.Length - 1
. Se você corrigir isso,Enumerable.Range(0, ba0.Length)
ele ainda retornará incorretamentetrue
para matrizes de comprimento igual, onde apenas os primeiros elementos diferem porque você não pode distinguir entre os primeiros elementos satisfatóriospredicate
e nenhum elemento satisfatóriopredicate
;FirstOrDefault<int>()
retorna0
em ambos os casos.