Luchian ให้คำอธิบายว่าทำไมพฤติกรรมนี้เกิดขึ้น แต่ฉันคิดว่ามันเป็นความคิดที่ดีที่จะแสดงวิธีแก้ปัญหาที่เป็นไปได้วิธีหนึ่งและในเวลาเดียวกันก็แสดงให้เห็นเล็กน้อยเกี่ยวกับอัลกอริธึมที่หลงลืม
โดยทั่วไปอัลกอริทึมของคุณจะ:
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
A[j][i] = A[i][j];
ซึ่งน่ากลัวมากสำหรับซีพียูรุ่นใหม่ ทางออกหนึ่งคือการรู้รายละเอียดเกี่ยวกับระบบแคชของคุณและปรับแต่งอัลกอริทึมเพื่อหลีกเลี่ยงปัญหาเหล่านั้น ใช้งานได้ดีตราบใดที่คุณรู้รายละเอียดเหล่านั้น .. ไม่พกพาโดยเฉพาะ
เราทำได้ดีกว่านั้นได้ไหม ใช่เราทำได้: วิธีการทั่วไปในการแก้ไขปัญหานี้คืออัลกอริทึมลบเลือนของแคชที่ชื่อบอกว่าหลีกเลี่ยงการพึ่งพาขนาดแคชเฉพาะ [1]
วิธีแก้ปัญหาจะมีลักษณะเช่นนี้:
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];
}
}
ซับซ้อนขึ้นเล็กน้อย แต่การทดสอบสั้น ๆ แสดงให้เห็นถึงสิ่งที่ค่อนข้างน่าสนใจสำหรับ e8400 โบราณของฉันด้วยการเปิดตัว VS2010 x64 รหัสการทดสอบสำหรับ 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
แก้ไข: เกี่ยวกับอิทธิพลของขนาด: มันมีความเด่นชัดน้อยกว่าแม้ว่าจะยังคงเห็นได้ชัดเจนในระดับหนึ่งนั่นเป็นเพราะเราใช้โซลูชันวนซ้ำเป็นโหนดปลายใบไม้แทนการเรียกซ้ำถึง 1 (การปรับให้เหมาะสมตามปกติสำหรับอัลกอริทึมแบบเรียกซ้ำ) หากเราตั้งค่า LEAFSIZE = 1 แคชจะไม่มีผลกับฉัน [ 8193: 1214.06; 8192: 1171.62ms, 8191: 1351.07ms
- นั่นคือภายในขอบของข้อผิดพลาดความผันผวนอยู่ในพื้นที่ 100ms; "เบนช์มาร์ก" นี้ไม่ใช่สิ่งที่ฉันจะสบายเกินไปถ้าเราต้องการค่าที่แม่นยำอย่างสมบูรณ์])
[1] แหล่งข้อมูลสำหรับสิ่งนี้: ถ้าคุณไม่สามารถบรรยายจากคนที่ทำงานร่วมกับ Leiserson และร่วมในเรื่องนี้ .. ฉันถือว่าเอกสารของพวกเขาเป็นจุดเริ่มต้นที่ดี อัลกอริธึมเหล่านั้นยังไม่ค่อยอธิบาย - CLR มีเชิงอรรถเดียวเกี่ยวกับพวกเขา ยังคงเป็นวิธีที่ดีในการทำให้คนแปลกใจ
แก้ไข (หมายเหตุ: ฉันไม่ใช่คนที่โพสต์คำตอบนี้ฉันแค่อยากจะเพิ่ม):
นี่คือรหัสด้านบนทั้งหมดของ C ++:
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];
}
}
}
}