La question d'origine
  Pourquoi une boucle est-elle tellement plus lente que deux boucles?
Conclusion:
Cas 1 est un problème d'interpolation classique qui s'avère être inefficace. Je pense également que c'était l'une des principales raisons pour lesquelles de nombreuses architectures de machines et développeurs ont fini par construire et concevoir des systèmes multicœurs avec la capacité de faire des applications multithread ainsi que la programmation parallèle.
Le regarder à partir de ce type d'approche sans impliquer comment le matériel, le système d'exploitation et les compilateurs fonctionnent ensemble pour effectuer des allocations de tas qui impliquent de travailler avec la RAM, le cache, les fichiers de page, etc. les mathématiques qui sont à la base de ces algorithmes nous montrent laquelle de ces deux est la meilleure solution.
Nous pouvons utiliser une analogie d'un Bossêtre Summationqui représentera un For Loopqui doit voyager entre les travailleurs Aet B.
Nous pouvons facilement voir que le cas 2 est au moins deux fois moins rapide, sinon un peu plus que le cas 1, en raison de la différence de distance à parcourir et du temps pris entre les travailleurs. Ces calculs s'alignent presque virtuellement et parfaitement avec le BenchMark Times ainsi qu'avec le nombre de différences dans les instructions de montage.
   
Je vais maintenant commencer à expliquer comment tout cela fonctionne ci-dessous.
Évaluer le problème
Le code de l'OP:
const int n=100000;
for(int j=0;j<n;j++){
    a1[j] += b1[j];
    c1[j] += d1[j];
}
Et
for(int j=0;j<n;j++){
    a1[j] += b1[j];
}
for(int j=0;j<n;j++){
    c1[j] += d1[j];
}
La prise en compte
Considérant la question originale du PO sur les 2 variantes des boucles for et sa question modifiée concernant le comportement des caches ainsi que de nombreuses autres excellentes réponses et commentaires utiles; J'aimerais essayer de faire quelque chose de différent ici en adoptant une approche différente de cette situation et de ce problème.
L'approche
Compte tenu des deux boucles et de toute la discussion sur le cache et le dépôt de pages, j'aimerais adopter une autre approche pour examiner les choses sous un angle différent. Celui qui n'implique pas le cache et les fichiers d'échange ni les exécutions pour allouer de la mémoire, en fait, cette approche ne concerne même pas du tout le matériel ou le logiciel.
La perspective
Après avoir regardé le code pendant un certain temps, il est devenu tout à fait évident quel est le problème et ce qui le génère. Décomposons cela en un problème algorithmique et examinons-le du point de vue de l'utilisation des notations mathématiques, puis appliquons une analogie aux problèmes mathématiques ainsi qu'aux algorithmes.
Ce que nous savons
Nous savons que cette boucle sera exécutée 100 000 fois. Nous savons aussi que a1, b1, c1et d1sont des pointeurs sur une architecture 64 bits. Dans C ++ sur une machine 32 bits, tous les pointeurs font 4 octets et sur une machine 64 bits, ils ont une taille de 8 octets car les pointeurs sont de longueur fixe.
Nous savons que nous avons 32 octets à allouer dans les deux cas. La seule différence est que nous allouons 32 octets ou 2 ensembles de 2 à 8 octets à chaque itération, dans le deuxième cas, nous allouons 16 octets pour chaque itération pour les deux boucles indépendantes.
Les deux boucles égalent toujours 32 octets dans les allocations totales. Avec ces informations, allons de l'avant et montrons les mathématiques générales, les algorithmes et l'analogie de ces concepts.
Nous savons le nombre de fois qu'un même ensemble ou groupe d'opérations devra être effectué dans les deux cas. Nous connaissons la quantité de mémoire qui doit être allouée dans les deux cas. Nous pouvons évaluer que la charge de travail globale des allocations entre les deux cas sera approximativement la même.
Ce que nous ne savons pas
Nous ne savons pas combien de temps cela prendra pour chaque cas, sauf si nous définissons un compteur et exécutons un test de référence. Cependant, les repères étaient déjà inclus à partir de la question initiale et de certaines réponses et commentaires également; et nous pouvons voir une différence significative entre les deux et c'est tout le raisonnement pour cette proposition à ce problème.
Enquêtons  
Il est déjà évident que beaucoup l'ont déjà fait en examinant les allocations de tas, les tests de référence, la RAM, le cache et les fichiers d'échange. L'examen de points de données spécifiques et d'indices d'itération spécifiques a également été inclus et les diverses conversations sur ce problème spécifique ont amené de nombreuses personnes à s'interroger sur d'autres sujets connexes. Comment commencer à regarder ce problème en utilisant des algorithmes mathématiques et en lui appliquant une analogie? Nous commençons par faire quelques affirmations! Ensuite, nous construisons notre algorithme à partir de là.
Nos affirmations:
- Nous allons laisser notre boucle et ses itérations être une sommation qui commence à 1 et se termine à 100000 au lieu de commencer par 0 comme dans les boucles car nous n'avons pas à nous soucier du schéma d'indexation 0 de l'adressage de la mémoire, car nous sommes simplement intéressés par l'algorithme lui-même.
 
