Honnêtement, il est trivial d'écrire un programme pour comparer les performances:
#include <ctime>
#include <iostream>
namespace {
class empty { }; // even empty classes take up 1 byte of space, minimum
}
int main()
{
std::clock_t start = std::clock();
for (int i = 0; i < 100000; ++i)
empty e;
std::clock_t duration = std::clock() - start;
std::cout << "stack allocation took " << duration << " clock ticks\n";
start = std::clock();
for (int i = 0; i < 100000; ++i) {
empty* e = new empty;
delete e;
};
duration = std::clock() - start;
std::cout << "heap allocation took " << duration << " clock ticks\n";
}
Il est dit qu' un idiot de cohérence est le lutin de petits esprits. Apparemment, l'optimisation des compilateurs sont les lutins de nombreux programmeurs de l'esprit. Cette discussion utilisé pour être à la base de la réponse, mais les gens ne peuvent apparemment pas être pris la peine de lire de loin, donc je suis en déplacement jusqu'ici pour éviter de se faire des questions que j'ai déjà répondu.
Un compilateur optimisant peut remarquer que ce code ne fait rien, et peut optimiser tout de suite. C'est l'optimiseur de faire des trucs comme ça, et la lutte contre l'optimiseur est sans issue.
Je recommande la compilation de ce code avec l'optimisation éteint car il n'y a pas de bonne façon de tromper tous optimiseur actuellement en cours d'utilisation ou qui sera utilisé dans l'avenir.
Quelqu'un qui fait de la optimizer, puis se plaint de combat qu'il doit être soumis au ridicule public.
Si je souciait une précision à la nanoseconde je ne voudrais pas utiliser std::clock()
. Si je voulais publier les résultats d'une thèse de doctorat, je voudrais faire une plus grosse affaire à ce sujet, et je serais probablement comparer GCC, je vais avoir/Ten15, LLVM, Watcom, Borland, Visual C++, Numérique de Mars, de la CPI et d'autres compilateurs. Comme il est, allocation de tas prend des centaines de fois plus que l'allocation de pile, et je ne vois rien d'utile pour enquêter sur la question.
L'optimiseur a pour mission de débarrasser le code je suis en essais. Je ne vois pas de raison de dire que l'optimiseur à exécuter, puis essayer de tromper l'optimiseur en a pas en fait de l'optimisation. Mais si j'ai vu de la valeur en faisant cela, je ferais un ou plusieurs des éléments suivants:
Ajouter un membre de données de empty
, et accéder à ses données, membre de la boucle; mais si je n'avais jamais lu du membre de données de l'optimiseur peut faire de constantes et de supprimer la boucle; si je ne jamais écrire pour le membre de données, l'optimiseur peut passer tous les mais la dernière itération de la boucle. En outre, la question n'était pas "pile de répartition et d'accès aux données vs tas de répartition et d'accès aux données."
Déclarer e
volatile
, mais volatile
est souvent compilées de manière incorrecte (PDF).
Prendre l'adresse de l' e
à l'intérieur de la boucle (et peut-être l'attribuer à une variable qui est déclarée extern
et définie dans un autre fichier). Mais même dans ce cas, le compilateur peut remarquer que, sur la pile au moins -- e
seront toujours attribués à la même adresse mémoire, et puis faire de constantes comme dans (1) ci-dessus. Je reçois toutes les itérations de la boucle, mais l'objet n'est jamais réellement allouée.
Au-delà de l'évident, ce test est erronée, car elle permet de mesurer à la fois l'allocation et la libération, et la question d'origine ne m'a pas demandé de libération de la mémoire. Bien sûr, les variables allouées sur la pile sont automatiquement libéré à la fin de leur portée, de sorte de ne pas appeler delete
(1) biaiser les chiffres (pile de libération de la mémoire est inclus dans les chiffres sur l'allocation de pile, il est donc juste de mesurer tas de libération de la mémoire) et (2) la cause d'un assez mauvais souvenir de la fuite, à moins que nous de conserver une référence à la nouvelle pointeur et appelez - delete
après nous avons eu notre mesure du temps.
Sur ma machine, à l'aide de g++ 3.4.4 sur Windows, j'obtiens un "0 tops d'horloge" à la fois la pile et le tas de l'allocation de rien de moins que de 100000 allocations, et même alors, j'ai "0 tops d'horloge" pour l'allocation de pile et "15 ticks" pour l'allocation de tas. Lorsque je mesure 10 000 000 d'allocations, l'allocation de pile prend le 31 tops d'horloge et de tas d'allocation prend 1562 tops d'horloge.
Oui, un compilateur optimisant peut éluder créer le vide d'objets. Si je comprends bien, il peut éluder l'ensemble de la première boucle. Quand j'ai heurté les itérations de 10 000 000 allocation de pile pris 31 tops d'horloge et d'allocation de tas a pris 1562 tops d'horloge. Je pense qu'il est sûr de dire que sans le dire à g++ pour optimiser l'exécutable, g++ n'a pas éluder les constructeurs.
Dans les années depuis que j'ai écrit cela, la préférence sur Stack Overflow a été de performance par l'optimisation des builds. En général, je pense que c'est correct. Cependant, je pense toujours que c'est idiot de demander au compilateur d'optimiser le code, quand en fait, vous ne voulez pas que le code optimisé. Il me frappe comme étant très similaire à payer un supplément pour service de voiturier, mais refusant de remettre les clés. Dans ce cas particulier, je ne veux pas que l'optimiseur en cours d'exécution.
À l'aide d'une version légèrement modifiée de l'indice de référence (à l'adresse de point valide que l'original n'a pas allouer quelque chose sur la pile à chaque passage dans la boucle) et de la compilation sans optimisations, mais en les reliant à la libération des bibliothèques (à l'adresse valide point que nous ne voulons pas comprendre tout ralentissement causé par les reliant à des bibliothèques de débogage):
#include <cstdio>
#include <chrono>
namespace {
void on_stack()
{
int i;
}
void on_heap()
{
int* i = new int;
delete i;
}
}
int main()
{
auto begin = std::chrono::system_clock::now();
for (int i = 0; i < 1000000000; ++i)
on_stack();
auto end = std::chrono::system_clock::now();
std::printf("on_stack took %f seconds\n", std::chrono::duration<double>(end - begin).count());
begin = std::chrono::system_clock::now();
for (int i = 0; i < 1000000000; ++i)
on_heap();
end = std::chrono::system_clock::now();
std::printf("on_heap took %f seconds\n", std::chrono::duration<double>(end - begin).count());
return 0;
}
affiche:
on_stack took 2.070003 seconds
on_heap took 57.980081 seconds
sur mon système, lorsqu'il est compilé avec la ligne de commande cl foo.cc /Od /MT /EHsc
.
Vous ne pouvez pas d'accord avec ma démarche pour l'obtention d'un non-optimisé construire. C'est très bien: n'hésitez pas à modifier l'indice de référence autant que vous le souhaitez. Quand j'allume l'optimisation, j'obtiens:
on_stack took 0.000000 seconds
on_heap took 51.608723 seconds
Non pas parce que l'allocation de pile est en fait instantané, mais parce que toute demi-décent compilateur peut remarquer qu' on_stack
ne pas faire quelque chose d'utile et peut être optimisé à l'écart. GCC sur mon Linux ordinateur portable aussi d'avis qu' on_heap
ne pas faire quelque chose d'utile, et optimise l'éloignant ainsi:
on_stack took 0.000003 seconds
on_heap took 0.000002 seconds