C/C++ 메모리접근 방식 vs local변수 저장 속도 최적화 차이
글쓴이: high385 / 작성시간: 월, 2016/10/03 - 2:15오후
예를 들어서
1. 메모리접근방식
int a [100000]; int b [100000]; for (int i=0;i <100000;i++){ a [i] = i/10; b [i] = a [i]; }
2.local변수저장
int b [100000]; for (int i=0;i <100000;i++){ int a = i/10; b [i] = a; }
위와 같이 중간변수로 메모리를 사용하는지와 로컬변수를
사용하는것중에 속도가 뭐가 빠를까요? 비슷할까요?
제가 지금 확인할수없는 상황이어서 질문드리는데
중간변수 사용과 메모리 사용중 어는것이 속도면에서 이득이 있을까요?
Forums:
굳이 중간에 중간 변수가 있어야 한다면,,,,,
굳이 중간에 중간 변수가 있어야 한다면,,,,, 아무래도 2번이 (아주 아주) 조금 더 빠를 것 같네요.
1번의 경우는 a[i] 에 대한 인덱스 계산이 추가로 들어갑니다. 그 외에는 동일할 것 같구요.
2번의 경우 루프 안에서 변수를 선언하면, 원칙적으로
2번의 경우 루프 안에서 변수를 선언하면, 원칙적으로 매 루프에서 push, pop 명령이 수행됩니다.
안전하게는 루프 밖에 변수를 선언하는게 속도 면에서는 좋습니다.
컴파일러가 최적화를 잘하면 변수를 레지스터에 저장할 수도 있기 때문에 더 빨라질 수도 있죠.
1. 이렇게 단편적인 C/C++ 코드 일부만 들고
1. 이렇게 단편적인 C/C++ 코드 일부만 들고 와서 성능비교를 한다는 건 무리입니다.
최소한 알고리즘 수준의 차이가 있다면 모를까...
C++라면 간혹 코드의 미묘한 차이 때문에 복사 생성자나 대입 연산자가 불릴까 말까 하는 경우도 있긴 있고, 그 경우엔 눈에 띄는 성능 차이가 불가피한 경우도 있기는 합니다.
그런데 이 질문의 경우는 해당사항이 없죠.
C언어에서
int i;
에 대해i++;
가 빠를까++i;
가 빠를까 하는 해묵은 논쟁과도 비슷합니다. 어지간한 컴파일러는 이런 코드에 대해 동일한 어셈블리를 생성합니다.2. 1번 코드에서 이후에 배열 a가 사용되지 않는다면, 컴파일러는 배열 a를 없애 버리고 루프 안에서의 a 참조도 다 날려 버립니다. (Dead code elimination)
이후에 a가 사용된다면, 그 경우엔 애초에 1번 코드와 2번 코드가 서로 다른 코드가 되어 버리는 거죠. 1번 코드가 하는 일이 더 많으니 조금 더 느려질 수도 있는 거 아니겠어요?
3. 2번 코드도 마찬가집니다. C언어를 읽을 땐 변수 a가 생겼다 없어졌다 하는 게 눈에 보이지만, 그렇다고 정말 컴파일러가 매 루프마다 스택에 push/pop을 반복하는 코드를 만들 것 같은가요?
디버그 모드 켜놓고 컴파일한다면 또 모르겠는데, 애초에 그래 놓고 성능 비교한다는 게 우스운 일이죠. 더욱이 어지간한 환경이라면 컴파일러가 변수 a를 그냥 레지스터에 박아 버릴 겁니다.
아니지, 그냥 표현식 (i/10)을 b[i]에 바로 대입해버릴 수도 있어요. 이 경우 변수 a는 더 하는 일이 없으므로 마찬가지로 Dead code가 되어 날라갑니다. (그 중간에 레지스터가 하나는 개입할 테니, 어차피 결과는 같겠네요.)
...이 정도의 최적화도 지원 못하는 컴파일러를 쓰는 환경이라면, 애초에 프로그램 성능 운운하는 게 의미가 없는 환경이라는 말씀입니다. 컴파일러부터 바꾸고 (혹은 개선하고) 볼 일이죠.
덧. 배열 크기는 100인데 루프는 100000까지 돌아가는 부분은 오타라고 생각하고 넘어가겠습니다.
하지만, 프로그래머의 입장에서는, 특히 C++
하지만, 프로그래머의 입장에서는, 특히 C++ 프로그래머라면, 최적화 이전에 코드 자체로 가장 이상적인 프로그램을 만들어야 하지 않나요. 최적화는 그 다음 문제죠. 로컬 변수를 루프 내에 선언하는 것은 많은 교과서에서 좋지 않다고 하고 있습니다. 윗 글 쓰신 분도 원칙적으로 push, pop 이 수행된다는 것이라고 한 것 뿐이구요.
...
로컬 변수를 루프 안에 선언하는 게 좋지 않다고 말한다면, 그 교과서가 상당히 옛날 교과서거나 제대로 업데이트가 안된 것 같습니다.
일반적인 상황에서는 임시 변수를 loop 안에 넣는 게 오히려 훨씬 좋습니다. 왜냐 하면 코드를 읽는 사람에게 "이 변수는 루프 안에서 쓰는 임시 변수"라는 의미를 명확히 전달하기 때문입니다. 코드가 오래 걸려서 기다리는 시간과, 코드에 버그가 있어서 그거 찾는 시간 중 일반적으로 프로그래머의 시간을 잡아먹는 것이 어느 쪽인지를 생각해 보신다면...
게다가 앞에 다른 분이 이야기하셨듯이, 정수 변수라면 컴파일러가 알아서 잘 최적화를 해줍니다. 루프 한번 돌 때마다 push/pop하는 삽질은 하지 않습니다.
---
경우에 따라 루프 안에 선언하는 게 오버헤드가 될 수도 있습니다. string이나 vector처럼 실제로 생성자/소멸자에서 하는 일이 있고, *루프가 굉장히 자주 불려서 그 변수를 만들고 없애는 게 프로그램 성능에 영향을 끼친다면* 밖으로 빼는 게 좋습니다.
하지만 실제로 그런 일이 일어날 가능성은 대단히 낮습니다.
-O3으로 컴파일하면 스트링을 1억번 만들고 없애는 데 제 컴퓨터에서 약 4.4초가 걸리고, s 정의를 for loop 앞으로 빼면 약 1.3초가 걸리는군요. string 변수를 하나 만들 때마다 약 30ns가 걸리는 셈이고, 1초에 백만번 도는 루프가 있을 때 string 변수를 앞으로 빼면 수행시간의 3%를 절약할 수 있습니다.
실제 이런 상황이라면, 루프가 백만번 도는 이유를 찾아서 그 원인을 제거하는 게 속도향상에 더 도움이 될 가능성이 꽤 됩니다.
글쎄요.
물론 코드 레벨에서의 최적화가 전혀 의미 없는 건 아니죠.
알고리즘 수준에서의 개선 뿐만 아니라, 예컨대 루프 순서를 바꿔서 메모리 접근의 Locality를 높인다던가 하는 식으로도 눈에 띄는 성능 향상을 얻을 수 있기는 합니다.
C++에서 루프 안에 클래스 인스턴스를 정의하게 할 경우, 분명히 눈에 보이는 동작인 생성자 및 소멸자 반복 호출 비용이 들어가기 때문에 그건 고려해야 하는 대상이 맞아요. 앞서 jick님이 보여주신
string
의 예가 그것이죠.하지만 반박할 만한 점은 반박해야겠군요.
1. 저는 분명히 그런데 이 질문의 경우는 해당사항이 없죠. 라는 전제를 달았습니다.
int
처럼 C에서나 C++에서나 기본 제공되고 컴파일러가 그 동작을 분명히 이해하고 있는 타입에 대해서는 컴파일러가 기초적인 최적화를 해 줄 거라고 기대하는 게 당연하다는 말이에요. 최적화되서 똑같은 바이나리를 생성할 게 뻔한 두 벌의 코드를 놓고 어느 쪽이 빠른지를 논하는 게 얼마나 의미가 있겠습니까. 차라리 프로그래머의 편의나 가독성 측면에서의 논의가 더 생산적입니다. 그나마도 사실은 대개 취향 문제인 것 같습니다만.의심되면 직접 한 번 컴파일해보세요. 단, 1번 코드를 컴파일할 땐 배열 a가 이후에 사용되지 않을 것임을 반드시 코드에 드러내 주어야 합니다. 그렇지 않으면, 당연히 배열 a에도 데이터를 써야만 하므로 차이가 발생합니다.
제가 해 보니 gcc 4.4.7에서 최적화를 완전히 끄고(-O0) 컴파일해야 차이가 있고, -O1 정도만 줘도 둘 다 완벽히 똑같은 코드 바이나리를 생성하는군요.
2. 대체 무슨 원칙에 따라서 push/pop을 해 주어야 한다는 거죠?
사실 이 말씀은 slee0303님이 먼저 말씀하신 것이고, DarkSide님은 인용만 하셨을 뿐이죠.
하지만 이왕 거론된 김에 제대로 다뤄야겠네요.
엄밀히 말하면, 그런 "원칙"은 존재하지 않습니다.
automatic variable을 구현할 수 있는 메커니즘으로써 그런 방법이 있다는 건 저도 압니다.
하지만, C/C++언어는 automatic variable의 scope, storage duration 등을 정의할 뿐, automatic variable을 어디에 어떻게 만들지에 대해 규정하지는 않습니다.
그것을 결정하는 것은 컴파일러, 구현환경의 몫입니다. 그리고 최적화를 하는 것도 컴파일러의 몫입니다.
"원칙대로라면 push/pop을 해야 하지만 컴파일러 최적화로 인해 레지스터에 들어갈 수 있다."가 아니라, 엄밀히 말하면,
"스택에 push/pop으로 들어갈 수도 있고 레지스터에 들어갈 수도 있다. 컴파일러가 결정한다."인 겁니다.
물론 컴파일러의 결정에 영향을 주는 요소들도 있죠. 예컨대 automatic variable의 주소값을 취하는 코드가 있다면, 이 때는 별 수 없이 메모리 어딘가에 변수를 만들어야 할 테니까요.
어찌되었건 이 질문에 한해서는 이론의 여지를 만들 요소는 없어 보이는군요.
댓글 달기