Les langages OO modernes peuvent-ils concurrencer les performances de la baie de disques C ++?

40

Je viens de remarquer que tous les langages de programmation OO modernes avec lesquels je suis au moins quelque peu familiarisé (essentiellement Java, C # et D) autorisent les tableaux covariants. Autrement dit, un tableau de chaînes est un tableau d'objets:

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

Les tableaux covariants sont un trou dans le système de types statique. Elles rendent possibles les erreurs de type qui ne peuvent pas être détectées lors de la compilation. Chaque écriture dans un tableau doit donc être vérifiée à l'exécution:

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

Cela semble être une terrible performance si je fais beaucoup de magasins de matrices.

C ++ n'a pas de tableaux covariants, il n'est donc pas nécessaire d'effectuer une telle vérification à l'exécution, ce qui signifie qu'il n'y a aucune pénalité de performance.

Une analyse est-elle effectuée pour réduire le nombre de vérifications d'exécution nécessaires? Par exemple, si je dis:

arr[1] = arr[0];

on pourrait dire que le magasin ne peut pas échouer. Je suis sûr qu'il y a beaucoup d'autres optimisations possibles auxquelles je n'ai pas pensé.

Les compilateurs modernes effectuent-ils ce type d'optimisation ou dois-je accepter le fait que, par exemple, un Quicksort effectue toujours des vérifications d'exécution inutiles O (n log n)?

Les langages OO modernes peuvent-ils éviter les frais généraux générés par la prise en charge de tableaux de co-variantes?

fredoverflow
la source
7
Je ne comprends pas pourquoi vous suggérez que C ++ est plus rapide que d'autres langages pour une fonctionnalité que C ++ ne prend même pas en charge.
17
@eco: Le C ++ est plus rapide avec l'accès à un tableau car il ne prend pas en charge les tableaux à co-variantes. Fred veut savoir s'il est possible pour les "langages OO modernes" d'éluder les frais généraux des tableaux de co-variantes afin de se rapprocher des vitesses C ++.
Mooing Duck
13
"Un code qui compile mais jette des exceptions à l'exécution est une mauvaise nouvelle"
4
@Jesse Et des millions de personnes écrivent un code fiable et hautement évolutif dans des langages dynamiques. Imo: Si vous n'écrivez pas de scénario de test pour votre code, peu importe qu'il y ait ou non des erreurs de compilation, je ne vais pas lui faire confiance de toute façon.
Voo le
7
@ Jesse Et quand attendriez-vous des exceptions mais au moment de l'exécution? Le problème n'est pas le code qui jette des exceptions au moment de l'exécution - il existe de nombreux cas où cela semble logique - le problème est le code dont on garantit que le code est erroné et qui n'est pas intercepté de manière statique par le compilateur mais qui génère une exception à la place. runtime.
Jonathan M Davis

Réponses:

33

D n'a pas de tableaux covariants. Cela leur permettait avant la dernière version ( dmd 2.057 ), mais ce bogue a été corrigé.

Un tableau en D est en réalité juste une structure avec un pointeur et une longueur:

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

La vérification des limites est effectuée normalement lors de l'indexation d'un tableau, mais elle est supprimée lors de la compilation avec -release. Ainsi, en mode release, il n'y a pas de réelle différence de performances entre les tableaux en C / C ++ et ceux en D.

Jonathan M Davis
la source
Ne semble pas - d-programming-language.org/arrays.html dit « Un tableau statique T[dim]peut être converti implicitement à une des options suivantes: ... U[]... Un tableau dynamique T[]peut être converti implicitement à une des opérations suivantes: U[]. .. où Uest une classe de base de T. "
Ben Voigt
2
@BenVoigt Ensuite, les documents en ligne doivent être mis à jour. Malheureusement, ils ne sont pas toujours à jour à 100%. La conversion fonctionnera avec const U[], car dans ce cas, vous ne pouvez pas affecter un type incorrect aux éléments du tableau, mais vous ne pouvez T[]certainement pas convertir en U[]tant que ce dernier U[]est modifiable. Autoriser les tableaux covariants comme auparavant était un sérieux défaut de conception qui a maintenant été corrigé.
Jonathan M Davis
@JonathanMDavis: Les tableaux de covariants sont difficiles, mais fonctionnent bien pour le scénario de code particulier qui écrit uniquement dans un tableau des éléments lus à partir de ce même tableau . Comment D permettrait-il d’écrire une méthode permettant de trier des tableaux de type arbitraire?
Supercat
@supercat Si vous voulez écrire une fonction qui trie des types arbitraires, modélisez-la. par exemple, dlang.org/phobos/std_algorithm.html#sort
Jonathan M Davis
21

Oui, une optimisation cruciale est la suivante:

sealed class Foo
{
}

En C #, cette classe ne peut être un sur-type pour aucun type, vous pouvez donc éviter la vérification d'un tableau de type Foo.

Et à la deuxième question, dans F # les co-variantes ne sont pas autorisées (mais je suppose que la vérification restera dans le CLR sauf si elle est jugée inutile dans les optimisations à l'exécution).

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

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

Un problème quelque peu lié est la vérification des tableaux. Cela pourrait être une lecture intéressante (mais ancienne) sur les optimisations effectuées dans le CLR (la covariance est également mentionnée à un endroit): http://blogs.msdn.com/b/clrcodegeneration/archive/2009/08/13/array-bounds -check-élimination-dans-le-clr.aspx

Lasse Espeholt
la source
2
Scala empêche également cette construction: val a = Array("st"); val b: Array[Any] = aest illégale. (Cependant, les tableaux dans Scala sont ... magiques spéciales ... en raison de la JVM sous-jacente utilisée.)
Pst
13

Réponse Java:

Je suppose que vous n'avez pas réellement comparé le code, n'est-ce pas? En général, 90% des conversions dynamiques en Java sont gratuites car le JIT peut les élier (le tri rapide devrait en être un bon exemple) et le reste est une ld/cmp/brséquence absolument prévisible (sinon, pourquoi diable votre code est-il lancé?) toutes ces exceptions de distribution dynamique?).

