Pointeurs C # vs. pointeurs C ++

J’ai appris à programmer et j’ai choisi la programmation en C ++ et C # comme première langue. Plus précisément, j’ai un vieux livre en C que quelqu’un a eu la gentillesse de me laisser emprunter et je l’utilise pour apprendre le C #. J’utilise Visual Studio Express et j’écris en C ++ et C #. Un domaine qui m’intéresse est la capacité de gérer directement la mémoire. J’essaie d’apprendre à utiliser ceci pour optimiser mon code. Cependant, j’ai du mal à le faire correctement et je constate une réelle amélioration des performances. Par exemple, voici le code suivant en C #:

unsafe static void Main(ssortingng[] args) { int size = 300000; char[] numbers = new char[size]; for (int i = 0; i < size; i++) { numbers[i] = '7'; } DateTime start = DateTime.Now; fixed (char* c = &numbers[0]) { for (int i = 0; i < 10000000; i++) { int number = myFunction(c, 100000); } } /*char[] c = numbers; // commented out C# non-pointer version same speed as C# pointer version { for (int i = 0; i < 10000000; i++) { int number = myFunction(c, 100000); } }*/ TimeSpan timeSpan = DateTime.Now - start; Console.WriteLine(timeSpan.TotalMilliseconds.ToString()); Console.ReadLine(); } static int myFunction(ref char[] numbers, int size) { return size * 100; } static int myFunction(char[] numbers, int size) { return size * 100; } unsafe static int myFunction(char* numbers, int size) { return size * 100; } 

Peu importe laquelle des trois méthodes que j’appelle, j’obtiens la même vitesse d’exécution. J’essaie également de comprendre la différence entre utiliser un pointeur et utiliser un pointeur, sauf que c’est probablement quelque chose qui demandera du temps et de la pratique.

Ce que je ne comprends pas, cependant, est que je suis capable de produire une différence de performance très significative en C ++. Voici ce que j’ai trouvé lorsque j’ai tenté d’approcher le même code en C ++:

 /*int myFunction(std::ssortingng* numbers, int size) // C++ pointer version commented out is much faster than C++ non-pointer version { return size * 100; }*/ int myFunction(std::ssortingng numbers, int size) // value version { return size * 100; } int _tmain(int argc, _TCHAR* argv[]) { int size = 100000; std::ssortingng numbers = ""; for (int i = 0; i < size; i++) { numbers += "777"; } clock_t start = clock(); for (int i = 0; i < 10000; i++) { int number = myFunction(numbers, 100000); } clock_t timeSpan = clock() - start; std::cout <> c; return 0; } 

Quelqu’un peut-il me dire pourquoi mon code C # ne profite pas de mon utilisation de références ou de pointeurs? J’ai lu des articles en ligne, etc., sauf que je suis coincé.

C # génère déjà des pointeurs sans que vous les déclariez explicitement. Chaque référence de type de référence, comme votre variable de nombres , est en fait un pointeur au moment de l’exécution. Chaque argument que vous passez avec les mots-clés ref ou out sont en fait des pointeurs au moment de l’exécution. L’équivalent C exact de votre argument de tableau est char **, char * & en C ++. Il n’y a pas de différence en C #.

Donc, vous ne voyez pas de différence de vitesse car le code qui s’exécute est le même.

Ce n’est pas exactement là que ça s’arrête non plus, vous ne faites jamais rien avec le tableau. La méthode que vous appelez disparaît au moment de l’exécution, comme dans un compilateur C ou C ++, elle sera insérée par l’optimiseur. Et puisque vous n’utilisez pas l’argument array, vous ne recevez pas non plus de code.

Les pointeurs deviennent utiles pour accélérer les programmes lorsque vous les utilisez pour adresser réellement la mémoire. Vous pouvez indexer le tableau et vous assurer de ne jamais payer pour la vérification des limites du tableau. Dans de nombreux cas, vous ne paierez pas non plus dans des conditions normales d’utilisation. L’optimiseur de gigue est assez intelligent pour supprimer les vérifications s’il sait que l’indexation est toujours sécurisée. C’est un usage dangereux d’un pointeur. Vous pouvez facilement graver des parties de la mémoire qui n’appartiennent pas au tableau et corrompre ainsi le tas GC. Les pointeurs utilisés pour une référence d’object ou un argument ref ne sont jamais dangereux.

La seule façon de voir tout cela est de regarder le code machine généré. Fenêtre Debug + Windows + Disassembly. Il est important que le code soit toujours optimisé même si vous le déboguez ou si vous ne pouvez pas voir les optimisations. Assurez-vous d’exécuter la version Release et d’utiliser Outils + Options, Débogage, Général, puis décochez l’option “Supprimer l’optimisation JIT lors du chargement du module”. Une certaine connaissance du code machine est nécessaire pour comprendre ce que vous voyez.

Le problème est que vous ne mesurez pas ce que vous pensez mesurer. Je peux lire votre code et voir immédiatement pourquoi vous obtiendrez ce résultat ou ce résultat, et ce n’est pas uniquement à cause d’indicateurs ou non. Il y a beaucoup d’autres facteurs en jeu, ou potentiellement en jeu. Les différents commentaires reflètent cela.

Pour ce que cela vaut, la raison principale pour laquelle un appel C ++ est beaucoup plus lent est que parce que le plus lent copie un std :: ssortingng et le plus rapide ne le fait pas. Les exemples en C # n’ont rien de comparable à cet ordre de différence.

Ma suggestion est que, en tant que programmeur shiny mais débutant, vous vous concentrez d’abord sur le fait de devenir un meilleur programmeur. Ne vous préoccupez pas de “l’optimisation” jusqu’à ce que vous sachiez ce que vous essayez d’atteindre.

Lorsque vous êtes prêt à vraiment comprendre ce problème, vous devrez étudier le code généré. Dans le cas de C #, il s’agit de MSIL, avec tout ce qu’il est dans une JIT sur la plate-forme particulière. Dans le cas de C ++, il s’agit de codes d’opération Intel pour tous les processeurs. Jusqu’à ce que vous sachiez ce que sont MSIL, JIT et leurs codes d’opération, il sera difficile d’expliquer pourquoi vous obtenez les résultats que vous obtenez.