As linguagens OO modernas podem competir com o desempenho da loja de array do C ++?

40

Acabei de notar que toda linguagem de programação OO moderna com a qual estou familiarizado (que é basicamente apenas Java, C # e D) permite matrizes covariantes. Ou seja, uma matriz de cadeias de caracteres é uma matriz de objetos:

Object[] arr = new String[2];   // Java, C# and D allow this

Matrizes covariantes são um orifício no sistema de tipo estático. Eles possibilitam erros de tipo que não podem ser detectados em tempo de compilação, portanto, toda gravação em uma matriz deve ser verificada em tempo de execução:

arr[0] = "hello";        // ok
arr[1] = new Object();   // ArrayStoreException

Parece um desempenho terrível se eu fizer muitas lojas de matriz.

O C ++ não possui matrizes covariantes, portanto, não há necessidade de fazer essa verificação de tempo de execução, o que significa que não há penalidade de desempenho.

Existe alguma análise feita para reduzir o número de verificações de tempo de execução necessárias? Por exemplo, se eu disser:

arr[1] = arr[0];

alguém poderia argumentar que a loja não pode falhar. Tenho certeza de que existem muitas outras otimizações possíveis em que não pensei.

Os compiladores modernos realmente fazem esse tipo de otimização ou eu tenho que conviver com o fato de que, por exemplo, um Quicksort sempre verifica O (n log n) verificações de tempo de execução desnecessárias?

Os idiomas OO modernos podem evitar a sobrecarga criada pelo suporte a matrizes de co-variantes?

fredoverflow
fonte
7
Estou confuso por que você está sugerindo que o C ++ é mais rápido do que outras linguagens em um recurso que o C ++ nem suporta.
17
@eco: C ++ é mais rápido com acesso a array porque não suporta matrizes de co-variantes. Fred quer saber se é possível que "linguagens OO modernas" elimine a sobrecarga de matrizes de co-variantes para se aproximar das velocidades de C ++.
Mooing Duck
13
"código que compila, mas gera exceções em tempo de execução, é uma má notícia"
4
@ Jessé E milhões de pessoas escrevem código confiável e altamente escalável em linguagens dinâmicas. Imo: Se você não escrever casos de teste para o seu código, não me importo se há erros no compilador ou não, não confiarei nele.
Voo
7
@ Jessie E quando mais você esperaria exceções, mas em tempo de execução? O problema não é o código que gera exceções em tempo de execução - há muitos casos em que isso faz sentido - o problema é o código que é garantido errado, que não é capturado estaticamente pelo compilador, mas resulta em uma exceção em tempo de execução.
Jonathan M Davis

Respostas:

33

D não possui matrizes covariantes. Permitiu-os antes da versão mais recente ( dmd 2.057 ), mas esse bug foi corrigido.

Uma matriz em D é efetivamente apenas uma estrutura com um ponteiro e um comprimento:

struct A(T)
{
    T* ptr;
    size_t length;
}

A verificação de limites é feita normalmente ao indexar uma matriz, mas é removida quando você compila -release. Portanto, no modo de liberação, não há diferença real de desempenho entre matrizes em C / C ++ e aquelas em D.

Jonathan M Davis
fonte
Parece não - d-programming-language.org/arrays.html diz "array A estática T[dim]pode ser implicitamente convertido para um dos seguintes procedimentos: ... U[]... matriz dinâmica A T[]pode ser implicitamente convertido para um dos seguintes procedimentos: U[]. .. onde Ué uma classe base de T. "
quer
2
@BenVoigt Em seguida, os documentos on-line precisam ser atualizados. Eles nem sempre estão 100% atualizados, infelizmente. A conversão funcionará com const U[], desde então, você não poderá atribuir o tipo errado aos elementos da matriz, mas T[]definitivamente não será convertido U[]enquanto U[]for mutável. Permitir matrizes covariantes, como antes, era uma falha grave de projeto que agora foi corrigida.
Jonathan M Davis
@JonathanMDavis: matrizes covariantes são nojentas, mas funcionam bem para o cenário particular de código que só grava em elementos de uma matriz que foram lidos a partir dessa mesma matriz . Como D permitiria escrever um método que pudesse classificar matrizes de tipo arbitrário?
supercat
@ supercat Se você quiser escrever uma função que classifique tipos arbitrários, faça um modelo. por exemplo, dlang.org/phobos/std_algorithm.html#sort
Jonathan M Davis
21

Sim, uma otimização crucial é esta:

sealed class Foo
{
}

Em C #, essa classe não pode ser um supertipo para nenhum tipo, para evitar a verificação de uma matriz de tipos Foo.

E para a segunda pergunta, em matrizes de co-variantes F # não são permitidas (mas acho que a verificação permanecerá no CLR, a menos que seja desnecessário em otimizações em tempo de execução)

let a = [| "st" |]
let b : System.Object[] = a // Fails

https://stackoverflow.com/questions/7339013/array-covariance-in-f

Um problema um pouco relacionado é a verificação de limites de matriz. Pode ser uma leitura interessante (mas antiga) sobre otimizações feitas no CLR (covariância também é mencionada em 1 local): http://blogs.msdn.com/b/clrcodegeneration/archive/2009/08/13/array-bounds -check-eliminação-in-the-clr.aspx

Lasse Espeholt
fonte
2
Scala também impede esta construção: val a = Array("st"); val b: Array[Any] = aé ilegal. (No entanto, matrizes em Scala são ... magia especial ... devido à JVM subjacente usada.)
pst
13

Resposta Java:

Presumo que você não tenha comparado o código, não é? Em geral, 90% de todas as transmissões dinâmicas em Java são gratuitas porque o JIT pode eliminá-las (o quicksort deve ser um bom exemplo disso) e o restante é uma ld/cmp/brsequência absolutamente previsível (se não, bem, por que diabos seu código está sendo lançado) todas essas exceções de elenco dinâmico?).

