Luchian, bu davranışın neden olduğunu açıklıyor , ancak bu soruna olası bir çözümü göstermenin ve aynı zamanda önbellek kayıtsız algoritmalar hakkında biraz göstermenin iyi bir fikir olacağını düşündüm.
Algoritmanız temel olarak şunları yapar:
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
A[j][i] = A[i][j];
Bu da modern bir CPU için korkunç. Bir çözüm, önbellek sisteminizle ilgili ayrıntıları bilmek ve bu sorunları önlemek için algoritmayı değiştirmektir. Bu ayrıntıları bildiğiniz sürece harika çalışıyor .. özellikle taşınabilir değil.
Bundan daha iyisini yapabilir miyiz? Evet yapabiliriz: Bu soruna genel bir yaklaşım , adından da anlaşılacağı gibi, belirli önbellek boyutlarına bağımlı olmaktan kaçınan önbellek habersiz algoritmalardır [1]
Çözüm şöyle görünecektir:
void recursiveTranspose(int i0, int i1, int j0, int j1) {
int di = i1 - i0, dj = j1 - j0;
const int LEAFSIZE = 32; // well ok caching still affects this one here
if (di >= dj && di > LEAFSIZE) {
int im = (i0 + i1) / 2;
recursiveTranspose(i0, im, j0, j1);
recursiveTranspose(im, i1, j0, j1);
} else if (dj > LEAFSIZE) {
int jm = (j0 + j1) / 2;
recursiveTranspose(i0, i1, j0, jm);
recursiveTranspose(i0, i1, jm, j1);
} else {
for (int i = i0; i < i1; i++ )
for (int j = j0; j < j1; j++ )
mat[j][i] = mat[i][j];
}
}
Biraz daha karmaşık, ancak kısa bir test VS2010 x64 sürümü ile eski e8400'ümde oldukça ilginç bir şey gösteriyor, test kodu MATSIZE 8192
int main() {
LARGE_INTEGER start, end, freq;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&start);
recursiveTranspose(0, MATSIZE, 0, MATSIZE);
QueryPerformanceCounter(&end);
printf("recursive: %.2fms\n", (end.QuadPart - start.QuadPart) / (double(freq.QuadPart) / 1000));
QueryPerformanceCounter(&start);
transpose();
QueryPerformanceCounter(&end);
printf("iterative: %.2fms\n", (end.QuadPart - start.QuadPart) / (double(freq.QuadPart) / 1000));
return 0;
}
results:
recursive: 480.58ms
iterative: 3678.46ms
Düzenleme: Boyutu etkisi hakkında: Bu hala bir dereceye kadar fark rağmen çok daha az belirgindir, çünkü biz yinelenen çözüm 1'e kadar tekrarlayan yerine bir yaprak düğüm olarak (özyinelemeli algoritmalar için her zamanki optimizasyon) kullanıyoruz. LEAFSIZE = 1 değerini ayarlarsak, önbelleğin benim için bir etkisi olmaz [ 8193: 1214.06; 8192: 1171.62ms, 8191: 1351.07ms
- bu hata payının içindedir, dalgalanmalar 100 ms'dir; bu "karşılaştırmalı değerlendirme", tamamen doğru değerler istiyorsak çok rahat edeceğim bir şey değildir])
[1] Bu tür kaynaklar: Leiserson ve co ile birlikte çalışmış birinden ders alamazsanız .. Kağıtlarının iyi bir başlangıç noktası olduğunu varsayıyorum. Bu algoritmalar hala oldukça nadiren açıklanmaktadır - CLR'nin onlar hakkında tek bir dipnotu vardır. Yine de insanları şaşırtmak için harika bir yol.
Düzenleme (not: Ben bu cevabı gönderen değilim; Sadece eklemek istedim):
İşte yukarıdaki kodun tam bir C ++ sürümü:
template<class InIt, class OutIt>
void transpose(InIt const input, OutIt const output,
size_t const rows, size_t const columns,
size_t const r1 = 0, size_t const c1 = 0,
size_t r2 = ~(size_t) 0, size_t c2 = ~(size_t) 0,
size_t const leaf = 0x20)
{
if (!~c2) { c2 = columns - c1; }
if (!~r2) { r2 = rows - r1; }
size_t const di = r2 - r1, dj = c2 - c1;
if (di >= dj && di > leaf)
{
transpose(input, output, rows, columns, r1, c1, (r1 + r2) / 2, c2);
transpose(input, output, rows, columns, (r1 + r2) / 2, c1, r2, c2);
}
else if (dj > leaf)
{
transpose(input, output, rows, columns, r1, c1, r2, (c1 + c2) / 2);
transpose(input, output, rows, columns, r1, (c1 + c2) / 2, r2, c2);
}
else
{
for (ptrdiff_t i1 = (ptrdiff_t) r1, i2 = (ptrdiff_t) (i1 * columns);
i1 < (ptrdiff_t) r2; ++i1, i2 += (ptrdiff_t) columns)
{
for (ptrdiff_t j1 = (ptrdiff_t) c1, j2 = (ptrdiff_t) (j1 * rows);
j1 < (ptrdiff_t) c2; ++j1, j2 += (ptrdiff_t) rows)
{
output[j2 + i1] = input[i2 + j1];
}
}
}
}