Mengapa dalam siklus yang terpisah suplemen stigma jauh lebih cepat daripada dalam siklus gabungan?

Misalkan titik a1 , b1 , c1 dan d1 untuk menumpuk memori, dan kode numerik saya memiliki loop utama berikut.

 const int n = 100000; for (int j = 0; j < n; j++) { a1[j] += b1[j]; c1[j] += d1[j]; } 

Loop ini dijalankan 10.000 kali melalui loop eksternal lain for . Untuk mempercepatnya, saya mengubah kode menjadi:

 for (int j = 0; j < n; j++) { a1[j] += b1[j]; } for (int j = 0; j < n; j++) { c1[j] += d1[j]; } 

Dikompilasi pada MS Visual C ++ 10.0 dengan optimasi penuh dan SSE2 diaktifkan untuk 32-bit pada Intel Core 2 Duo (x64), contoh pertama membutuhkan 5,5 detik, dan contoh dengan loop ganda hanya membutuhkan waktu 1,9 detik. Pertanyaan saya adalah: (Silakan merujuk ke pertanyaan saya yang sudah saya bahas di bawah)

PS: Saya tidak yakin apakah ini akan membantu:

Pembongkaran untuk siklus pertama pada dasarnya terlihat seperti ini (blok ini diu>

 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] 

Setiap loop dalam contoh loop ganda membuat kode ini (blok berikut diu>

 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 

Pertanyaan itu ternyata tidak relevan, karena perilaku sangat bergantung pada ukuran array (n) dan cache CPU. Jadi, jika ada minat lebih lanjut, saya u>

Bisakah Anda memberikan beberapa wawasan terperinci ke detail yang mengarah ke perilaku cache yang berbeda, seperti yang ditunjukkan pada lima area pada grafik berikut?

Menarik juga untuk menunjukkan perbedaan antara arsitektur CPU dan cache, memberikan jadwal yang sama untuk CPU ini.

PPS: di sini adalah kode lengkap. Ini menggunakan TBB Tick_Count untuk menyinkronkan dengan resolusi yang lebih tinggi, yang dapat dinonaktifkan tanpa menentukan TBB_TIMING :

 #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; } } 

(Menunjukkan FLOP / s untuk nilai n berbeda.)

2019

2073
17 дек. Johannes Gerer ditetapkan pada 17 Desember 2011-12-17 23:40 '11 pada 23:40 2011-12-17 23:40
@ 10 jawaban

Setelah analisis lebih lanjut tentang ini, saya percaya bahwa ini (setidaknya sebagian) disebabkan oleh penyelarasan dari empat petunjuk. Ini akan menyebabkan beberapa konflik cache / jalur.

Jika saya mengerti benar bagaimana Anda mengalokasikan array Anda, mereka kemungkinan besar akan diselaraskan dengan string halaman .

Ini berarti bahwa semua panggilan Anda dalam setiap siklus akan jatuh ke dalam file cache yang sama. Namun, prosesor Intel untuk beberapa waktu memiliki asosiasi cache L1 8 arah. Namun sebenarnya performanya tidak sepenuhnya seragam. Akses ke saluran 4 saluran masih lebih lambat dari dua arah.

Sunting: Sebenarnya, Anda memilih semua array secara terpisah. Biasanya, ketika alokasi besar seperti itu diminta, distributor meminta halaman baru dari OS. Oleh karena itu, ada kemungkinan besar bahwa pilihan besar akan ditampilkan dengan offset yang sama dari batas halaman.

Ini adalah kode tes:

 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; } 

Hasil tes:

EDIT: Hasil dari Core 2 Real Architecture:

2 x Intel Xeon X5482 Harpertown @ 3.2 GHz:

 #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 

komentar:

  • 6,206 detik dengan satu siklus dan 2,116 detik dengan dua siklus. Ini secara akurat mereproduksi hasil OP.

  • Dalam dua tes pertama, array dialokasikan secara terpisah. Anda akan melihat bahwa mereka semua memiliki perataan yang relatif relatif terhadap halaman.

  • Dalam dua tes kedua, array dikemas bersama untuk memecah keselarasan ini. Di sini Anda akan melihat bahwa kedua siklus lebih cepat. Selain itu, siklus kedua (ganda) sekarang lebih lambat, seperti yang biasanya Anda harapkan.

Sebagai catatan @Stephen Cannon dalam komentar, ada kemungkinan besar bahwa perataan ini menyebabkan kesalahan perataan dalam hal memuat / penyimpanan atau cache. Saya memikirkannya dan mengetahui bahwa Intel sebenarnya memiliki penghitung perangkat keras untuk menghaluskan alamat parsial :

