چرا این مقایسه اهمیت دارد
در کد سطح بالای C، پیمایش یک آرایه با استفاده از نمادگذاری ایندکس و پیمایش آن با استفاده از اشارهگر، هر دو طبیعی بهنظر میرسند و اغلب نتیجه منطقی یکسانی تولید میکنند. اما یک کامپایلر هرکدام از این سبکها را به الگوی متمایزی از دستورات RISC-V ترجمه میکند، و کار سختافزاری حاصل یکسان نیست. درک این تفاوت منبع رایجی از بینش درباره اینکه چرا سبکهای کدنویسی خاصی در عمل سریعتر اجرا میشوند است.
رویکرد ایندکسگذاری آرایه
یک حلقه ساده را در نظر بگیرید که هر عنصر یک آرایه را با استفاده از نمادگذاری ایندکس پاک میکند:
void clear1(long array[], long size) {
for (long i = 0; i < size; i += 1) {
array[i] = 0;
}
}برای ترجمه array[i] به یک دسترسی حافظه، پردازنده باید در هر تکرار حلقه، آدرس حافظه را با ضرب کردن ایندکس i در اندازه هر عنصر مجدداً محاسبه کند، سپس آن نتیجه را به آدرس پایه آرایه اضافه کند:
Loop:
bge i, size, Exit
slli t0, i, 3
add t1, array, t0
sd x0, 0(t1)
addi i, i, 1
jal x0, Loop
Exit:توجه کنید به دستورات اضافی slli (شیفت به چپ، که اینجا برای ضرب در ۸ بایت استفاده شده) و add که در هر تکرار صرفاً برای محاسبه آدرس از روی ایندکس مورد نیازند.
رویکرد مبتنی بر اشارهگر
حالا همان منطق را با استفاده از حساب اشارهگر بهجای ایندکسگذاری در نظر بگیرید:
void clear2(long *array, long size) {
long *p;
for (p = &array[0]; p < &array[size]; p = p + 1) {
*p = 0;
}
}اینجا، کامپایلر میتواند حلقهای تولید کند که در آن خود اشارهگر مستقیماً در هر تکرار بهاندازه یک مقدار ثابت افزایش مییابد، بدون اینکه هرگز نیاز باشد آدرسی از روی ایندکس دوباره محاسبه شود:
Loop:
bge p, end_p, Exit
sd x0, 0(p)
addi p, p, 8
jal x0, Loop
Exit:این نسخه مرحله ضرب را کاملاً حذف میکند، چون اشارهگر از قبل یک آدرس حافظه واقعی را نگه میدارد و فقط به یک جمع ثابت ساده برای رفتن به عنصر بعدی نیاز دارد.
چرا این تفاوت وجود دارد
یک ایندکس آرایه صرفاً یک عدد است که باید هر بار که استفاده میشود به یک آدرس تبدیل شود، که نیازمند ضرب در اندازه عنصر در هر دسترسی است. یک اشارهگر، در مقابل، از قبل یک آدرس است، پس پیشبردن آن به عنصر بعدی فقط نیازمند اضافه کردن یکباره اندازه ثابت عنصر است، نه محاسبه مجدد یک حاصلضرب از ابتدا.
نتیجه عملی
کامپایلرهای بهینهساز امروزی اغلب قادرند بهطور خودکار حلقههای مبتنی بر ایندکس را داخلاً به حلقههای مبتنی بر اشارهگر تبدیل کنند، فرآیندی به نام Strength Reduction، که میتواند این شکاف کارایی را بدون نیاز به بازنویسی دستی کد توسط برنامهنویس از بین ببرد. با این حال، درک تفاوت زیربنایی سختافزاری توضیح میدهد چرا از نظر تاریخی، و در مواردی که کامپایلر نمیتواند این بهینهسازی را با امنیت انجام دهد، پیمایش مبتنی بر اشارهگر برای کد حساس به کارایی توصیه شده است.