- Dans les deux cas, nous avons 4 fonctions avec lesquelles travailler et 2 appels de fonction avec 2 opérations en cours sur chaque appel de fonction. Nous allons définir ces fonctions et comme des appels à des fonctions comme suit: 
F1(), F2(), f(a), f(b), f(c)et f(d). 
Les algorithmes:
1er cas: - Une seule sommation mais deux appels de fonction indépendants.
Sum n=1 : [1,100000] = F1(), F2();
                       F1() = { f(a) = f(a) + f(b); }
                       F2() = { f(c) = f(c) + f(d); }
2ème cas: - Deux sommations mais chacune a son propre appel de fonction.
Sum1 n=1 : [1,100000] = F1();
                        F1() = { f(a) = f(a) + f(b); }
Sum2 n=1 : [1,100000] = F1();
                        F1() = { f(c) = f(c) + f(d); }
Si vous avez remarqué qu'il F2()n'existe que dans Sumd' Case1où F1()est contenu dans Sumde Case1et dans les deux Sum1et Sum2de Case2. Cela sera évident plus tard lorsque nous commencerons à conclure qu'il y a une optimisation qui se produit dans le deuxième algorithme.
Les itérations à travers les premiers Sumappels de cas f(a)qui s'ajouteront à lui-même f(b)puis les appels f(c)qui feront de même mais s'ajouteront f(d)à chaque 100000itération. Dans le second cas, nous avons Sum1et Sum2que les deux agissent de la même manière que s'ils étaient la même fonction appelée deux fois de suite.
Dans ce cas, nous pouvons traiter Sum1et Sum2comme tout simplement vieux Sumoù Sumdans ce cas, cela ressemble à ceci: Sum n=1 : [1,100000] { f(a) = f(a) + f(b); }et maintenant cela ressemble à une optimisation où nous pouvons simplement considérer que c'est la même fonction.
Résumé avec l'analogie
Avec ce que nous avons vu dans le deuxième cas, il semble presque qu'il y ait optimisation car les deux boucles ont la même signature exacte, mais ce n'est pas le vrai problème. La question n'est pas le travail qui est fait par f(a), f(b), f(c)et f(d). Dans les deux cas et la comparaison entre les deux, c'est la différence de distance que doit parcourir la somme dans chaque cas qui vous donne la différence de temps d'exécution.
Pensez à l' For Loopscomme étant le Summationsqui fait les itérations comme étant un Bossqui donne des ordres à deux personnes Aet Bet que leurs emplois sont à la viande Cet , Drespectivement , et de ramasser quelques paquet d'eux et de le retourner. Dans cette analogie, les boucles for ou les itérations de sommation et les vérifications de conditions elles-mêmes ne représentent pas réellement le Boss. Ce qui représente réellement le Bossn'est pas directement issu des algorithmes mathématiques réels, mais du concept réel de Scopeet Code Blockau sein d'une routine ou d'un sous-programme, d'une méthode, d'une fonction, d'une unité de traduction, etc.
Dans le premier cas sur chaque fiche d'appel, le Bossva à Aet donne la commande et Apart chercher le B'spaquet puis le Bossva à Cet donne les ordres de faire de même et de recevoir le paquet Dà chaque itération.
Dans le second cas, le programme Bosstravaille directement avec Apour aller chercher le B'spaquet jusqu'à ce que tous les paquets soient reçus. Ensuite, Bossfonctionne avec Cpour faire de même pour obtenir tous les D'spackages.
Étant donné que nous travaillons avec un pointeur de 8 octets et que nous traitons l'allocation de segment de mémoire, considérons le problème suivant. Disons que le Bossest à 100 pieds Aet à A500 pieds C. Nous n'avons pas à nous soucier de la distance qui les Bosssépare initialement en Craison de l'ordre des exécutions. Dans les deux cas, le Bossvoyage d' Aabord du premier au ensuite B. Cette analogie ne veut pas dire que cette distance est exacte; c'est juste un scénario de cas de test utile pour montrer le fonctionnement des algorithmes.
Dans de nombreux cas, lors de l'allocation de segments de mémoire et de l'utilisation du cache et des fichiers d'échange, ces distances entre les emplacements d'adresses peuvent ne pas varier beaucoup ou elles peuvent varier considérablement en fonction de la nature des types de données et des tailles de tableau.
Les cas de test:
Premier cas: lors de la première itération, leBossdoit d'abord parcourir 100 pieds pour donner le bon de commandeAet s'enAva et fait son truc, mais ilBossdoit ensuite parcourir 500 pieds pourClui donner son bon de commande. Ensuite, à la prochaine itération et à toutes les autres itérations après leBossdoit faire 500 allers-retours entre les deux.
Deuxième cas: LeBossdoit parcourir 100 pieds lors de la première itérationA, mais après cela, il est déjà là et attend justeAde revenir jusqu'à ce que tous les feuillets soient remplis. Ensuite, ilBossdoit parcourir 500 pieds lors de la première itérationCcar il seCtrouve à 500 pieds deA. Étant donné que celaBoss( Summation, For Loop )est appelé juste après avoir travaillé avec,Ail attend juste là comme il l'a faitAjusqu'à ce que tous lesbonsdeC'scommande soient effectués.  
La différence de distances parcourues
const n = 100000
distTraveledOfFirst = (100 + 500) + ((n-1)*(500 + 500); 
// Simplify
distTraveledOfFirst = 600 + (99999*100);
distTraveledOfFirst = 600 + 9999900;
distTraveledOfFirst =  10000500;
// Distance Traveled On First Algorithm = 10,000,500ft
distTraveledOfSecond = 100 + 500 = 600;
// Distance Traveled On Second Algorithm = 600ft;    
La comparaison des valeurs arbitraires
Nous pouvons facilement voir que 600 est bien moins de 10 millions. Maintenant, ce n'est pas exact, car nous ne connaissons pas la différence réelle de distance entre quelle adresse de RAM ou de quel cache ou fichier d'échange chaque appel à chaque itération sera dû à de nombreuses autres variables invisibles. Il s'agit simplement d'une évaluation de la situation à prendre en compte et de l'examiner dans le pire des cas.
D'après ces chiffres, il semblerait presque que l'algorithme un devrait être 99%plus lent que l'algorithme deux; Cependant, ce n'est que la Boss'spartie ou la responsabilité des algorithmes et il ne tient pas compte des travailleurs réels A, B, C, et Det ce qu'ils ont à faire sur chaque itération de la boucle. Le travail du patron ne représente donc qu'environ 15 à 40% du travail total effectué. La majeure partie du travail effectué par les travailleurs a un impact légèrement plus important sur le maintien du rapport des différences de taux de vitesse à environ 50-70%
L'observation: - Les différences entre les deux algorithmes
Dans cette situation, c'est la structure du processus de travail en cours. Cela montre que le cas 2 est plus efficace à la fois par l'optimisation partielle d'avoir une déclaration et une définition de fonction similaires où seules les variables diffèrent par leur nom et la distance parcourue.
Nous voyons également que la distance totale parcourue dans le cas 1 est beaucoup plus éloignée que dans le cas 2 et nous pouvons considérer cette distance parcourue comme notre facteur temps entre les deux algorithmes. Le cas 1 a beaucoup plus de travail à faire que le cas 2 .
Cela ressort de la preuve des ASMinstructions qui ont été montrées dans les deux cas. Avec ce qui a déjà été dit à propos de ces cas, cela ne tient pas compte du fait que dans le cas 1, le patron devra attendre les deux Aet Crevenir avant de pouvoir revenir à Anouveau à chaque itération. Cela ne tient pas compte non plus du fait que si Aou Bprend un temps extrêmement long, le Bossou les autres travailleurs sont inactifs en attente d'être exécutés.
Dans le cas 2, le seul qui est inactif est Bossjusqu'à ce que le travailleur revienne. Donc, même cela a un impact sur l'algorithme.
Question (s) modifiée (s) du PO
  EDIT: La question s'est avérée sans pertinence, car le comportement dépend fortement des tailles des tableaux (n) et du cache du processeur. Donc s'il y a plus d'intérêt, je reformule la question:
  Pourriez-vous fournir des informations solides sur les détails qui conduisent aux différents comportements de cache, comme illustré par les cinq régions sur le graphique suivant?
  Il pourrait également être intéressant de souligner les différences entre les architectures CPU / cache, en fournissant un graphique similaire pour ces CPU.
Concernant ces questions
Comme je l'ai démontré sans aucun doute, il y a un problème sous-jacent avant même que le matériel et les logiciels ne soient impliqués. 
Maintenant, en ce qui concerne la gestion de la mémoire et de la mise en cache avec les fichiers de page, etc. qui fonctionnent tous ensemble dans un ensemble intégré de systèmes entre les éléments suivants: 
The Architecture {Matériel, micrologiciel, certains pilotes intégrés, noyaux et jeux d'instructions ASM}. 
The OS{Systèmes de gestion de fichiers et de mémoire, pilotes et registre}.  
The Compiler {Unités de traduction et optimisations du code source}. 
- Et même le 
Source Codelui - même avec son ensemble d'algorithmes distinctifs. 
On peut déjà voir qu'il ya un goulot d' étranglement qui se passe dans le premier algorithme avant d' appliquer même à une machine avec un arbitraire Architecture, OSet par Programmable Languagerapport au deuxième algorithme. Il existait déjà un problème avant d'impliquer les intrinsèques d'un ordinateur moderne.
Les résultats finaux
Toutefois; cela ne veut pas dire que ces nouvelles questions n'ont pas d'importance parce qu'elles le sont elles-mêmes et qu'elles jouent un rôle après tout. Ils ont un impact sur les procédures et les performances globales et cela est évident avec les différents graphiques et évaluations de beaucoup de ceux qui ont donné leur (s) réponse (s) et / ou commentaire (s).
Si vous avez fait attention à l'analogie entre le Bosset les deux travailleurs A& Bqui ont dû aller chercher des packages à partir de C& Drespectivement et en considérant les notations mathématiques des deux algorithmes en question; vous pouvez voir sans l'implication du matériel informatique et des logiciels Case 2est environ 60%plus rapide que Case 1.
Lorsque vous regardez les graphiques et les graphiques après que ces algorithmes ont été appliqués à du code source, compilés, optimisés et exécutés via le système d'exploitation pour effectuer leurs opérations sur un matériel donné, vous pouvez même voir un peu plus de dégradation entre les différences dans ces algorithmes. 
Si l' Dataensemble est assez petit, il peut ne pas sembler si mauvais d'une différence au premier abord. Cependant, étant donné qu'il Case 1est à peu près 60 - 70%plus lent que Case 2nous pouvons regarder la croissance de cette fonction en termes de différences dans les exécutions de temps:
DeltaTimeDifference approximately = Loop1(time) - Loop2(time)
//where 
Loop1(time) = Loop2(time) + (Loop2(time)*[0.6,0.7]) // approximately
// So when we substitute this back into the difference equation we end up with 
DeltaTimeDifference approximately = (Loop2(time) + (Loop2(time)*[0.6,0.7])) - Loop2(time)
// And finally we can simplify this to
DeltaTimeDifference approximately = [0.6,0.7]*Loop2(time)
Cette approximation est la différence moyenne entre ces deux boucles à la fois algorithmiquement et les opérations machine impliquant des optimisations logicielles et des instructions machine. 
Lorsque l'ensemble de données croît de façon linéaire, la différence de temps entre les deux augmente également. L'algorithme 1 a plus de récupérations que l'algorithme 2, ce qui est évident lorsque le Bossdoit parcourir la distance maximale entre Aet Cpour chaque itération après la première itération tandis que l'algorithme 2 Bossdoit parcourir Aune fois puis après avoir terminé, Ail doit voyager une distance maximale une seule fois pour aller de Aà C.
Essayer de se Bossconcentrer sur deux choses similaires à la fois et de les jongler d'avant en arrière au lieu de se concentrer sur des tâches consécutives similaires va le mettre en colère à la fin de la journée car il a dû voyager et travailler deux fois plus. Par conséquent, ne perdez pas la portée de la situation en laissant votre patron entrer dans un goulot d'étranglement interpolé parce que le conjoint et les enfants du patron ne l'apprécieraient pas.
Amendement: principes de conception en génie logiciel 
- La différence Local Stacket les Heap Allocatedcalculs au sein de l'itératif pour les boucles et la différence entre leurs usages, leurs efficacités et leur efficacité - 
L'algorithme mathématique que j'ai proposé ci-dessus s'applique principalement aux boucles qui effectuent des opérations sur les données allouées sur le tas. 
- Opérations de pile consécutives:
- Si les boucles effectuent des opérations sur les données localement dans un seul bloc de code ou portée qui se trouve dans le cadre de la pile, cela s'appliquera toujours, mais les emplacements de mémoire sont beaucoup plus proches où ils sont généralement séquentiels et la différence de distance parcourue ou de temps d'exécution est presque négligeable. Puisqu'aucune allocation n'est effectuée dans le tas, la mémoire n'est pas dispersée et la mémoire n'est pas récupérée via RAM. La mémoire est généralement séquentielle et relative au cadre de pile et au pointeur de pile.
 
- Lorsque des opérations consécutives sont effectuées sur la pile, un processeur moderne mettra en cache les valeurs répétitives et les adresses en conservant ces valeurs dans les registres de cache locaux. Le temps des opérations ou des instructions ici est de l'ordre de la nano-seconde.
 
 
- Opérations d'affectation de segments consécutives:
- Lorsque vous commencez à appliquer des allocations de tas et que le processeur doit récupérer les adresses mémoire lors d'appels consécutifs, selon l'architecture du CPU, du contrôleur de bus et des modules Ram, le temps des opérations ou de l'exécution peut être de l'ordre du micro à millisecondes. Par rapport aux opérations de pile en cache, elles sont assez lentes.
 
- Le CPU devra récupérer l'adresse mémoire de Ram et généralement tout ce qui se trouve sur le bus système est lent par rapport aux chemins de données internes ou aux bus de données au sein du CPU lui-même.
 
 
Ainsi, lorsque vous travaillez avec des données qui doivent se trouver sur le tas et que vous les parcourez en boucles, il est plus efficace de conserver chaque ensemble de données et ses algorithmes correspondants dans sa propre boucle unique. Vous obtiendrez de meilleures optimisations par rapport à la tentative de factoriser des boucles consécutives en mettant plusieurs opérations de différents ensembles de données qui sont sur le tas dans une seule boucle.
Il est correct de le faire avec les données qui sont sur la pile car elles sont fréquemment mises en cache, mais pas pour les données qui doivent avoir leur adresse mémoire interrogée à chaque itération.
C'est là que l'ingénierie logicielle et la conception de l'architecture logicielle entrent en jeu. C'est la capacité de savoir comment organiser vos données, de savoir quand mettre en cache vos données, de savoir quand allouer vos données sur le tas, de savoir comment concevoir et mettre en œuvre vos algorithmes, et de savoir quand et où les appeler.
Vous pourriez avoir le même algorithme qui appartient au même ensemble de données, mais vous voudrez peut-être une conception d'implémentation pour sa variante de pile et une autre pour sa variante allouée au tas juste en raison du problème ci-dessus qui est vu de son O(n) complexité de l'algorithme lors du travail avec le tas.
D'après ce que j'ai remarqué au fil des ans, beaucoup de gens ne prennent pas ce fait en considération. Ils auront tendance à concevoir un algorithme qui fonctionne sur un ensemble de données particulier et ils l'utiliseront indépendamment de l'ensemble de données mis en cache localement sur la pile ou s'il a été alloué sur le tas.
Si vous voulez une véritable optimisation, oui, cela pourrait ressembler à une duplication de code, mais pour généraliser, il serait plus efficace d'avoir deux variantes du même algorithme. Un pour les opérations de pile et l'autre pour les opérations de tas qui sont effectuées dans des boucles itératives!
Voici un pseudo exemple: deux structures simples, un algorithme.
struct A {
    int data;
    A() : data{0}{}
    A(int a) : data{a}{} 
};
struct B {
    int data;
    B() : data{0}{}
    A(int b) : data{b}{}
}                
template<typename T>
void Foo( T& t ) {
    // do something with t
}
// some looping operation: first stack then heap.
// stack data:
A dataSetA[10] = {};
B dataSetB[10] = {};
// For stack operations this is okay and efficient
for (int i = 0; i < 10; i++ ) {
   Foo(dataSetA[i]);
   Foo(dataSetB[i]);
}
// If the above two were on the heap then performing
// the same algorithm to both within the same loop
// will create that bottleneck
A* dataSetA = new [] A();
B* dataSetB = new [] B();
for ( int i = 0; i < 10; i++ ) {
    Foo(dataSetA[i]); // dataSetA is on the heap here
    Foo(dataSetB[i]); // dataSetB is on the heap here
} // this will be inefficient.
// To improve the efficiency above, put them into separate loops... 
for (int i = 0; i < 10; i++ ) {
    Foo(dataSetA[i]);
}
for (int i = 0; i < 10; i++ ) {
    Foo(dataSetB[i]);
}
// This will be much more efficient than above.
// The code isn't perfect syntax, it's only psuedo code
// to illustrate a point.
C'est à cela que je faisais référence en ayant des implémentations distinctes pour les variantes de pile par rapport aux variantes de tas. Les algorithmes eux-mêmes n'ont pas trop d'importance, ce sont les structures en boucle que vous allez utiliser dans ce cas.