Why are elementwise additions much faster in separate loops than in a combined loop?
我们
1 2 3 4 5 6 | const int n = 100000; for (int j = 0; j < n; j++) { a1[j] += b1[j]; c1[j] += d1[j]; } |
这个环是executed 10000时报通过另一个
1 2 3 4 5 6 7 | for (int j = 0; j < n; j++) { a1[j] += b1[j]; } for (int j = 0; j < n; j++) { c1[j] += d1[j]; } |
编译的多发性硬化的Visual C + +和10.0全优化和SSE2 ''enabled for是一个32位英特尔酷睿2核心(64),第一个实例把5.5和nbsp;秒和双回路以把只读1.9和nbsp;秒。我的问题是:(请参阅我的提问的问题的? </P >
PS:我是不担心,如果这helps: </P >
disassembly的第一环,基本上就像这样(这是五块重复约时报》在全程序): </P >
1 2 3 4 5 6 7 8 9 10 11 | movsd xmm0,mmword ptr [edx+18h] addsd xmm0,mmword ptr [ecx+20h] movsd mmword ptr [ecx+20h],xmm0 movsd xmm0,mmword ptr [esi+10h] addsd xmm0,mmword ptr [eax+30h] movsd mmword ptr [eax+30h],xmm0 movsd xmm0,mmword ptr [edx+20h] addsd xmm0,mmword ptr [ecx+28h] movsd mmword ptr [ecx+28h],xmm0 movsd xmm0,mmword ptr [esi+18h] addsd xmm0,mmword ptr [eax+38h] |
每个环的双回路以产生的这段代码(下面的是重复上面的三块:《纽约时报》) </P >
1 2 3 4 5 6 7 8 9 10 11 | addsd xmm0,mmword ptr [eax+28h] movsd mmword ptr [eax+28h],xmm0 movsd xmm0,mmword ptr [ecx+20h] addsd xmm0,mmword ptr [eax+30h] movsd mmword ptr [eax+30h],xmm0 movsd xmm0,mmword ptr [ecx+28h] addsd xmm0,mmword ptr [eax+38h] movsd mmword ptr [eax+38h],xmm0 movsd xmm0,mmword ptr [ecx+30h] addsd xmm0,mmword ptr [eax+40h] movsd mmword ptr [eax+40h],xmm0 |
该问题被淘汰是不relevance,为行为severely depends的大小之翼(N)和CPU的缓存。所以,如果有进一步的兴趣,我rephrase的问题: </P >
你可以提供一些洞察入固的细节,那铅对不同的高速缓存的行为作为插图由五个地区是下面的图吗? </P >
它也可能是有趣的点出的差异之间的CPU /缓存architectures,用类似的方法提供一个图,这些CPU。 </P >
PPS:这里是全码。它使用的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | #include <iostream> #include <iomanip> #include <cmath> #include <string> //#define TBB_TIMING #ifdef TBB_TIMING #include <tbb/tick_count.h> using tbb::tick_count; #else #include <time.h> #endif using namespace std; //#define preallocate_memory new_cont enum { new_cont, new_sep }; double *a1, *b1, *c1, *d1; void allo(int cont, int n) { switch(cont) { case new_cont: a1 = new double[n*4]; b1 = a1 + n; c1 = b1 + n; d1 = c1 + n; break; case new_sep: a1 = new double[n]; b1 = new double[n]; c1 = new double[n]; d1 = new double[n]; break; } for (int i = 0; i < n; i++) { a1[i] = 1.0; d1[i] = 1.0; c1[i] = 1.0; b1[i] = 1.0; } } void ff(int cont) { switch(cont){ case new_sep: delete[] b1; delete[] c1; delete[] d1; case new_cont: delete[] a1; } } double plain(int n, int m, int cont, int loops) { #ifndef preallocate_memory allo(cont,n); #endif #ifdef TBB_TIMING tick_count t0 = tick_count::now(); #else clock_t start = clock(); #endif if (loops == 1) { for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++){ a1[j] += b1[j]; c1[j] += d1[j]; } } } else { for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { a1[j] += b1[j]; } for (int j = 0; j < n; j++) { c1[j] += d1[j]; } } } double ret; #ifdef TBB_TIMING tick_count t1 = tick_count::now(); ret = 2.0*double(n)*double(m)/(t1-t0).seconds(); #else clock_t end = clock(); ret = 2.0*double(n)*double(m)/(double)(end - start) *double(CLOCKS_PER_SEC); #endif #ifndef preallocate_memory ff(cont); #endif return ret; } void main() { freopen("C:\\test.csv","w", stdout); char *s =""; string na[2] ={"new_cont","new_sep"}; cout <<"n"; for (int j = 0; j < 2; j++) for (int i = 1; i <= 2; i++) #ifdef preallocate_memory cout << s << i <<"_loops_" << na[preallocate_memory]; #else cout << s << i <<"_loops_" << na[j]; #endif cout << endl; long long nmax = 1000000; #ifdef preallocate_memory allo(preallocate_memory, nmax); #endif for (long long n = 1L; n < nmax; n = max(n+1, long long(n*1.2))) { const long long m = 10000000/n; cout << n; for (int j = 0; j < 2; j++) for (int i = 1; i <= 2; i++) cout << s << plain(n, m, j, i); cout << endl; } } |
(它的交往中触发器/ s的方法的不同的值
</P >
进一步分析后,我认为这是(至少部分)由四个指针的数据对齐引起的。这将导致某种程度的缓存库/路径冲突。
如果我对您如何分配数组进行了正确的猜测,那么它们很可能与页面行对齐。
这意味着每个循环中的所有访问都将以相同的缓存方式进行。然而,英特尔处理器有一段时间具有8路L1缓存关联性。但实际上,演出并不完全一致。访问4路仍然比访问2路慢。
编辑:事实上,它看起来像是单独分配所有数组。通常,当请求如此大的分配时,分配程序将从操作系统请求新的页面。因此,大量分配很可能出现在与页面边界相同的偏移量处。
测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | int main(){ const int n = 100000; #ifdef ALLOCATE_SEPERATE double *a1 = (double*)malloc(n * sizeof(double)); double *b1 = (double*)malloc(n * sizeof(double)); double *c1 = (double*)malloc(n * sizeof(double)); double *d1 = (double*)malloc(n * sizeof(double)); #else double *a1 = (double*)malloc(n * sizeof(double) * 4); double *b1 = a1 + n; double *c1 = b1 + n; double *d1 = c1 + n; #endif // Zero the data to prevent any chance of denormals. memset(a1,0,n * sizeof(double)); memset(b1,0,n * sizeof(double)); memset(c1,0,n * sizeof(double)); memset(d1,0,n * sizeof(double)); // Print the addresses cout << a1 << endl; cout << b1 << endl; cout << c1 << endl; cout << d1 << endl; clock_t start = clock(); int c = 0; while (c++ < 10000){ #if ONE_LOOP for(int j=0;j<n;j++){ a1[j] += b1[j]; c1[j] += d1[j]; } #else for(int j=0;j<n;j++){ a1[j] += b1[j]; } for(int j=0;j<n;j++){ c1[j] += d1[j]; } #endif } clock_t end = clock(); cout <<"seconds =" << (double)(end - start) / CLOCKS_PER_SEC << endl; system("pause"); return 0; } |
基准结果:
编辑:实际核心2体系结构计算机上的结果:2 x Intel Xeon X5482 [email protected] GHz:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #define ALLOCATE_SEPERATE #define ONE_LOOP 00600020 006D0020 007A0020 00870020 seconds = 6.206 #define ALLOCATE_SEPERATE //#define ONE_LOOP 005E0020 006B0020 00780020 00850020 seconds = 2.116 //#define ALLOCATE_SEPERATE #define ONE_LOOP 00570020 00633520 006F6A20 007B9F20 seconds = 1.894 //#define ALLOCATE_SEPERATE //#define ONE_LOOP 008C0020 00983520 00A46A20 00B09F20 seconds = 1.993 |
观察:
一圈6.206秒,两圈2.116秒。这准确地复制了OP的结果。
在前两个测试中,数组是单独分配的。您会注意到,它们相对于页面都具有相同的对齐方式。
在第二个测试中,数组被打包在一起以破坏对齐。在这里你会发现两个循环都更快。此外,第二个(双)循环现在是您通常期望的较慢的循环。
正如@stephen cannon在评论中指出的那样,这种对齐很可能会导致加载/存储单元或缓存中出现假别名。我搜索了一下,发现英特尔实际上有一个硬件计数器,用于部分地址别名暂停:
http://software.intel.com/sites/products/documentation/doclib/stdxe/2013/~amplifierxe/pmw_-dp/events/partial_-address_-alias.html
5个地区-解释区域1:
这个很容易。数据集太小,性能由循环和分支等开销控制。
区域2:
这里,随着数据大小的增加,相对开销的数量下降,性能"饱和"。这里,两个循环的速度较慢,因为它的循环和分支开销是原来的两倍。
我不确定这里到底发生了什么…当Agner Fog提到缓存库冲突时,对齐仍然会起到作用。(该链接是关于Sandy Bridge的,但该想法仍应适用于核心2。)
区域3:
此时,数据不再适合一级缓存。因此,性能受到l1<->l2缓存带宽的限制。
区域4:
我们正在观察单循环中的性能下降。如前所述,这是由于对齐(很可能)导致处理器加载/存储单元中出现假别名暂停。
但是,为了发生假别名,数据集之间必须有足够大的跨度。这就是为什么你在3区没有看到这个。
区域5:
此时,缓存中没有适合的内容。所以你受内存带宽的限制。
好的,正确的答案肯定与CPU缓存有关。但是使用cache参数可能非常困难,尤其是没有数据时。
有很多答案,导致了很多讨论,但让我们面对现实:缓存问题可能非常复杂,而且不是一维的。它们很大程度上依赖于数据的大小,所以我的问题是不公平的:在缓存图中,这是一个非常有趣的点。
@神秘主义的答案使很多人(包括我)信服,可能是因为它是唯一一个似乎依赖事实的答案,但它只是真理的一个"数据点"。
这就是为什么我结合了他的测试(使用连续的和单独的分配)和@james'答案的建议。
下面的图表显示,根据所使用的具体场景和参数,大多数答案,尤其是对问题和答案的大多数评论可能被认为是完全错误或真实的。
注意,我最初的问题是n=100000。这一点(偶然)表现出特殊行为:
它在单环版本和双环版本之间具有最大的差异(几乎是三倍)
这是唯一的一点,一个循环(即连续分配)胜过两个循环版本。(这使得神秘主义的答案成为可能。)
使用初始化数据的结果:
使用未初始化数据的结果(这是神秘测试的结果):
这是一个很难解释的问题:初始化的数据,它被分配一次,并对每个不同向量大小的以下测试用例重复使用:
提议堆栈溢出上的每一个低级性能相关问题都需要为整个缓存相关数据大小范围提供mflops信息!每个人都浪费时间去思考答案,尤其是在没有这些信息的情况下与他人讨论。
第二个循环涉及的缓存活动要少得多,因此处理器更容易跟上内存需求。
假设您正在一台机器上工作,其中
假设有一个简单的后进先出缓存策略,此代码:
1 2 3 4 5 6 | for(int j=0;j<n;j++){ a[j] += b[j]; } for(int j=0;j<n;j++){ c[j] += d[j]; } |
首先会导致
另一个循环
1 2 3 4 | for(int j=0;j<n;j++){ a[j] += b[j]; c[j] += d[j]; } |
每次循环时都会调出两个数组,并在另外两个数组中分页。这显然要慢得多。
您可能在测试中没有看到磁盘缓存,但您可能看到了其他缓存形式的副作用。
这里似乎有一点困惑/误解,所以我将尝试用一个例子来阐述一下。
比如说
假设一个相当愚蠢的缓存策略,如果字节不在缓存中,那么将其放在缓存中,并在我们进行缓存时获取以下字节,您将得到类似这样的场景:
用
1
2
3
4
5
6for(int j=0;j<n;j++){
a[j] += b[j];
}
for(int j=0;j<n;j++){
c[j] += d[j];
}缓存
a[0] 和a[1] ,然后缓存b[0] 和b[1] ,并在缓存中设置a[0] = a[0] + b[0] -现在缓存中有四个字节,a[0], a[1] 和b[0], b[1] 。成本=100+100。- 在缓存中设置
a[1] = a[1] + b[1] 。成本=1+1。 - 对
c 和d 重复上述步骤。 总成本=
(100 + 100 + 1 + 1) * 2 = 404 。用
1
2
3
4for(int j=0;j<n;j++){
a[j] += b[j];
c[j] += d[j];
}缓存
a[0] 和a[1] ,然后缓存b[0] 和b[1] 并在缓存中设置a[0] = a[0] + b[0] —现在缓存中有四个字节,a[0], a[1] 和b[0], b[1] 。成本=100+100。- 从缓存中弹出
a[0], a[1], b[0], b[1] ,缓存c[0] 和c[1] ,然后将d[0] 和d[1] 设置到缓存中。成本=100+100。 - 我怀疑你开始明白我要去哪里了。
- 总成本=
(100 + 100 + 100 + 100) * 2 = 800 。
这是一个经典的高速缓存重击场景。
这不是因为不同的代码,而是因为缓存:RAM比CPU寄存器慢,并且CPU内有缓存内存,以避免每次变量变化时都写入RAM。但是缓存并不像RAM那么大,因此它只映射其中的一小部分。
第一个代码修改远程内存地址,在每个循环中交替使用它们,因此需要不断地使缓存失效。
第二个代码不交替:它只在相邻地址上流动两次。这使得所有的作业都在缓存中完成,只有在第二个循环开始后才会失效。
我不能复制这里讨论的结果。
我不知道是否应该怪糟糕的基准代码,或者是什么,但是在我的机器上,这两种方法使用以下代码的比例都在10%以内,而且一个循环通常比两个稍微快一点——正如您所期望的那样。
数组大小从2^16到2^24不等,使用八个循环。我很小心地初始化了源数组,所以
我玩了各种各样的方案,比如把
如您所料,使用
硬件是Dell XPS 8500,具有第3代核心[email protected] GHz和8 GB内存。对于2^16到2^24,使用八个循环,累计时间分别为44.987和40.965。Visual C++ 2010,完全优化。
PS:我把循环数改为倒数为零,合并后的方法稍微快一点。抓我的头。注意新的数组大小和循环计数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | // MemBufferMystery.cpp : Defines the entry point for the console application. // #include"stdafx.h" #include <iostream> #include <cmath> #include <string> #include <time.h> #define dbl double #define MAX_ARRAY_SZ 262145 //16777216 // AKA (2^24) #define STEP_SZ 1024 // 65536 // AKA (2^16) int _tmain(int argc, _TCHAR* argv[]) { long i, j, ArraySz = 0, LoopKnt = 1024; time_t start, Cumulative_Combined = 0, Cumulative_Separate = 0; dbl *a = NULL, *b = NULL, *c = NULL, *d = NULL, *InitToOnes = NULL; a = (dbl *)calloc( MAX_ARRAY_SZ, sizeof(dbl)); b = (dbl *)calloc( MAX_ARRAY_SZ, sizeof(dbl)); c = (dbl *)calloc( MAX_ARRAY_SZ, sizeof(dbl)); d = (dbl *)calloc( MAX_ARRAY_SZ, sizeof(dbl)); InitToOnes = (dbl *)calloc( MAX_ARRAY_SZ, sizeof(dbl)); // Initialize array to 1.0 second. for(j = 0; j< MAX_ARRAY_SZ; j++) { InitToOnes[j] = 1.0; } // Increase size of arrays and time for(ArraySz = STEP_SZ; ArraySz<MAX_ARRAY_SZ; ArraySz += STEP_SZ) { a = (dbl *)realloc(a, ArraySz * sizeof(dbl)); b = (dbl *)realloc(b, ArraySz * sizeof(dbl)); c = (dbl *)realloc(c, ArraySz * sizeof(dbl)); d = (dbl *)realloc(d, ArraySz * sizeof(dbl)); // Outside the timing loop, initialize // b and d arrays to 1.0 sec for consistent += performance. memcpy((void *)b, (void *)InitToOnes, ArraySz * sizeof(dbl)); memcpy((void *)d, (void *)InitToOnes, ArraySz * sizeof(dbl)); start = clock(); for(i = LoopKnt; i; i--) { for(j = ArraySz; j; j--) { a[j] += b[j]; c[j] += d[j]; } } Cumulative_Combined += (clock()-start); printf(" %6i miliseconds for combined array sizes %i and %i loops", (int)(clock()-start), ArraySz, LoopKnt); start = clock(); for(i = LoopKnt; i; i--) { for(j = ArraySz; j; j--) { a[j] += b[j]; } for(j = ArraySz; j; j--) { c[j] += d[j]; } } Cumulative_Separate += (clock()-start); printf(" %6i miliseconds for separate array sizes %i and %i loops ", (int)(clock()-start), ArraySz, LoopKnt); } printf(" Cumulative combined array processing took %10.3f seconds", (dbl)(Cumulative_Combined/(dbl)CLOCKS_PER_SEC)); printf(" Cumulative seperate array processing took %10.3f seconds", (dbl)(Cumulative_Separate/(dbl)CLOCKS_PER_SEC)); getchar(); free(a); free(b); free(c); free(d); free(InitToOnes); return 0; } |
我不知道为什么要确定mflops是一个相关的度量标准。我认为我的想法是把重点放在内存访问上,所以我尽量减少浮点计算时间。我离开了江户区,但我不知道为什么。
没有计算的直接分配将是对内存访问时间的一个更干净的测试,并且将创建一个不考虑循环计数的统一测试。也许我在谈话中遗漏了一些东西,但值得再想一想。如果在分配中忽略了加号,则累计时间在31秒时几乎相同。
这是因为CPU没有那么多的缓存未命中(它必须等待来自RAM芯片的阵列数据)。对您来说,连续地调整数组的大小是很有趣的,这样您就可以超过CPU的1级缓存(l1)和2级缓存(l2)的大小,并绘制代码根据数组大小执行所需的时间。图不应该像你想象的那样是直线。
第一个循环交替写入每个变量。第二个和第三个只进行元素大小的小跳跃。
试着用一支笔和一张纸,用20厘米分开,写两条20个十字的平行线。试着先写完一行再写完另一行,然后在每一行交替地写一个十字。
最初的问题好的。
Why is one loop so much slower than two loops?
Ok.
结论:好的。
案例1是一个典型的插值问题,恰好是一个效率低下的问题。我还认为,这是许多机器体系结构和开发人员最终构建和设计具有多线程应用程序和并行编程能力的多核系统的主要原因之一。好的。
从这种方法看它,而不涉及硬件、操作系统和编译器如何一起工作堆堆分配,包括处理RAM、缓存、页文件等等;在这些算法的基础上的数学告诉我们这两者中哪一个是更好的解决方案。我们可以使用一个类比,其中一个 下面我将开始解释所有这些是如何工作的。好的。 评估问题好的。 OP的代码:好的。 和好的。 对价好的。 考虑到OP关于for循环的2个变体的原始问题,以及他对缓存行为的修正问题,以及许多其他优秀的答案和有用的评论;我想尝试通过对这种情况和问题采取不同的方法来做一些不同的事情。好的。 途径好的。 考虑到这两个循环以及所有关于缓存和页面归档的讨论,我想从另一个角度来看待这个问题。一种不涉及缓存和页面文件,也不涉及为分配内存而执行的方法,实际上这种方法甚至根本不涉及实际的硬件或软件。好的。 透视好的。 在看了一会儿代码之后,问题是什么以及产生问题的原因变得非常明显。让我们把它分解成一个算法问题,从使用数学符号的角度来看,然后对数学问题和算法进行类比。好的。 我们所知道的好的。 我们知道他的循环将运行100000次。我们还知道 我们不知道的好的。 我们不知道每种情况需要多长时间,除非我们设置计数器并进行基准测试。然而,基准点已经包括在最初的问题和一些答案和评论中,我们可以看到这两者之间的显著差异,这就是这个问题对这个问题的整体推理,并从回答开始。好的。 让我们调查一下好的。 很明显,许多人已经通过查看堆分配、基准测试、RAM、缓存和页面文件来完成了这项工作。查看特定的数据点和特定的迭代索引也包括在内,关于这个特定问题的各种对话让许多人开始质疑与之相关的其他事情。那么,我们如何开始用数学算法和类比来看待这个问题呢?我们先做几个断言!然后我们从那里构建了我们的算法。好的。 我们的主张:好的。 算法:好的。 第一种情况:只有一个求和,但有两个独立的函数调用。好的。 第二种情况:两个求和,但每个求和都有自己的函数调用。好的。 如果你注意到 通过第一种情况的迭代, 类比总结好的。 在第二种情况下,我们看到的几乎是优化,因为两个for循环具有相同的精确签名,但这不是真正的问题。问题不在于 把 在每个呼叫单的第一个案例中, 在第二种情况下, 既然我们正在处理一个8字节指针和堆分配,那么让我们在这里考虑这个问题。假设 测试用例:好的。 第一种情况:在第一次迭代中, 第二种情况: 行驶距离的差异好的。 任意值的比较好的。 我们可以很容易地看到,600远低于1000万。现在这并不准确,因为我们不知道在每次迭代中哪个RAM地址,哪个缓存或页面文件之间的实际距离差异,每个调用都是由许多其他未看到的变量造成的,但这只是对需要注意的情况的一个评估,并试图从最坏的情况来看。好的。 因此,从这些数字来看,算法1的速度应该比算法2慢99%;然而,这只是算法的 观察:两种算法的区别好的。 在这种情况下,它是正在进行的工作的过程的结构,并且它确实表明,情况2比具有类似函数声明和定义的部分优化更有效,因为只有名称不同的变量。我们还可以看到,案例1中的总行驶距离远大于案例2中的总行驶距离,我们可以考虑这两种算法之间的时间系数。案例1比案例2有更多的工作要做。这一点也在两起案件之间显示的 行动组修改了问题好的。
EDIT: The question turned out to be of no relevance, as the behavior severely depends on the sizes of the arrays (n) and the CPU cache. So if there is further interest, I rephrase the question:
Ok. 好的。
Could you provide some solid insight into the details that lead to the different cache behaviors as illustrated by the five regions on the following graph?
Ok. 好的。
It might also be interesting to point out the differences between CPU/cache architectures, by providing a similar graph for these CPUs.
Ok. 关于这些问题好的。 正如我毫无疑问地证明的那样,即使在涉及到硬件和软件之前,也存在一个潜在的问题。现在关于内存和缓存以及页面文件等的管理,它们都是在一组集成的系统中工作的: 最后的结果好的。 然而,并不是说这些新问题并不重要,因为它们本身就是,而且它们毕竟扮演了一个角色。它们确实会影响程序和整体性能,这一点从许多给出答案和/或评论的人的各种图表和评估中可以明显看出。如果你注意到 现在,如果"数据"集相当小,那么一开始看起来差异并不是那么糟糕,但是由于 这个近似值是这两个循环之间的平均差,无论是算法上的还是涉及软件优化和机器指令的机器操作。所以当数据集呈线性增长时,两者之间的时间差也是如此。算法1比算法2具有更多的取数,这一点很明显,当 因此,让 它可以是旧C++和优化。在我的电脑上,我获得了几乎相同的速度: 单回路:1.577 ms 双回路:1.507 ms 我在带有16 GB RAM的E5-1620 3.5 GHz处理器上运行Visual Studio 2015。
2
3
4
5
6
for(int j=0;j<n;j++){
a1[j] += b1[j];
c1[j] += d1[j];
}
2
3
4
5
6
a1[j] += b1[j];
}
for(int j=0;j<n;j++){
c1[j] += d1[j];
}
2
3
F1() = { f(a) = f(a) + f(b); }
F2() = { f(c) = f(c) + f(d); }
2
3
4
5
F1() = { f(a) = f(a) + f(b); }
Sum2 n=1 : [1,100000] = F1();
F1() = { f(c) = f(c) + f(d); }
2
3
4
5
6
7
8
9
10
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;
2
3
4
5
6
7
//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)