Profiling et optimisation générale

Bonjour,

Je vais traiter du profiling de vos programmes, de l’analyse de la vitesse, de la loi de wirth: le logiciel ralentit plus vite que le matériel n’accélère. Mes propos sont trés généraux, les articles suivant parleront plus de micro-optimisation dans des cas précis.

Les ordinateurs peuvent traiter facilement des milliards d’instructions par secondes (177 millards pour un Intel Core i7 Extreme Edition 3960X).

Comment profiler?

A première vue cela semble facile: on lance le profiler comme valgrind sous linux, et boom, c’est fini. Même si c’est déjà un début que peu de développeurs font, si c’est mal fait cela ne sert à rien.

Il faut avant tout isoler les cas de test, c’est à dire les cas plus ou moins probable d’utilisation. Un cas souvent oublié: le démarrage. Et oui, tout le monde démarre le programme avant de l’utiliser, or peu de développeurs profilent cette partie où intervient souvent le disque dur et le FS (car pas mal de fichier sont chargé).

Sous ultracopier j’ai isolé ces cas de test:

  • Copie/déplacement de gros fichiers
  • Copie d’une liste raisonnable de petits fichiers (<10000 fichiers)
  • Cas que j’ai rajouté car Ultracopier est des fois utilisé comme ça, et que nos hdd devienne de plus en plus gros: 2 000 000 fichiers, tailles mixtes, destination/sources mixte
Ensuite je me suis aperçu durant l’analyse que l’obtention des modules via une bête liste chaînée prends pas mal de temps, je suis donc passé à une MultiHash.
Un dernier exemple : ouvrir plusieurs fichiers avec un éditeur texte est normal. Or, quand je tentait ça avec kwrite, il fallait 10 minutes à cause d’un problème d’initialisation (corrigé depuis).

Optimiser l’algorithmique

Vous devez évaluer le coût de chaque opération. Votre profiler peut vous y aider.

Essayer de mettre un minimum de chose dans vos boucles. Par exemple, pour 2 boucles imbriqués, mettez l’instanciation en dehors des 2 boucles, cela permet de ne pas désallouer et réallouer vos variables à l’infini.

Evitez de trop presser la mémoire: évitez les allocations inutiles, évitez d’utiliser trop de mémoire. Lorsque vous utilisez beaucoup de donnée, pensez à la cohérence des données dans le cache (accéder à une donnée située à l’adresse X, puis accéder à une donnée située à l’adresse X + 1024 provoque une erreur de cache (cache miss), forçant le cache à être rechargé).

Faites la différence entre le temps d’appel d’une fonction, et le temps total passé dans cette fonction (temps d’appel multiplié par le nombre d’appel).

Bien coder

Ne pas coder un crapware qui fait tout et n’importe quoi, fait partie de la conception générale. Si le logiciel est trop fourni d’options, l’utilisateur vas s’y perdre. Un mode détaillé peu aider.

Les fonctionalités peu utilisées doivent être mises à part (sous forme de patch ou de plugins).

Un code clair est très important.

Le fait de faire tout ça peut vous aider à vous protéger du software bloat, cause de ralentissement logiciel.

Les sections lentes

Soit une section est lente car elle est mal codé.

Évitez qu’une fonction fasse intervenir le hdd puis le cpu, puis de nouveau le hdd. Car à chaque fois l’un des éléments attends l’autre (valable pour d’autre éléments).

Quand ont recherche un éléments dans une longue liste, il faut souvent parcourir toutes la liste, cela prend du cpu et demande beaucoup d’accès à des zones mémoire fragmentées. Lorsque c’est possible, utilisé une table de hashage: celà vous évitera de nombreux parcours de liste.

Soit c’est du à sa nature et cela ne peuT être changé (une compression xz mettra toujours plus de temps qu’une compression gzip; parsING d’un fichier xml).

Dans ce cas essayer de la mettre dans un thread à coté, ou/et d’utilisé les events.

Il ce peu que ce soit la lib que vous utilisez qui ralentisse votre programme. Hormis signaler les ralentissements excessifs au mainteneur ou aider à l’optimisation de la lib, ont ne peut pas y faire grands chose.

