Équilibrer C++ et Blueprints – Programmation générale et gameplay

frob a dit :
Je pense que le meilleur parallèle est de demander “quelle goutte de pluie est responsable de l’inondation?” Individuellement, chaque appel, chaque fonction, chaque nœud de blueprint a un coût. Individuellement, le coût du plan est relativement faible, de l’ordre de quelques centaines de nanosecondes à quelques microsecondes. Des copies de UStructs sont faites tout le temps, et elles ne sont pas gratuites avec les allocations et la duplication. La gestion de la mémoire des intérimaires n’est pas gratuite. Ils prennent des cycles CPU, ils consomment de l’espace cache, ils entraînent des coûts pour la mémoire principale. Ils ne sont pas intrinsèquement mauvais, mais ils s’accumulent.

Ce n’est pourtant pas tout à fait ce que je demandais. “Chaque appel” ne les empêche pas d’utiliser un JIT. “Chaque appel” ne fait pas échouer la nativisation. Bien sûr, dans un langage interprété, tout a une surcharge supplémentaire, mais écrire Blueprint-Code avec des appels qui existent en C++ devrait signifier qu’une forme d’exécution plus optimale doit être disponible. J’étais plus intéressé par l’exemple de ce qui empêche cela ou le rend plus difficile, comme vous le prétendez.

frob a dit :
Le plus gros problème que j’ai eu dans la restructuration du code créé par les concepteurs est leur dépendance excessive à l’itération dans les conteneurs, ou à les faire de manière incorrecte. Un exemple récent, parcourant plusieurs milliers d’objets, les convertit dans le type souhaité et continue s’ils correspondent. Un lancer individuel prend un peu moins d’une microseconde chacun. Ils parlaient d’une grande partie de chaque mise à jour en essayant simplement de déterminer si un objet était le bon pour continuer le traitement.

Ainsi, une partie du problème avec les plans utilisés par les concepteurs réside dans de mauvais choix algorithmiques avec un ordre de grandeur élevé d’itérations. Je suppose que cela a au moins du sens, et je suppose que dans certains cas, nous parlons de choses qui sont si mal écrites que même si elles étaient exécutées en mode natif, cela ne serait pas viable en production ?

Bien que ce ne soit pas là encore un point qui devrait les empêcher de fournir un backend (relativement) plus efficace à moindre coût. On peut écrire de mauvais algorithmes en C # sans avoir à doubler avec le code non exécuté nativement. Je ne sais pas s’il me manque quelque chose ou si je n’ai pas réussi à communiquer efficacement ce que je voulais dire. Donc, afin de montrer ce que je veux dire, j’ai fait un test rapide à la fois dans Unreals et mes propres “plans”.

Cela prend environ 0,534 seconde pour s’exécuter, ce qui est plutôt mauvais pour une si petite boucle, ne peut même pas utiliser d’indices plus élevés ou détectera une boucle infinie.

C’est le même code dans mon propre moteur. Attendez-vous à ce que je fasse du profilage en externe, mais la surcharge principale ici devrait être la boucle de toute façon. Cette fonction. compilé avec un JIT, prend 0.011s. C’est presque x50 plus rapide ! C’est mon propos. Il n’y a aucune raison pour qu’un langage de programmation visuel soit aussi lent. Les mêmes choses sur la façon dont les artistes inexpérimentés l’utilisent s’appliqueraient à mon langage qui s’appliquerait aux plans. Mais, sans forcer un outil de nativisation (apparemment) cassé, mais simplement en utilisant (même un compilateur JIT très stupide), cela signifierait que si vous aviez un exemple où un ariste a fait une boucle sur 1000 éléments dans UE-Blueprint et c’était trop lent, avec un meilleur backend, cela aurait pu être presque négligeable.

Cela rend-il mon propos plus clair ? Évidemment, il y a quelque chose qui rend Blueprints lent, mais cela n’a rien à voir avec la façon dont l’interface utilisateur est présentée en premier lieu, car alors mon propre exemple serait tout aussi lent, ce dont il n’est même pas proche.

ÉDITER:
Pour plus de référence, le même code en C++ (dans mon moteur) effectue ce qui suit :

core::Timer	timer;
int x = 0;
for (int i = 0; i < 200000; i++)
{
	x = x + sys::Random::Max(1);
}

const auto duration = timer.Duration();
core::Log::OutInfoFmt("Took {}s", duration);

return x;

Débogage : 0,025 s
Libération : 0,005 s

Ainsi, mon JIT surpasse le code Debug-c++ d'un facteur 2x à ce stade (probablement car il utilise un format plus optimal pour l'ASM généré, qui ressemble davantage à la façon dont un optimiseur le ferait), tandis que le code c++ optimisé est d'environ 2x plus rapide que mon JIT (la différence étant 5x entre le débogage et la version dans cet exemple). Cela devrait donner une référence pour les performances relatives sur mon système, ainsi qu'illustrer davantage le fait que blueprint n'est pas forcément lent du fait de la façon dont il est interfacé, mais de la façon dont son backend est intégré.

.

Leave a Comment

Your email address will not be published. Required fields are marked *