http://software.intel.com/sites/products/documentation/doclib/stdxe/2013/~amplifierxe/pmw_dp/events/partial_address_alias.html


5 Wilayah - Penjelasan

Wilayah 1:

Itu mudah. Kumpulan data sangat kecil sehingga biaya overhead seperti siklus dan percabangan berlaku.

Wilayah 2:

Di sini, ketika ukuran data meningkat, jumlah overhead relatif berkurang, dan kinerja "jenuh". Di sini, dua siklus lebih lambat, karena memiliki dua kali lebih banyak benang dan cabang.

Saya tidak yakin apa yang terjadi di sini ... Penyelarasan masih dapat berpengaruh, karena Agner Fog menyebutkan konflik cache bank . (Tautan ini berlaku untuk Sandy Bridge, tetapi gagasan itu harus berlaku untuk Core 2.)

Wilayah 3:

Pada titik ini, data tidak lagi sesuai dengan cache L1. Dengan demikian, kinerja dibatasi oleh bandwidth L1 ↔ L2.

Wilayah 4:

Penurunan kinerja dalam satu siklus adalah apa yang kami amati. Dan, seperti yang telah disebutkan, ini disebabkan oleh penyelarasan, yang (kemungkinan besar) menyebabkan pemblokiran alias palsu di blok pemuatan / penyimpanan prosesor.

Namun, agar kesalahan pemulusan terjadi, harus ada >

Wilayah 5:

Pada titik ini, tidak ada yang cocok dengan cache. Jadi, Anda terhubung dengan bandwidth memori.


2019

1585
18 дек. Jawabannya diberikan Mysticial 18 Des. 2011-12-18 00:17 '11 pada 0:17 2011-12-18 00:17

OK, jawaban yang benar pasti harus melakukan sesuatu dengan cache prosesor. Tetapi menggunakan argumen cache bisa sangat rumit, terutama tanpa data.

Ada banyak jawaban yang menyebabkan banyak diskusi, tetapi biarkan mereka menghadapi ini: masalah cache bisa sangat kompleks dan tidak satu dimensi. Mereka sangat tergantung pada ukuran data, jadi pertanyaan saya tidak adil: ternyata sangat menarik dalam grafik cache.

@ Jawaban mistis meyakinkan banyak orang (termasuk saya), mungkin karena dia adalah satu-satunya yang tampaknya mengandalkan fakta, tetapi itu hanya satu "titik data" kebenaran.

Itulah sebabnya saya menggabungkan tesnya (menggunakan distribusi kontinu atau terpisah) dan jawabannya @James Answer.

Grafik di bawah ini menunjukkan bahwa sebagian besar jawaban, dan terutama sebagian besar komentar atas pertanyaan dan jawaban, dapat dianggap sepenuhnya salah atau benar, tergantung pada skenario dan parameter tertentu yang digunakan.

Harap dicatat bahwa pertanyaan awal saya adalah n = 100.000 . Poin ini (kebetulan) menunjukkan perilaku khusus:

  • Ini memiliki perbedaan terbesar antara satu dan dua versi siklus (hampir tiga kali)

  • Ini adalah satu-satunya titik di mana loop tunggal (yaitu, distribusi kontinu) melebihi versi dua loop. (Ini memungkinkan respons mistis).

Hasil menggunakan data yang diinisialisasi:

2019

203
18 дек. Jawaban yang diberikan oleh Johannes Gerer 18 Des 2011-12-18 04:29 '11 pada 4:29 2011-12-18 04:29

Siklus kedua mencakup aktivitas cache yang jauh lebih sedikit, sehingga prosesor lebih mudah untuk mempertahankan persyaratan memori.

69
17 дек. Balasan yang diberikan Puppy 17 Des 2011-12-17 23:47 '11 pada 23:47 2011-12-17 23:47

Bayangkan Anda sedang mengerjakan mesin di mana n adalah nilai yang benar sehingga Anda dapat secara bersamaan menyimpan dua array Anda dalam memori, tetapi jumlah total memori yang tersedia melalui cache disk masih cukup untuk menyimpan keempatnya.

Dengan asumsi kebijakan caching LIFO sederhana, kode ini:

 for(int j=0;j<n;j++){ a[j] += b[j]; } for(int j=0;j<n;j++){ c[j] += d[j]; } 

pertama-tama akan menyebabkan a dan b dimuat ke dalam RAM, dan kemudian bekerja sepenuhnya ke dalam RAM. Ketika siklus kedua dimulai, c dan d kemudian dimuat dari disk ke dalam RAM dan berfungsi.