Une partie des compilateurs supporte la compilation de fonction optimisé pour un certain cpu, cela peu être utile pour utiliser toutes les instructions du votre cpu (et gagner en performance). Dans certain cas, compilé votre programme pour votre cpu peu être intéressant (ce qui souvent ne le rends plus distribuable à d’autre). Un serveur applicatif qui est sur un serveur dédié spécifique (et qui ne vas pas en bouger), dans ce cas, le compiler sur le serveur même pour le cpu précis du serveur dédié peu être avantageux.

Les caches

On ne parle pas ici du cache processeur, mais des caches en général.

Les caches ne sont pas toujours une bonne idée. Je m’explique : si pour faire un calcul il me faut 10ms et que l’accès au cache demande 15ms (swap?), alors l’utilisation du cache ralenti les performances.

Comment déterminer si un cache est utile? Il faut pour cela mesure le temps d’accès au cache dans un scénario d’utilisation courant. Un cache utilisé souvent et qui est de petite taille a de bonne chance d’être préchargé par le processeur, ce qui va rendre son accès quasiment gratuit.

Attention : un cache de plusieurs centaines de Mo prendra plusieurs centaines de millisecondes pour être parcouru. L’algorithme qui fait le lien entre les données d’entrée et la position dans le cache doit être bien pensé et bien codé. Cela veux dire: ne pas parcourir chaque entrée du cache si le cache contiens des millions d’entrée ; utiliser plusieurs niveaux d’indirection (des sous dossier) au lieu de tout mettre dans une liste (dans un même dossier ; dans le cas réel de dossiers sur un FS, les performances sont fortement amoindries pour les dossiers de plus de 10000 entrée – surtout si les noms sont des hashs).

Il faut analyser le temps de traitement des données : plus il sera grands face au temps d’accès du cache, mieux cela sera. Quand le cache ne fait gagner que 10% alors passer votre chemin, ça fait plus de code, plus difficile à lire.

Régénération du cache, c’est quoi? Le cache peu/doit être mit à jour. Par exemple à chaque écriture de la données entrantes, la donnée sortant doit changer. Si cette donnée est aléatoire et change souvent, un cache risque de coûter plus cher qu’il ne rapporte. Le ratio mise à jour du cache/temps d’accès du cache/temps de génération de la donnée est important.

Voila quel exemple pour mieux comprendre:

  • Si mon image est une image de caméra, elle change par définition à chaque frame. Mettre en cache une frame avec l’effet « noir et blanc » ne servira à rien, vu que l’image ne se répétera pas – et le cache ne sera jamais utilisé en lecture.
  • Si le traitement de mon image se fait en 100ms, que je fait 2 lectures du résultat final avec l’effet « noir et blanc », et que le cache met 50ms, cela veux dire: 1er accéss 100ms, 2eme accéss: 50ms. Le gain apporté par le cache est trop faible.
  • Si maintenant, j’upload mon image, et que je veux l’afficher des milliers de fois sur mon site web avec l’effet « noir et blanc », et que le cache met 5ms, cela veux dire: 1er accès 100ms chaque 1000 lecture, autre accéss: 5ms pour les autres accès. Le cache nous est très utile dans ce cas.
Il faut bien sur voir les problématiques de cohérence de cache.

Les threads

Nous nous dirigeons vers des machines massivement multi-processeur (cpu, gpu…).

Il faut voir l’aspect synchronisation entre les threads. En effet, si vous avez 2000 thread qui se synchronisent (ça peu être juste pour attendre le résultat d’un calcul), et que chaque thread fait le traitement en 1s, cela serait dramatique de mettre aussi 1s par thread pour se synchroniser. Car 2000 thread en fonction de la taille des données transmise, du bus, … ça peu rapidement faire un goulot d’étranglement. Une structure de thread en arbre (1 maître pour 50 esclave) peu aider. La complexité peu être facilement quarré (temps de synchronisation d’un éléments = nombre de threads car il faut parcourir la liste des thread * nombre d’éléments qui doivent le faire = nombre de threads)

Ensuite il y a la localité des données. Si le thread à ses info pour travailler dans le cache de son processeur, il vas travailler sans attendre d’avoir la donnée. Mais si vous faite une grosse zone avec un mutex/vérou dessus, et que tout les threads y accèdent alors il vont plus attendre leur tour que travailler (idem pour la concurrence des autres ressources comme le hdd).

