utilizzo realistica della C99 'limitare' parola chiave?
-
09-09-2019 - |
Domanda
stavo dando un'occhiata agli po 'di documentazione e domande / risposte e ho visto che menzionato. Ho letto una breve descrizione, affermando che sarebbe fondamentalmente una promessa da parte del programmatore che il puntatore non sarà utilizzato per puntare da qualche altra parte.
Chiunque può offrire alcuni casi realistici in cui il suo valore in realtà usando questo?
Soluzione
restrict
dice che il puntatore è l'unica cosa che accede all'oggetto sottostante. Si elimina il potenziale di puntatore aliasing, consentendo una migliore ottimizzazione dal compilatore.
Per esempio, supponiamo di avere una macchina con le istruzioni specializzate che possono moltiplicare i vettori di numeri in memoria, e ho il seguente codice:
void MultiplyArrays(int* dest, int* src1, int* src2, int n)
{
for(int i = 0; i < n; i++)
{
dest[i] = src1[i]*src2[i];
}
}
Il compilatore deve gestire correttamente se dest
, src1
, e src2
si sovrappongono, il che significa che deve fare una moltiplicazione per volta, dall'inizio alla fine. Avendo restrict
, il compilatore è libero di ottimizzare il codice utilizzando le istruzioni vettoriali.
Wikipedia ha una voce su restrict
, con un altro esempio, qui .
Altri suggerimenti
Il Wikipedia esempio è molto illuminante.
Si mostra chiaramente come permette di salvare uno istruzioni di montaggio .
Senza limitare:
void f(int *a, int *b, int *x) {
*a += *x;
*b += *x;
}
assemblaggio Pseudo:
load R1 ← *x ; Load the value of x pointer
load R2 ← *a ; Load the value of a pointer
add R2 += R1 ; Perform Addition
set R2 → *a ; Update the value of a pointer
; Similarly for b, note that x is loaded twice,
; because a may be equal to x.
load R1 ← *x
load R2 ← *b
add R2 += R1
set R2 → *b
Con limitare:
void fr(int *restrict a, int *restrict b, int *restrict x);
assemblaggio Pseudo:
load R1 ← *x
load R2 ← *a
add R2 += R1
set R2 → *a
; Note that x is not reloaded,
; because the compiler knows it is unchanged
; load R1 ← *x
load R2 ← *b
add R2 += R1
set R2 → *b
Does GCC davvero farlo?
GCC 4.8 Linux x86-64:
gcc -g -std=c99 -O0 -c main.c
objdump -S main.o
Con -O0
, sono la stessa cosa.
Con -O3
:
void f(int *a, int *b, int *x) {
*a += *x;
0: 8b 02 mov (%rdx),%eax
2: 01 07 add %eax,(%rdi)
*b += *x;
4: 8b 02 mov (%rdx),%eax
6: 01 06 add %eax,(%rsi)
void fr(int *restrict a, int *restrict b, int *restrict x) {
*a += *x;
10: 8b 02 mov (%rdx),%eax
12: 01 07 add %eax,(%rdi)
*b += *x;
14: 01 06 add %eax,(%rsi)
Per chi non lo sapesse, il convenzione di chiamata è:
-
rdi
= primo parametro -
rsi
= secondo parametro -
rdx
= terzo parametro
uscita GCC è stato ancora più chiaro che l'articolo wiki: 4 istruzioni vs 3 Istruzioni
.Array
Finora abbiamo singole risparmio di istruzione, ma se il puntatore rappresentare array ad essere passato sopra, un caso d'uso comune, allora un gruppo di istruzioni potuto essere salvati, come detto da supercat .
Si consideri ad esempio:
void f(char *restrict p1, char *restrict p2) {
for (int i = 0; i < 50; i++) {
p1[i] = 4;
p2[i] = 9;
}
}
A causa di restrict
, un compilatore intelligente (o umano), potrebbe ottimizzare quello a:
memset(p1, 4, 50);
memset(p2, 9, 50);
che è potenzialmente molto più efficiente in quanto può essere ottimizzato montaggio su un'implementazione libc discreto (come glibc): e 'meglio usare std :: memcpy () o std :: copy () in termini di prestazioni?
Does GCC davvero farlo?
GCC 5.2.1.Linux x86-64 Ubuntu 15.10:
gcc -g -std=c99 -O0 -c main.c
objdump -dr main.o
Con -O0
, entrambi sono gli stessi.
Con -O3
:
-
con limitare:
3f0: 48 85 d2 test %rdx,%rdx 3f3: 74 33 je 428 <fr+0x38> 3f5: 55 push %rbp 3f6: 53 push %rbx 3f7: 48 89 f5 mov %rsi,%rbp 3fa: be 04 00 00 00 mov $0x4,%esi 3ff: 48 89 d3 mov %rdx,%rbx 402: 48 83 ec 08 sub $0x8,%rsp 406: e8 00 00 00 00 callq 40b <fr+0x1b> 407: R_X86_64_PC32 memset-0x4 40b: 48 83 c4 08 add $0x8,%rsp 40f: 48 89 da mov %rbx,%rdx 412: 48 89 ef mov %rbp,%rdi 415: 5b pop %rbx 416: 5d pop %rbp 417: be 09 00 00 00 mov $0x9,%esi 41c: e9 00 00 00 00 jmpq 421 <fr+0x31> 41d: R_X86_64_PC32 memset-0x4 421: 0f 1f 80 00 00 00 00 nopl 0x0(%rax) 428: f3 c3 repz retq
Due chiamate
memset
come previsto. -
senza limitare: nessuna chiamata stdlib, a soli 16 iterazione vasta anello svolgimento che non intendo riprodurre qui: -)
Non ho avuto la pazienza di punto di riferimento, ma credo che la versione limitare sarà più veloce.
C99
Vediamo lo standard per completezza.
restrict
dice che due puntatori non possono puntare a sovrapposizione delle regioni di memoria. L'utilizzo più comune è per gli argomenti della funzione.
Questo restringe come la funzione può essere chiamata, ma consente una più ottimizzazioni in fase di compilazione.
Se il chiamante non segue il contratto restrict
, un comportamento indefinito.
Il C99 N1256 progetto 6.7. 3/7 "Tipo di qualificazione", dice:
La destinazione d'uso del qualificatore limitare (come la classe di archiviazione registro) è quello di promuovere l'ottimizzazione e l'eliminazione di tutte le istanze del qualificatore da tutte le unità di traduzione di pre-elaborazione che compongono un programma conforme non cambia il suo significato (cioè, comportamento osservabile).
e 6.7.3.1 "Definizione formale di limitare" dà i dettagli scabrosi.
regola aliasing Strict
La parola chiave restrict
riguarda solo i puntatori di tipi compatibili (ad esempio due int*
) perché le rigide regole aliasing dice che aliasing tipi incompatibili è un comportamento indefinito di default, e così compilatori può assumere che non accada e ottimizzare via.
Vedi: Qual è la regola rigorosa aliasing
Vedi anche
- C ++ 14 non c'è ancora un analogo per
restrict
, ma GCC ha__restrict__
come estensione: Che cosa significa la parola limita significare in C ++? - Molte domande che chiedono: in base ai dettagli scabrosi, fa questo codice UB o no?
- A "quando utilizzare" domanda: Quando da utilizzare restringere e quando non
- Il legati
__attribute__((malloc))
GCC, che dice che il valore di ritorno di una funzione non è alias a nulla: GCC: __attribute__ ((malloc))