Fazemos a carga muito antes da comparação real, a ramificação é predita corretamente em 99,9999% (estatística composta!) De todos os casos, para que não paremos o pipeline (supondo que não atingimos a memória com a carga, se não é bem isso será perceptível, mas a carga é necessária de qualquer maneira). Portanto, o custo é de 1 ciclo de relógio, se o JIT não puder evitar a verificação.

Alguma sobrecarga? Claro, mas duvido que você alguma vez notará ..


Para ajudar a apoiar minha resposta, consulte este post do Dr. Cliff Click discutindo o desempenho de Java vs. C.

Voo
fonte
10

D não permite matrizes covariantes.

void main()
{
    class Foo {}
    Object[] a = new Foo[10];
}  

/* Error: cannot implicitly convert expression (new Foo[](10LU)) of type Foo[]
to Object[] */

Como você diz, haveria um buraco no sistema de tipos para permitir isso.

Você pode ser perdoado pelo erro, pois esse bug foi corrigido apenas no último DMD, lançado em 13 de dezembro.

O acesso à matriz em D é tão rápido quanto em C ou C ++.

Peter Alexander
fonte
De acordo com d-programming-language.org/arrays.html "Uma matriz estática T [dim] pode ser implicitamente convertida em um dos seguintes itens : ... U [] ... Uma matriz dinâmica T [] pode ser implicitamente convertida para um dos seguintes: U [] ... em que U é uma classe base de T. "
Ben Voigt
11
@BenVoigt: documentos desatualizados.
BCS
11
@BenVoigt: Criei uma solicitação pull para atualizar a documentação. Espero que isso seja resolvido em breve. Obrigado por apontar isso.
Peter Alexander
5

Do teste que fiz em um laptop barato, a diferença entre usar int[]e Integer[]é de cerca de 1,0 ns. É provável que a diferença se deva à verificação extra do tipo.

Geralmente, os objetos são usados ​​apenas para lógica de nível superior quando nem todos os ns contam. Se você precisar salvar todos os ns, evitaria o uso de construções de nível superior, como Objetos. Somente as atribuições provavelmente serão um fator muito pequeno em qualquer programa real. por exemplo, criar um novo objeto na mesma máquina é 5 ns.

As chamadas para compareTo provavelmente serão muito mais caras, especialmente se você estiver usando um objeto complexo como String.

Peter Lawrey
fonte
2

Você perguntou sobre outras linguagens OO modernas? Bem, Delphi evita esse problema inteiramente por stringser um objeto primitivo, não um objeto. Portanto, uma matriz de cadeias de caracteres é exatamente uma matriz de cadeias de caracteres e nada mais, e qualquer operação nelas é tão rápida quanto o código nativo pode ser, sem verificação de tipo de sobrecarga.

No entanto, matrizes de seqüência de caracteres não são usadas com muita frequência; Os programadores Delphi tendem a favorecer a TStringListclasse. Para uma quantidade mínima de sobrecarga, ele fornece um conjunto de métodos de grupos de cordas que são úteis em tantas situações que a classe foi comparada a um canivete suíço. Portanto, é idiomático usar um objeto de lista de cadeias em vez de uma matriz de cadeias.

Quanto a outros objetos em geral, o problema não existe porque no Delphi, como no C ++, as matrizes não são covariantes, a fim de evitar o tipo de tipo de buracos do sistema descritos aqui.

Mason Wheeler
fonte
1

ou tenho que conviver com o fato de que, por exemplo, um Quicksort sempre verifica O (n log n) runtime desnecessário?

O desempenho da CPU não é monotônico, o que significa que os programas mais longos podem ser mais rápidos que os mais curtos (isso depende da CPU e isso é verdade para as arquiteturas comuns x86 e amd64). Portanto, é possível que um programa que faça a verificação vinculada em matrizes seja realmente mais rápido que o programa deduzido do anterior removendo essas verificações vinculadas.

A razão desse comportamento é que a verificação vinculada modifica o alinhamento do código na memória, modifica a frequência de acertos do cache etc.

Portanto, sim, viva com o fato de que o Quicksort sempre faz verificações espúrias O (n log n) e otimiza após a criação de perfil.

user40989
fonte
1

Scala é uma linguagem OO que possui matrizes invariantes e não covariantes. Ele tem como alvo a JVM, portanto, não há ganho de desempenho por lá, mas evita um erro comum de Java e C # que compromete sua segurança de tipo por motivos de compatibilidade com versões anteriores.

Pillsy
fonte