Il faut éviter de partager les données entre plusieurs threads. Un thread qui écrit une donnée accédé en lecture par un autre thread (ça peut être un mutex aussi : à la base, un mutex c’est une donnée à laquelle le processeur accède de manière atomique), alors le coeur gérant le thread en lecture va devoir remettre à jour son cache de données. Si les interactions de ce type sont trop fréquente, les performances vont s’en ressentir.

Si un thread accède à une variable protégé par un mutex, et que le thread qui à besoin de puissance accède à cette même variable via ce même mutex, mais 1000x plus. Alors c’est qu’il y as un problème  car le mutex ralenti fortement le thread qui à besoin de puissance, juste pour que l’autre thread y accède 1/1000.

Mais bien optimiser son programme est plus important que d’utiliser des threads ou les possibilités du matériel. Un programme mal conçu sera lent avec ou sans multi-thread, et ce, quel que soit le CPU.

Les approximations et erreurs

A certains endroits, ont peut tolérer des erreurs, des incertitudes. On peut dire: c’est suffisamment correct.

Il faut utiliser cette logique quand c’est possible. Par exemple, le moteur de copie sur ultracopier remonte la vitesse de lecture. Cette vitesse peut être approximative. De plus, je lit depuis un autre thread la variable sans mutex, ça veux dire que ce que je lit peu être faux une fois sur 1 millions. Pas grave, ça ne sert qu’à l’affichage, et le fait de ne pas poser de mutex me permet de gagner en performance.

L’OS vous fournit des timers précis, ou un peu moins précis. Même chose pour les nombres aléatoires. Utilisez les correctement pour ne pas ralentir votre programme.

Ne pas dire non

J’ai rapporter des problèmes de performance à des développeurs (openTTD). Il m’ont dit: Nous ne voulons pas bosser sur ton problème spécifique qui demande 1 heures, car la gestions d’un autre problèmes (gestion des trains) est un vrai goulot d’étranglement depuis plusieurs années. Voila un bon contre exemple: il vaut mieux corriger un problème qui n’affecte qu’une partie des utilisateurs en 1h plutôt que se limiter à travailler sur un problème qui affecte tous les utilisateurs, mais qui est présent depuis plusieurs années et qui nécessitera encore de long moins de développement.

D’autre diront: non, un éditeur texte n’as pas besoin de performance. Alors qu’il pourrai gagner facilement en performance, consommer moins de cpu (et économisé de la batterie). Cela permet aussi de passer sur des ordinations un peu vieux. Pourquoi faire un programme lent, quand on peu le faire rapide?

Chaque optimisation peut faire que le cpu diminue ses cache miss, que tel ou tel cache de données est mieux exploité, que l’OS swap moins. Il ne faut pas le faire à l’extrême, ni contre le bon sens, mais faire toutes les optimisations facile peu aider un programme à être plus efficace. Cela permet aussi d’apprendre les bons réflexes pour directement bien coder la prochaine fois.

Ne pas considérer les optimisations comme une tache annexe. Cela rends l’optimisation difficile quand elle n’est pas fait en amont (comme la sécurité).

Cas particulier: les cms subissant des attaques DDOS vont résister bien plus facilement si il sont optimisés, car il faudra un botnet et une bande passante plus importante pour saturer le serveur. Alors que s’il ne faut que quelques requêtes par secondes pour saturer le serveur, faut juste 1000 bot chargeant des pages sur le site à quelque page toutes les 10s (navigation normale), et le serveur est saturé. Et vu que les vrai visiteurs naviguent à cette vitesse, comme voulez vous faire la différence? (aucune protection ne marchera). Sans parler du fait que le confort de navigation est meilleur sur un site rapide.

Ne pas répéter comme un perroquet : « Google/Microsoft/Apple/Linux/Untel à dit… » Il peuvent se tromper, vous pouvez avoir mal compris leurs arguments, ou leur cas d’utilisation peut ne pas s’appliquer à la personne que vous conseillez. Ne répéter que si vous comprenez bien les implications de ce que vous dites.

Les erreurs à éviter