Nous effectuons la charge beaucoup plus tôt que la comparaison réelle, la branche est correctement prédite dans 99,9999% (statistiques composées!) De tous les cas, de sorte que nous ne bloquons pas le pipeline (en supposant que la charge ne soit pas saturée, si pas bien, ce sera visible, mais alors la charge est nécessaire de toute façon). Par conséquent, le coût est d'un cycle d'horloge SI le JIT ne peut pas du tout éviter la vérification.

Quelques frais généraux? Bien sûr, mais je doute que vous le remarquiez jamais ..


Pour aider à appuyer ma réponse, veuillez consulter cet article de blog du Dr. Cliff Click traitant des performances de Java contre C.

Voo
la source
10

D n'autorise pas les tableaux covariants.

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

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

Comme vous le dites, ce serait un trou dans le système de types de permettre cela.

Vous pouvez être pardonné pour cette erreur, car ce bogue n’a été corrigé que dans la dernière version de DMD, publiée le 13 décembre.

L'accès aux tableaux en D est aussi rapide qu'en C ou C ++.

Peter Alexander
la source
Selon d-programming-language.org/arrays.html "Un tableau statique T [dim] peut être implicitement converti en l'un des éléments suivants: ... U [] ... Un tableau dynamique T [] peut être implicitement converti à l'un des éléments suivants: U [] ... où U est une classe de base de T. "
Ben Voigt
1
@BenVoigt: Documents obsolètes.
BCS
1
@BenVoigt: J'ai créé une demande d'extraction pour mettre à jour la documentation. Espérons que cela sera bientôt résolu. Merci de l'avoir signalé.
Peter Alexander
5

De test, j'ai fait sur un ordinateur portable pas cher, la différence entre l'utilisation int[]et Integer[]est d'environ 1,0 ns. La différence est probablement due à la vérification supplémentaire pour le type.

Généralement, les objets ne sont utilisés que pour la logique de niveau supérieur lorsque tous les ns ne comptent pas. Si vous devez enregistrer tous les ns, j’éviterai d’utiliser des constructions de niveau supérieur telles que Objects. Les affectations à elles seules risquent d’être très peu importantes dans tout programme réel. par exemple, la création d'un nouvel objet sur la même machine correspond à 5 ns.

Les appels à compareTo risquent d'être beaucoup plus coûteux, surtout si vous utilisez un objet complexe tel que String.

Peter Lawrey
la source
2

Vous avez demandé d'autres langues OO modernes? Bien, Delphi évite complètement ce problème en étant stringun primitif, pas un objet. Ainsi, un tableau de chaînes est exactement un tableau de chaînes et rien d’autre, et toutes les opérations qu’ils effectuent sont aussi rapides que le code natif, sans surcharge de vérification de type.

Cependant, les tableaux de chaînes ne sont pas utilisés très souvent. Les programmeurs Delphi ont tendance à privilégier la TStringListclasse. Pour un minimum de temps système, il fournit un ensemble de méthodes de groupe de chaînes qui sont utiles dans de nombreuses situations, si bien que la classe a été comparée à un couteau militaire suisse. Il est donc idiomatique d'utiliser un objet liste de chaînes au lieu d'un tableau de chaînes.

Comme pour d’autres objets en général, le problème n’existe pas car dans Delphi, comme en C ++, les tableaux ne sont pas covariants, afin d’éviter le type de trous du système de types décrits ici.

Maçon Wheeler
la source
1

ou dois-je vivre avec le fait que, par exemple, un Quicksort effectue toujours O (n log n) des vérifications d'exécution inutiles?

Les performances du processeur ne sont pas monotones, ce qui signifie que des programmes plus longs peuvent être plus rapides que des programmes plus courts (cela dépend du processeur et cela vaut pour les architectures communes x86 et amd64). Il est donc possible qu'un programme effectuant des vérifications liées sur des tableaux soit en réalité plus rapide que le programme déduit de la précédente en supprimant ces vérifications liées.

La raison de ce comportement est que la vérification liée modifie l'alignement du code en mémoire, modifie la fréquence des accès au cache, etc.

Alors oui, tenez compte du fait que Quicksort effectue toujours O (n log n) des contrôles parasites et optimise après le profilage.

utilisateur40989
la source
1

Scala est un langage OO qui a des tableaux invariants plutôt que covariants. Il cible la machine virtuelle Java. Par conséquent, il n’ya aucun gain de performances, mais il évite les erreurs communes à Java et à C # qui compromettent la sécurité de type pour des raisons de compatibilité avec les versions antérieures.

Pillsy
la source