Luchian memberikan penjelasan tentang alasannya perilaku ini terjadi, tapi saya pikir itu akan menjadi ide bagus untuk menunjukkan satu solusi yang mungkin untuk masalah ini dan pada saat yang sama menunjukkan sedikit tentang algoritma cache cache.
Algoritma Anda pada dasarnya tidak:
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
A[j][i] = A[i][j];
yang hanya mengerikan untuk CPU modern. Salah satu solusinya adalah mengetahui detail tentang sistem cache Anda dan menyesuaikan algoritme untuk menghindari masalah tersebut. Bekerja dengan baik selama Anda tahu detail-detail itu .. tidak terlalu portabel.
Bisakah kita berbuat lebih baik dari itu? Ya kita bisa: Pendekatan umum untuk masalah ini adalah algoritma yang tidak menyadari cache yang sehingga seperti namanya, jangan bergantung pada ukuran cache tertentu [1]
Solusinya akan terlihat seperti ini:
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];
}
}
Sedikit lebih rumit, tetapi tes singkat menunjukkan sesuatu yang cukup menarik pada e8400 kuno saya dengan rilis VS2010 x64, testcode for 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
Sunting: Tentang pengaruh ukuran: Ini jauh lebih sedikit diucapkan meskipun masih terlihat sampai batas tertentu, itu karena kami menggunakan solusi berulang sebagai simpul daun alih-alih berulang ke 1 (pengoptimalan biasa untuk algoritma rekursif). Jika kita menetapkan LEAFSIZE = 1, cache tidak mempengaruhi saya [ 8193: 1214.06; 8192: 1171.62ms, 8191: 1351.07ms
- itu ada di dalam margin of error, fluktuasi ada di area 100ms; "tolok ukur" ini bukanlah sesuatu yang akan membuat saya terlalu nyaman jika kita menginginkan nilai yang sepenuhnya akurat])
[1] Sumber untuk hal-hal ini: Ya, jika Anda tidak bisa mendapatkan kuliah dari seseorang yang bekerja dengan Leiserson dan rekan mengenai hal ini .. Saya menganggap makalah mereka sebagai titik awal yang baik. Algoritma tersebut masih sangat jarang dijelaskan - CLR memiliki catatan kaki tunggal tentang mereka. Tetap itu cara yang bagus untuk mengejutkan orang.
Sunting (catatan: Saya bukan orang yang memposting jawaban ini; Saya hanya ingin menambahkan ini):
Berikut adalah versi C ++ lengkap dari kode di atas:
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];
}
}
}
}