La perte de performance est inévitable dans l’absolu (même si vous codez en assembleur : vous voudrez alors écrire du code un peu lisible, or un code lisible n’est pas la meilleure manière d’écrire du code assembleur). Par contre faire un CMS en PHP avec framework qui est 1000x plus lent que du PHP pur, cela n’est pas acceptable.

Une astuces pour les performances peut être vrai en générale, mais pas forcément dans votre cas. Et elle peut tout simplement être mal implémentée. Il vaut mieux ne pas utiliser les variables globales, ni les goto. Mais si une série de goto permet de faire en 100 lignes ce qu’un code sans fait en 5000 lignes, alors foncez! C’est l’un des cas particulier où son usage est conseillé. C’est surement que l’algorithme veux ça (ou la prise en main utilisateurs, …).

Tout à un coût. Analyser bien le coût de ce qu’on vous dit avant de vous lancer. Par exemple grouper les CSS pour avoir moins de taille au final et moins de requête HTTP semble avantageux, mais ce n’est pas toujours vrai: car si 1 CSS sur 10 change, vous re-téléchargez tout les CSS de la page. Alors qu’avec des CSS séparé, juste celui qui n’est pas en cache est téléchargé. Sur un groupement, il faut donc recalculer le groupement des CSS et leur compression à chaque fois en PHP + framework (qui est donc super long).

La compression avec upx peut vous faire gagner un peu de temps dans certains cas sur le chargement du programme en mémoire. Ce ne sera probablement pas le cas pour une petite application (<100Ko) car l’overhead associé au temps de décompression est alors plus fort (le point important ici est le ration temps de lecture sur disque / temps de décompression ; au delà d’une certaine taille, il est toujours plus lent de lire sur disque, mais la différence peut être négligeable voire s’inverser si on traite un petit fichier).

Ne dites pas: les utilisateurs veulent voir les temps rééls et veulent qu’on exploite tous les CPU. Par exemple une fois que vous avez écrit un gros fichier et avant le flush() et close(), masquer la fenêtre, ça évitera de rester sur 100% de progression pendant plusieurs secondes. Vos utilisateurs vont surement vous dire: utilisez tout les cores de nos CPU ! Mais vont râler si votre application est plus lente que celle du voisin (juste car l’utilisation du multi coeur ralenti l’application).

 Statique vs dynamique

Utilisé une bibliothèque en statique permet de minimiser les dépendances. Cela permet donc d’avoir moins de symboles à résoudre lors du chargement de la dll/so/… (qui rajoutent des symboles particuliers utilisés par le linker dynamique), ce qui permet d’avoir un meilleur temps de démarrage.

Cela permet aussi au compilateur de faire des optimisation inter-procédurale, particulièrement quand les optimisation sont fait au link (LTO).

En échange, cela ce traduit par moins de souplesse (pour mettre à jour la lib ont doit recompiler l’application). Cela fait une application plus grosses (mais plus facilement compressible avec upx).

Protocole pour mmorpg

Bonjour,

C’est mon retour d’expérience dans le domaine de la création d’un protocole pour un MMORPG (pokecraft). Mon 1er protocole étant dans la transmission de liste de copies un interne d’un Pc, il n’a que peu de rapport. Le protocole n’est pas fini ni définitif, mais ça fait plusieurs mois que je le travaille.

Cryptage et compression

Crypter la connexion ne sert théoriquement à rien (comme le http à ses débuts). D’après ce qu’ont m’a dit, une technique avec des hashs permet de transmettre le login/pass en hash sans possibilités de l’intercepter. J’ai donc choisi de la rendre optionnelle (je sais pas ce qui va passer par la dans le futur, et que les hardcores gamers soient protégés pour jouer 24h/24)
Le découpage des paquets, le flux non constant des données, le traitement rapide de bloque de données ne permettent pas une compression naturelle et transparente comme le ferait le http avec le gzip. Rien que l’entête de compression ajoute au moins 8 bits à chaque transmission, et la compression n’est efficace qu’a parti de 128 octets (données majoritairement non compressible, compression mixte). Il a donc été choisi de bien écrire le protocole pour éviter la redondance de données similaires. Mais pour les données arbitraires une compression fixe a été choisie: xz pour les éléments qui peuvent être mis en cache (soit dans le data pack directement, soit dans un cache sur le disque), Zlib pour la compression/décompression dynamique.

Minimisation des latences

