C/C++ 메모리접근 방식 vs local변수 저장 속도 최적화 차이

high385의 이미지

예를 들어서

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;
}

위와 같이 중간변수로 메모리를 사용하는지와 로컬변수를

사용하는것중에 속도가 뭐가 빠를까요? 비슷할까요?

제가 지금 확인할수없는 상황이어서 질문드리는데

중간변수 사용과 메모리 사용중 어는것이 속도면에서 이득이 있을까요?

라스코니의 이미지

굳이 중간에 중간 변수가 있어야 한다면,,,,, 아무래도 2번이 (아주 아주) 조금 더 빠를 것 같네요.
1번의 경우는 a[i] 에 대한 인덱스 계산이 추가로 들어갑니다. 그 외에는 동일할 것 같구요.

ifree의 이미지

2번의 경우 루프 안에서 변수를 선언하면, 원칙적으로 매 루프에서 push, pop 명령이 수행됩니다.
안전하게는 루프 밖에 변수를 선언하는게 속도 면에서는 좋습니다.
컴파일러가 최적화를 잘하면 변수를 레지스터에 저장할 수도 있기 때문에 더 빨라질 수도 있죠.

 의 이미지

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까지 돌아가는 부분은 오타라고 생각하고 넘어가겠습니다.

DarkSide의 이미지

하지만, 프로그래머의 입장에서는, 특히 C++ 프로그래머라면, 최적화 이전에 코드 자체로 가장 이상적인 프로그램을 만들어야 하지 않나요. 최적화는 그 다음 문제죠. 로컬 변수를 루프 내에 선언하는 것은 많은 교과서에서 좋지 않다고 하고 있습니다. 윗 글 쓰신 분도 원칙적으로 push, pop 이 수행된다는 것이라고 한 것 뿐이구요.

jick의 이미지

로컬 변수를 루프 안에 선언하는 게 좋지 않다고 말한다면, 그 교과서가 상당히 옛날 교과서거나 제대로 업데이트가 안된 것 같습니다.

일반적인 상황에서는 임시 변수를 loop 안에 넣는 게 오히려 훨씬 좋습니다. 왜냐 하면 코드를 읽는 사람에게 "이 변수는 루프 안에서 쓰는 임시 변수"라는 의미를 명확히 전달하기 때문입니다. 코드가 오래 걸려서 기다리는 시간과, 코드에 버그가 있어서 그거 찾는 시간 중 일반적으로 프로그래머의 시간을 잡아먹는 것이 어느 쪽인지를 생각해 보신다면...

게다가 앞에 다른 분이 이야기하셨듯이, 정수 변수라면 컴파일러가 알아서 잘 최적화를 해줍니다. 루프 한번 돌 때마다 push/pop하는 삽질은 하지 않습니다.

---

경우에 따라 루프 안에 선언하는 게 오버헤드가 될 수도 있습니다. string이나 vector처럼 실제로 생성자/소멸자에서 하는 일이 있고, *루프가 굉장히 자주 불려서 그 변수를 만들고 없애는 게 프로그램 성능에 영향을 끼친다면* 밖으로 빼는 게 좋습니다.

하지만 실제로 그런 일이 일어날 가능성은 대단히 낮습니다.

#include <iostream>
#include <string>
 
using namespace std;
 
int main()
{
    size_t len = 0;
    for (int i = 0; i < 100000000; i++) {
        string s;
        s = "abc";
        len += s.size();
    }
 
    cout << len << "\n";
}
 
 
$ g++ -O3 string.cc && time ./a.out
300000000
 
real    0m4.403s
user    0m4.401s
sys     0m0.004s

-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의 주소값을 취하는 코드가 있다면, 이 때는 별 수 없이 메모리 어딘가에 변수를 만들어야 할 테니까요.

어찌되었건 이 질문에 한해서는 이론의 여지를 만들 요소는 없어 보이는군요.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.