siklus lain

 for(int j=0;j<n;j++){ a[j] += b[j]; c[j] += d[j]; } 

akan menampilkan dua array dan satu halaman dalam dua lainnya setiap kali di sekitar loop . Ini jelas akan jauh lebih lambat.

Anda mungkin tidak melihat cache disk dalam pengujian Anda, tetapi Anda mungkin melihat efek samping dari beberapa bentuk cache lainnya.


Tampaknya ada sedikit kebingungan / kesalahpahaman di sini, jadi saya akan mencoba menjelaskan sedikit dengan contoh.

Katakan n = 2 dan kami bekerja dengan byte. Jadi, dalam skenario saya, kami hanya memiliki 4 byte RAM, dan sisa memori jauh lebih lambat (katakanlah, akses 100 kali lebih banyak).

Dengan asumsi kebijakan caching yang agak bodoh, jika satu byte tidak ada dalam cache, letakkan di sana dan dapatkan byte berikutnya ketika kita sedang menggunakannya, Anda mendapatkan skrip seperti ini:

  • Dengan

     for(int j=0;j<n;j++){ a[j] += b[j]; } for(int j=0;j<n;j++){ c[j] += d[j]; } 
  • kita men a[0] cache a[0] dan a[1] lalu b[0] dan b[1] dan mengatur a[0] = a[0] + b[0] ke cache - sekarang ada empat byte dalam cache, a[0], a[1] dan b[0], b[1] . Biaya = 100 + 100.

  • atur a[1] = a[1] + b[1] dalam cache. Biaya = 1 + 1.
  • U>c dan d .
  • Total Biaya = (100 + 100 + 1 + 1) * 2 = 404

  • Dengan

     for(int j=0;j<n;j++){ a[j] += b[j]; c[j] += d[j]; } 
  • kita men a[0] cache a[0] dan a[1] lalu b[0] dan b[1] dan mengatur a[0] = a[0] + b[0] ke cache - sekarang ada empat byte dalam cache, a[0], a[1] dan b[0], b[1] . Biaya = 100 + 100.

  • hapus a[0], a[1], b[0], b[1] dari cache dan cache c[0] dan c[1] lalu d[0] dan d[1] dan atur c[0] = c[0] + d[0] dalam cache. Biaya = 100 + 100.
  • Saya curiga Anda mulai melihat ke mana saya pergi.
  • Total Biaya = (100 + 100 + 100 + 100) * 2 = 800

Ini adalah skenario cache sampah klasik.

41
18 дек. Jawabannya diberikan oleh OldCurmudgeon 18 Des. 2011-12-18 04:36 '11 pada 4:36 2011-12-18 04:36

Ini bukan karena kode yang berbeda, tetapi karena caching: RAM lebih lambat dari register prosesor, dan memori cache ada di dalam CPU untuk menghindari penulisan RAM setiap kali variabel berubah. Tapi cache itu kecil, karena RAM, oleh karena itu, hanya menampilkan sebagian saja.

Kode pertama mengubah alamat memori jarak jauh, berganti-ganti dalam setiap siklus, sehingga membutuhkan terus-menerus membuang cache.

Kode kedua tidak berganti: hanya bergerak ke alamat yang berdekatan dua kali. Ini menyebabkan semua tugas diselesaikan dalam cache, membatalkannya hanya setelah memulai siklus kedua.

29
17 дек. Balas diberikan oleh Emilio Garavaglia pada 17 Des. 2011-12-17 23:49 '11 pada 11:49 PM 2011-12-17 23:49

Saya tidak bisa mengu>

Saya tidak tahu apakah kode pengujian yang salah yang harus disalahkan, atau apa, tetapi kedua metode ini berada dalam jarak 10% satu sama lain pada mesin saya menggunakan kode berikut, dan satu siklus biasanya sedikit lebih cepat dari dua - seperti yang Anda harapkan.

Ukuran array berkisar antara 2 ^ 16 hingga 2 ^ 24 menggunakan delapan siklus. Saya berhati-hati untuk menginisialisasi array asli sehingga tugas += tidak meminta FPU untuk menambahkan memori sampah, ditafsirkan sebagai ganda.

Saya bermain dengan berbagai skema, seperti menempatkan tugas b[j] , d[j] untuk InitToZero[j] di dalam siklus, serta menggunakan += b[j] = 1 dan += d[j] = 1 , dan Saya mendapat hasil yang cukup konsisten.