Un point important appliqué dans pas mal de jeu, la minimisation des latences. Ici le jeu s’y prête grace à un gameplay principalement mono-joueur (multi-joueur fait pas les clans, leagues, …). Donc cela se résume à envoyer des données groupées, et de préférence avant que le client en as besoin. Par exemple, une liste de nombre aléatoire est envoyé pour que le client puisse dedans (liste gardé côté serveur pour faire la même chose). Ça évite le trafic réseau et les lenteurs pour tous les éléments aléatoires spécifiques au joueur.
Quand le joueur va vraiment avoir besoin d’attendre (attente du choix d’une attaque d’un autre joueur), alors c’est au client de le faire attendre (message d’attente).

Séparation du statique et du dynamique

Les données sont séparées en statique (le datapack) et dynamique (envoyée par le protocole). Cela permet de juste faire un rsync pour envoyé le datapack quand il y a un changement, histoire de minimiser les données envoyées, car les données ne changent que très peu (majoritairement statique). Et les données dynamiques sont transmises à la volée en fonction de leur type.

Protocole adpaté pour étre réparti

Le protocole est fait pour être découpé côté serveur. Les paquets de gestion de chat, de gestion de map, gestion des données locales au joueur (combat, inventaire…), sont faits pour être réparti sur plusieurs threads avec une bande passante minimale et sans latence (prêt pour un cluster?). Coté serveur le tout est réparti par des messages asynchrones pour ne jamais bloquer les différents éléments.

Attention, ici l’utilisation de threads est la pour éviter qu’une tache bloquante ne bloque les autres. Je pense à la répartition des déplacements des joueurs sur les autres joueurs, qui a une complexité carré et donc une fâcheuse tendance à mettre le cpu à 100% (surtout que c’est une partie trés solicité). Mais il y a aussi les accès disque ou les accès bloquant à la base de données, qui s’il ne serai pas mis dans un thread à part, bloquerai les joueurs, ou induirai des latences.

Les taches comme les combats, utilisation des objects ont une compléxité fixe, donc en plus d’étre très rapide, les répartir sur plusieurs thread/serveur ne servirai à rien (pose de mutex, induction de lenteur réseau, passage d’un traitement de 1µs à 1ms, soit 1000x plus lent). Et ils sont bien plus rapides que par exemple l’est la répartition des déplacements des joueurs sur les autres joueurs. Les traiter sur 1 cpu permet de ne pas rajouter de couche pour traiter ces données qui ce compte en centaines d’octects.

Datapack

Comment apporter de la souplesse pour ne pas obliger tout le monde à avoir la même histoire, même monstres à combattre, mêmes maps, …? C’est simple, il suffit de faire varier le datapack en function du serveur. Donc ce dernier doit contenir le contenu du jeu, graphisme, audio, données diverses, objets de l’inventaire, … Le client se comporte donc comme un navigateur (lecture avec des codecs déjà connus), les formats supportés sont donc multi-platforme comme: png, xml, ogg, …

Conculsion

L’écriture d’un protocole demande du temps, surtout de ce genre. Il faut bien veiller à l’asynchronisme (pour de pas avoir d’influence des latences), la minimisation de bande passante, et la fléxibilité. Ne rajoutez pas de compression, au d’autre couche du genre car ça vas être mieux, calculer les coups de ces couches (en terme de bande passante, cpu, latences, …). Vous devez aussi trouver quel sont les packets/messages/ordres qui vont étre appeller tout le temps, pour concentrer vos effort et minimiser encore la bande passante.

Qt, select(), poll() et epoll()

Bonjour, je viens vous parler d’un problème récurent avec Qt. La boucle d’évènement, c’est la base de Qt, car c’est grâce à elle que Qt fait ces signaux/slots, les divers events, le réseau, et bien d’autre chose.

Problème

