Luchian gibt eine Erklärung dafür, warum dieses Verhalten auftritt, aber ich dachte, es wäre eine gute Idee, eine mögliche Lösung für dieses Problem aufzuzeigen und gleichzeitig ein wenig über Algorithmen zu zeigen, die den Cache nicht kennen.
Ihr Algorithmus macht im Grunde:
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
A[j][i] = A[i][j];
Das ist einfach schrecklich für eine moderne CPU. Eine Lösung besteht darin, die Details Ihres Cache-Systems zu kennen und den Algorithmus zu optimieren, um diese Probleme zu vermeiden. Funktioniert hervorragend, solange Sie diese Details kennen. Nicht besonders tragbar.
Können wir es besser machen? Ja, wir können: Ein allgemeiner Ansatz für dieses Problem sind Algorithmen , die den Cache nicht kennen und die , wie der Name schon sagt, vermeiden, von bestimmten Cache-Größen abhängig zu sein [1].
Die Lösung würde so aussehen:
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];
}
}
Etwas komplexer, aber ein kurzer Test zeigt etwas ziemlich Interessantes auf meinem alten e8400 mit VS2010 x64 Release, Testcode für 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
Bearbeiten: Über den Einfluss der Größe: Es ist viel weniger ausgeprägt, obwohl es bis zu einem gewissen Grad immer noch spürbar ist. Dies liegt daran, dass wir die iterative Lösung als Blattknoten verwenden, anstatt auf 1 zu rekursieren (die übliche Optimierung für rekursive Algorithmen). Wenn wir LEAFSIZE = 1 setzen, hat der Cache für mich keinen Einfluss [ 8193: 1214.06; 8192: 1171.62ms, 8191: 1351.07ms
- das liegt innerhalb der Fehlergrenze, die Schwankungen liegen im Bereich von 100 ms; Dieser "Benchmark" ist nichts, mit dem ich mich zu wohl fühlen würde, wenn wir völlig genaue Werte wollten])
[1] Quellen für dieses Zeug: Nun, wenn Sie keinen Vortrag von jemandem bekommen können, der mit Leiserson und Co. daran gearbeitet hat. Ich gehe davon aus, dass ihre Arbeiten ein guter Ausgangspunkt sind. Diese Algorithmen werden noch recht selten beschrieben - CLR enthält eine einzige Fußnote. Trotzdem ist es eine großartige Möglichkeit, Menschen zu überraschen.
Bearbeiten (Hinweis: Ich bin nicht derjenige, der diese Antwort gepostet hat; ich wollte dies nur hinzufügen):
Hier ist eine vollständige C ++ - Version des obigen Codes:
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];
}
}
}
}