restrict adalah type qualifier yang diperkenalkan di C99 untuk memberi tahu compiler bahwa sebuah pointer adalah satu-satunya cara untuk mengakses objek yang ditunjuknya selama lifetime pointer tersebut. Dengan janji ini, compiler tahu bahwa tidak ada aliasing—dua pointer tidak menunjuk ke memori yang sama—sehingga optimasi yang lebih agresif dapat dilakukan. Tanpa restrict, compiler harus berhati-hati karena setiap akses pointer berpotensi mengubah nilai yang sama. Pada chapter ini kita akan memahami kontrak restrict, dampaknya pada optimasi, dan jebakan bila kontrak dilanggar.
Apa Itu Aliasing dan Mengapa restrict Penting #
Aliasing terjadi ketika dua pointer menunjuk ke lokasi memori yang sama. Contoh klasik: fungsi copy(dst, src, n) yang menyalin n elemen dari src ke dst. Bila dst dan src mengoverlap (misalnya dst mulai dari indeks 1 dan src dari indeks 0), iterasi dst[i] = src[i] dapat menghasilkan hasil yang salah karena nilai src[i] sudah diubah oleh iterasi sebelumnya. Compiler tidak bisa tahu apakah overlap terjadi, sehingga harus memuat ulang src[i] dari memori di setiap iterasi—menghalangi optimasi seperti vectorization.
#include <stdio.h>
#include <string.h>
/* restrict menjanjikan: src dan dst tidak mengoverlap */
void copy_int(int *restrict dst, const int *restrict src, size_t n) {
for (size_t i = 0; i < n; i++) {
dst[i] = src[i];
}
}
/* Tanpa restrict: compiler harus asumsikan kemungkinan aliasing */
void copy_int_norestrict(int *dst, const int *src, size_t n) {
for (size_t i = 0; i < n; i++) {
dst[i] = src[i];
}
}
int main(void) {
int src[5] = {1, 2, 3, 4, 5};
int dst[5] = {0};
copy_int(dst, src, 5);
printf("setelah copy: ");
for (int i = 0; i < 5; i++) printf("%d ", dst[i]);
printf("\n");
return 0;
}Output yang Dihasilkan:
setelah copy: 1 2 3 4 5 Mari kita telaah baris-baris kode di atas:
- Baris 5 (
void copy_int(int *restrict dst, const int *restrict src, size_t n)): kualifikasirestrictpada kedua pointer. Kontrak: selama pemanggilan fungsi ini,dstdansrctidak menunjuk memori yang sama, dan tidak ada pointer lain yang juga mengakses memori tersebut. - Baris 6-8 (
for (...) dst[i] = src[i];): implementasi naif. Tanparestrict, compiler tidak bisa memindahkansrc[i]ke register karena khawatirdst[i] = src[i]mungkin mengubahsrc[i+1]lewat aliasing. - Baris 11-15 (
copy_int_norestrict): signature tanparestrict. Fungsi ini bekerja identik, tetapi compiler tidak bisa mengoptimalkan seagresif versirestrict. - Baris 17-25 (
main): pemakaian normal.dstdansrcadalah array berbeda, jadi kontrakrestrictterpenuhi. Hasil benar.
Optimasi yang Dimungkinkan restrict #
Dengan restrict, compiler dapat melakukan optimasi berikut:
- Vectorization: memproses 4 elemen sekaligus lewat SIMD (SSE/AVX) karena dijamin tidak ada ketergantungan antar iterasi.
- Common subexpression elimination: nilai
src[i]yang dimuat sekali dapat dipakai ulang tanpa memuat ulang. - Reordering: compiler dapat menyusun ulang urutan baca-tulis untuk pipelining yang lebih baik.
Untuk melihat efeknya, bandingkan assembly yang dihasilkan dengan dan tanpa restrict:
gcc -O2 -S -o copy_restrict.s test.c # dengan restrict
gcc -O2 -S -o copy_norestrict.s test.c # tanpa restrictPada praktiknya, versi restrict menghasilkan instruksi movdqu (vector) sedangkan versi tanpa restrict menghasilkan loop mov satu per satu. Selisih kecepatan bisa 2-4x untuk array besar.
#include <stdio.h>
/* Operasi yang aman dengan restrict */
void scale_add(int *restrict dst,
const int *restrict a,
const int *restrict b,
int factor, size_t n) {
for (size_t i = 0; i < n; i++) {
dst[i] = a[i] + b[i] * factor;
}
}
int main(void) {
int data[5] = {10, 20, 30, 40, 50};
int out[5] = {0};
/* Pemakaian normal: tidak ada overlap */
scale_add(out, data, data, 2, 5);
printf("hasil: ");
for (int i = 0; i < 5; i++) printf("%d ", out[i]);
printf("\n");
return 0;
}Output yang Dihasilkan:
hasil: 30 60 90 120 150 Fungsi scale_add memakai tiga pointer restrict. Pemanggilan scale_add(out, data, data, 2, 5) mengoper data untuk parameter a dan b sekaligus. Apakah ini melanggar restrict? Tidak—a dan b sama-sama const, jadi keduanya hanya membaca. Kontrak restrict melarang dua pointer menulis/membaca memori yang sama bila salah satunya non-const. Bila a adalah const int *restrict dan b juga const int *restrict, keduanya boleh menunjuk lokasi yang sama karena tidak ada modifikasi.
Kapan Memakai restrict #
- Hot loop yang melakukan banyak akses array:
restrictmemberi sinyal jelas ke compiler. - Wrapper fungsi standar seperti
memcpy,strcpyyang sudah memakairestrict. - API yang dokumentasinya eksplisit melarang overlap:
restrictmembuat kontrak menjadi compiler-checkable.
Hindari restrict bila:
- Fungsi benar-benar mendukung overlap (misal
memmovesengaja tidak memakairestrict). - Codebase masih prototipe dan optimasi belum dibutuhkan—kerumitan tidak sepadan.
- Compiler target tidak mendukung C99.