Le problème est le suivant. Ce système s’appuie sur les mécanismes interne de l’os, select() est utilisé sur les systèmes unix. Hors ce procédé est obsoléte, en plus, plus il as de chose utilisé ou non, plus il vas être lent (complexité n(x)). Exemple:

  • En programmation, ça veux dire que si vous avec 30 sockets tcp d’ouvert (ça arrive souvent avec un navigateur moderne), 40 dossiers surveillés (pour les changements), bref une bonne partie des choses basé sur les events, et qui utilise un nombre minimal de connexion signals/slots (les programmes en compte je pense 150 pour les plus petit, 500 pour les moyens). Alors l’application vas ralentir car elle vas regarder dans une liste des choses ouverte celle qui corresponds à la votre
  • Coté utilisateur: Les applications comme Ultracopier, ktorrent, dolphin (explorateur de fichiers), … vont ralentir petit à petit. ktorrent vas être beaucoup plus lent qu’il ne le devrai juste à cause de cette histoire (d’un ordre de 10x d’après l’auteur)

Solution

 

L’utilisation d’epoll() permet d’avoir une complexité fixe n(1), ça veux dire que le temps de traitement est fixe, et donc ne dépends pas du nombre de fichier affiché à l’écran, du nombre de torrent ouvert, du nombre de socket tcp ouvert.

Qt dit que c’est pour l’utilisation mémoire, hors sur les petites applications 10Ko de mémoire, c’est pas ce qui vas changer grands choses, surtout que chez moi (mesuré avec l’outils massif de valgrind), le système signals/slots + les autres truc de Qt consomme 90% de la mémoire prise par mon application. Et les grosses application deviennes impraticable ou trés lent. Et les gros bureaux comme KDE, qui ont des dizaines de milliers de descripteur ouvert, j’en parle même pas.

Conclusion

Hors il n’existe pour l’instant aucun event dispatcher fini/stable avec epoll pour Qt que j’ai pu trouver. Il serai utile de trouver ce genre d’event dispatcher, pour avoir accès à ces méthodes même si Qt ne veux pas y passer.

N’étant pas un expert sur ce que je vous ai dit plus haut, il ce peu qu’il y ai des erreurs.

Création de mmorpg

Bonjour, voilà mon retour d’expérience comme joueur/administrateur système et GM de mmorpg, cet avis sera peut-être déjà acquis pour la plus part:
Point gameplay:
– Le jeu doit être amusant en solo comme un multi, pour ne pas abandonner si peu de personnes viennent
Seulement recopier un gameplay ne sert à rien, leurs joueurs irons toujours à gameplay égale là où il y a d’autres joueurs. Inovez!
Avoir un datapack/rates personnalisable, histoire que chaque personne y trouve son compte (histoire spécifique, augmentation des levels à son rythmes)
– Avoir des extensions de protocoles avec une bonne base, pour étendre le gameplay.
Partie performance:
– Beaucoup de dev font du: multiplayer online role-playing game (MORPG) pas du _Massively_ multiplayer online role-playing game (_M_MORPG), il prévoit pour 50 joueurs (pour eux c’est _Massively_, et se plante quand ils ont plus de joueurs)
– Ce qui en résulte, que le petit GM qui veut ouvrir son petit serveur le ferme rapidement car tous les mois il doit claquer des sommes folles en dédié (60€/mois pour avoir une machine correcte chez ovh).
– La personne qui veut mettre son serveur sur sa ligne ADSL ne peuvent en général pas. Car le débit est trop faible (personne ne fait de compression de la connexion tcp, ni ne prévois des options pour minimisé le débit), et à cause des pings. On peut pour la plupart des jeux, faire des protocoles d’auto-correction des latences qui permettent d’éviter que les pings n’ont trop d’importance. Pourtant, héberger chez soit, ou sur un trés petit hébergement est très utile pour démarrer.
– Calcule de complexité, rare sont ceux qui le font, cela ce pose surtout sur les joueurs qui se vois mutuellement, c’est en général: bande passant + cpu = nombre de joueur qui se vois mutuellement ^ 2

– Tout le monde se dit: on verra après pour les performances, hors une fois que tu as 1000 personnes en ligne et un serveur/client complet, personne n’a le courage de le refaire, surtout si il est crade. Résultat, tout le monde ralle car sa rame.

– Les pc ont une puissance qui augmente exponentiellement et le serveur sont exponentiellement plus lent (ce qui fait que depuis des années le nombre général maximum de joueurs reste fixe). Idiot vous direz. La plupart des serveurs ne supportent que <70 joueurs visibles mutuellement et 1000 connectés. Ce qui est très petit (j’ai fait sans problème 500 visibles et 100 000 connectés).

