volatile
gcc compiler는 성능 향상(optimization)을 목적으로 경우에 따라 변수의 사용에 대해 무시하거나 사용 위치를 변경할 수 있는데 volatile을 사용하면 다음의 optimization을 하지 않고 코드를 만들어낸다.
- Optimization case
- 객체(변수)가 사용되지 않아도 된다고 판단할 때 무시한다.
- 루프 문 내부에서 사용되는 객체(변수)가 input용도로만 사용되는 경우 루프문 바깥으로 이전한다.
- 메모리, I/O 주소 등에 접근 시 생략될 가능성이 있거나 access 횟 수가 의도와 다르게 적게 호출될 가능성이 있는 경우 반드시 volatile을 사용하여 컴파일러로 하여금 관련 주소의 코드를 optimization 하지 않도록 해야 한다.
Using C
1) 무시되는 case
- d1 값을 10번 읽어들일 때.
volatile-1.c
void discard1()
{
int i;
int d1 = 1;
int sum = 0;
for (i = 0; i < 10; i++)
sum += d1;
}
void discard2()
{
int i;
volatile int d2 = 1;
int sum = 0;
for (i = 0; i < 10; i++)
sum += d2;
}
int main()
{
discard1();
discard2();
}
- 다음과 같이 disassemble 해보면 discard1() 함수의 경우 아무것도 하지 않음을 알 수 있다.
$ gcc -O2 volatile-1.c -o volatile-1
$ objdump -d volatile-1
(...생략...)
000083b4 <discard1>:
83b4: e12fff1e bx lr
000083b8 <discard2>:
83b8: e24dd008 sub sp, sp, #8
83bc: e3a0300a mov r3, #10
83c0: e3a02001 mov r2, #1
83c4: e58d2004 str r2, [sp, #4]
83c8: e2533001 subs r3, r3, #1
83cc: e59d2004 ldr r2, [sp, #4]
83d0: 1afffffc bne 83c8 <discard2+0x10>
83d4: e28dd008 add sp, sp, #8
83d8: e12fff1e bx lr
(...생략...)
2) 루프 밖으로 이동되는 케이스
#include <stdio.h>
int loop1(int * addr)
{
int i;
for (i = 0; i < 10; i++)
{
*addr += 1;
}
return i;
}
int loop2(volatile int * addr)
{
int i;
for (i = 0; i < 10; i++)
{
*addr += 1;
}
return i;
}
int main()
{
int l1 = 0;
volatile int l2 = 0;
loop1(&l1);
loop2(&l2);
printf("l1=%d, l2=%d\n", l1, l2);
}
- 다음과 같이 disassemble 해보면 loop1() 함수의 경우 10번 반복하지 않고 1번만 결과 값을 저장함을 알 수 있다.
$ gcc -O2 volatile-2.c -o volatile-2
$ objdump -d volatile-2
(...생략...)
00008408 <loop1>:
8408: e1a03000 mov r3, r0
840c: e3a0000a mov r0, #10
8410: e5932000 ldr r2, [r3]
8414: e0822000 add r2, r2, r0
8418: e5832000 str r2, [r3]
841c: e12fff1e bx lr
00008420 <loop2>:
8420: e3a0300a mov r3, #10
8424: e5902000 ldr r2, [r0]
8428: e2533001 subs r3, r3, #1
842c: e2822001 add r2, r2, #1
8430: e5802000 str r2, [r0]
8434: 1afffffa bne 8424 <loop2+0x4>
8438: e3a0000a mov r0, #10
843c: e12fff1e bx lr
(...생략...)
$ ./volatile-2
l1=10, l2=10
Using Inline Assembly
1) 무시되는 case
volatile-3.c
void discard3(int * input)
{
int output;
asm ("ldr %0, [%1]"
: "=r" (output)
: "r" (input)
: "cc");
}
void discard4(int * input)
{
int output;
asm volatile ("ldr %0, [%1]"
: "=r" (output)
: "r" (input));
}
int main()
{
int d3 = 0;
int d4 = 0;
discard3(&d3);
discard4(&d4);
}
- 다음과 같이 disassemble 해보면 discard3() 함수의 경우 아무것도 하지 않음을 알 수 있다.
$ gcc -O2 volatile-3.c -o volatile-3
$ objdump -d volatile-3
(...생략...)
000083a4 <discard3>:
83a4: e12fff1e bx lr
000083ac <discard4>:
83a8: e5900000 ldr r0, [r0]
83ac: e12fff1e bx lr
(...생략...)
2) 루프 밖으로 이동되는 케이스
volatile-4.c
int loop3(int * addr)
{
int i;
int tmp = 0;
for (i = 0; i < 10; i++)
{
asm ("add %0, #1\n str %0, [%1]"
: "=r" (tmp)
: "r" (addr)
: "memory");
}
return tmp;
}
int loop4(int * addr)
{
int i;
int tmp = 0;
for (i = 0; i < 10; i++)
{
asm volatile ("add %0, #1\n str %0, [%1]"
: "=r" (tmp)
: "r" (addr)
: "memory");
}
return tmp;
}
int main()
{
int l3 = 1;
int l4 = 1;
loop3(&l3);
loop4(&l4);
}
- 1
$ gcc -O2 volatile-4.c -o volatile-4
$ objdump -d volatile-4
(...생략...)
000083cc <loop3>:
83cc: e3a0300a mov r3, #10
83d0: e2800001 add r0, r0, #1
83d4: e5820000 str r0, [r0]
83d8: e2533001 subs r3, r3, #1
83dc: 1afffffd bne 83d8 <loop3+0xc>
83e0: e12fff1e bx lr
000083e4 <loop4>:
83e4: e3a0300a mov r3, #10
83e8: e2800001 add r2, r2, #1
83ec: e5820000 str r2, [r0]
83f0: e2533001 subs r3, r3, #1
83f4: 1afffffb bne 83e8 <loop4+0x4>
83f8: e1a00002 mov r0, r2
83f8: e12fff1e bx lr
(...생략...)
기타
- Extended Asm – Assembler Instructions with C Expression Operands – volatile | gnu.org
- Using volatile | ARM
- [Linux] ACCESS_ONCE()와 volatile | F/OSS
- [Linux] 최적화 장벽? | F/OSS