Seperti yang diharapkan, inisialisasi b dan d di dalam loop menggunakan InitToZero[j] memberi pendekatan gabungan keuntungan, karena mereka dilakukan dengan seksama sebelum menetapkan a dan c , tetapi masih dalam 10%. Cari tahu itu.

Perangkat Keras - Dell XPS 8500 dengan prosesor 3 Core i7 @ 3.4 GHz dan memori 8 GB. Untuk 2 ^ 16 hingga 2 ^ 24, menggunakan delapan siklus, waktu kumulatif adalah masing-masing 44.987 dan 40.965. Visual C ++ 2010 sepenuhnya dioptimalkan.

PS: Saya mengubah siklus hitung mundur menjadi nol, dan metode gabungannya sedikit lebih cepat. Menggaruk-garuk kepala Anda Perhatikan ukuran array yang baru dan jumlah siklus.

 // 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("\n %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("\n %6i miliseconds for separate array sizes %i and %i loops \n", (int)(clock()-start), ArraySz, LoopKnt); } printf("\n Cumulative combined array processing took %10.3f seconds", (dbl)(Cumulative_Combined/(dbl)CLOCKS_PER_SEC)); printf("\n 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; } 

Saya tidak yakin mengapa diputuskan bahwa MFLOPS adalah indikator yang relevan. Meskipun idenya adalah untuk fokus pada akses memori, jadi saya mencoba untuk meminimalkan waktu mengambang. Saya pergi ke += , tetapi saya tidak yakin mengapa.

Tugas >

18
30 дек. Jawabannya diberikan oleh user1899861 30 Des. 2012-12-30 04:34 '12 pada 4:34 2012-12-30 04:34

Ini karena prosesor tidak memiliki cache yang terlalu banyak (di mana ia harus menunggu data array datang dari chip RAM). Akan menarik untuk menyesuaikan ukuran array sepanjang waktu sehingga Anda melebihi ukuran level cache 1 (L1) dan kemudian level cache 2 (L2) dari prosesor Anda dan menghitung waktu yang dihabiskan untuk mengeksekusi kode terhadap ukuran array. Grafik tidak boleh >

15
17 дек. balasan yang diberikan oleh James 17 Des 2011-12-17 23:52 '11 pada 11:52 PM 2011-12-17 23:52

Loop pertama bergantian entri dalam setiap variabel. Yang kedua dan ketiga hanya membuat ukuran elemen kecil melompat.

Cobalah untuk menulis dua garis sejajar 20 salib dengan pena dan selembar, dipisahkan oleh 20 sentimeter. Cobalah untuk menyelesaikan satu dan kemudian satu baris lagi dan coba lagi dengan menekan tanda si>

13
17 авг. jawabannya diberikan Guillaume Kiz 17 Agustus. 2012-08-17 18:23 '12 pada 18:23 2012-08-17 18:23

Pertanyaan asli

Mengapa satu siklus jauh lebih lambat dari dua?


Kesimpulan:

Kasus 1 adalah masalah interpolasi klasik yang tidak efektif. Saya juga berpikir bahwa ini adalah salah satu alasan utama mengapa banyak arsitektur dan pengembang mesin telah selesai membuat dan merancang sistem multi-core dengan kemampuan untuk menjalankan aplikasi multi-threaded, serta pemrograman paralel.

Mempertimbangkan hal ini menggunakan pendekatan ini, tanpa mempengaruhi bagaimana perangkat keras, OS dan kompiler bekerja sama untuk menyoroti heap, yang meliputi bekerja dengan RAM, cache, file paging, dll.; Matematika yang mendasari algoritma ini menunjukkan kepada kita yang mana dari dua opsi ini adalah solusi terbaik. Kita dapat menggunakan analogi, di mana Boss atau Summation , yang akan menjadi For Loop , yang harus bergerak di antara pekerja A B , kita dapat dengan mudah melihat bahwa case 2 setidaknya 1/2 , seberapa cepat, jika tidak sedikit lagi, kasus 1 karena perbedaan jarak yang diperlukan untuk perjalanan, dan waktu yang dihabiskan antara pekerja. Matematika ini hampir virtual dan sangat cocok dengan Bench Mark Times dan perbedaan dalam instruksi perakitan.

Sekarang saya akan mulai menjelaskan cara kerjanya di bawah.


Mengevaluasi masalah

Kode OP:

 const int n=100000; for(int j=0;j<n;j++){ a1[j] += b1[j]; c1[j] += d1[j]; } 

Juga

 for(int j=0;j<n;j++){ a1[j] += b1[j]; } for(int j=0;j<n;j++){ c1[j] += d1[j]; } 

Pertimbangan