– Pour un petit nombre de joueur (<20), un simple mono coeur atom/arm, quelque centaine de mega de mémoire, une connexion adsl doivent suffire et pour les larges échelles (>10 000), un bon serveur (quad core, 8Go de mémoire, 1000Mbps) doit aussi suffire.
J’ai déjà vu:
– L’utilisation de tous les coeurs en multi-thread avec tellement de mutex qui se gène mutuellement. Cela fait des temps système énorme car sur certain cpu il n’y a pas d’optimisation hardware des mutex. Et qu’un seul cpu était utilisé en gros au final. (Je parle de l2jfree comparé au serveur propriétaire).
– Des serveur qui prennent 10% du cpu à vide sans aucun joueur…(minecraft, l2jfree, …), soit des fois c’est utile (timer et autre), mais pas avec 10% du cpu!
– Blockage, les joueurs s’en fiches que vous utilisez vos 56 coeurs si ça rame, ils veulent que ça ne rame jamais. Le plus efficace est donc de travailler en event, d’isoler les complexités exponentielles et les accés db/disk dans un thread. Idem pour les parties lentes. Dans le multi-thread avec event, essayer de libérer les boucles d’event+thread critique d’un maximum de choses pour gagner en latence.
– Ne sauvegardez pas en continu dans la base de données, faites le à la déconnexion des joueurs, pour les données continues (tel que les déplacements), à intervalle régulier. Bien sûr, les choses ponctuels peuvent être backupé en instantané (mais dans un thread à part).

 

 

Donc pour tout le monde (hébergeur, celui qui paye le dédié, les joueurs avec compte payant pour payer le dédié), il est indispensable de soigner certain point tel que les performances, le protocole (ça permet aussi de faire d’autres implémentations), la sécurité, la prévention de bug (personne n’aime quand tout le serveur crash et ça re-roll avec les données d’il y as 1h).
Je ne conseille à personne de faire le noob et de se lancer dans un jeu sans avoir déjà une bonne expérience en programmation (et avec un projet clair et réaliste). Les joueurs font partie des utilisateurs les plus exigeants et ce segment est là où il y a le plus de concurrence. Cela se traduit par déjà un grand projet public ou une solide motivation + y jouer même tout seul.

 

 

Les graphismes (je ne suis pas graphiste, mais j’ai quand même un peu d’expérience, manga, pixel art):
– Il ne faut pas les bâcler, car comme moi, beaucoup de joueurs regardent ça pour trier les bons jeux des petits jeux de merde. Donc très important pour avoir de nouveaux joueurs, bien plus que pour les logiciels. Il vaut mieux en avoir peu et bien, que beaucoup et mal. (J’aime bien mars space shooter pour ça)
– Les graphismes personnalisables dans le datapack sont importants.

 

 

Les fonctionnalités des bases importantes:
– Toujours prévoir l’envoie du pass en hash (sécurité), beaucoup utilise le même passe partout (dons les visites qu’il visites)
– L’update du datapack (voir client) dans le protocole, c’est assez crade d’avoir un updater à côté de son jeu
– Datapack par serveur, pour avoir des données par serveur et charger soit en fixe (pour les données les – dynamiques) soit à chaud (pour les données les + dynamiques). Important pour la concurrence et minimiser les données transmises et ne pas charger tout le temps les données dynamiques identiques (ratio read/write correcte pour la partie dynamique).
– Définir le nombre max de joueurs en ligne et en db (définir sur 16Bits, 32Bits ou 64bits), et bien proportionner/optimiser son serveur/client.
– Ne pas exclure de joueurs (multi-platforme, ou au moins support sous wine). C’est toujours frustrant (surtout quand on a acheté le jeu), de passer sous windows pour jouer (ou d’être privé du jeu si on a pas windows 😉 ).

– Visibilité correcte, faite en sorte que le joueur voit toujours une proportion correcte de l’écran (zoom adaptatif, …). Quitte à réduire la distance de visibilité si le nombre de joueur est trop grand. Et à ne rien afficher si les calcules à faire sont exagérés (le joueurs préfèrent ne rien voir, que de tout voir et que ça lag tellement qu’ils ne peuvent pas jouer). N’oubliez pas de mettre des hystérésis pour éviter de lag/prendre trop de bande passante près de la limite du serveur.