캐티
1k
2021-03-23 00:21:47
12
2802

예전에 어디서 지적받은 반복문 제어


어디서 라고는 알려드리기 곤란하지만 전예에 반복문에 대해서 처리속도를 준수하게 하기 위한 제어방식을 지적받은적이 있습니다.

일반적인 반복문은 0 ~ 1000 을 반복하는데 어레이에서 하는경우에 이런 형식이됩니다. C언어

int a[1000] = {};
for( int i = 0; i < sizeof(a)/sizeof(*a); i++ ){
  //code
}

#1 C언어에서 *a 랑 a[0] 랑 같습니다.


여러가지 로직에 넣다보면 for 의 변수 선언부를 더 사용할 수도 있어요.

로직은 항상 ++ 증감만 하진 않아도 되는데 i 가 중요하지 않거나 단순하게 반복횟수만 제한하는 경우는 다르게 쓸 수 있습니다. C언어

int a[1000] = {};
for( int i = 100; i > 0; i-- ){
  //code
}

만일 i 가 별로 중요하지 않은 인덱스로 사용하지 않을 작정이라면 위에처럼 100 회를 한정할 수 있습니다.

어레이 타입을 하는 경우에는 좀 다른데 인덱스를 맞추기 위해서 시작 변수 선언에 -1을 하여야합니다. C언어

int a[1000] = {};
for( int i = sizeof(a)/sizeof(*a)-1; i>=0; i-- ){
  //code
}

-1이 불편해 보일 수 있겠지만 자바스크립트 같은 변수엑세스에 비용이 좀 들어가는 데서는 특히나 진가를 발휘합니다.


어쩌면 저도 고정관념을 많이 가지고 있었겠어요.

위에 방식은 그 뒤로 아주 자주 많이 사용하게 되었습니다.
커뮤니티에서 활동한다는 점은 이런적 처럼 좋은 경험치가 되지 않겠나요.

0
  • 댓글 12

  • 소환사쇽쇽
    1k
    2021-03-23 08:45:17

    sizeof같은 연산자가 조건에 들어있으면 반복횟수만큼 수행하게 되니까 초기 조건식에 한번만 수행토록 하는게 좋다라고 이해하면 맞을까요?

  • fender
    21k
    2021-03-23 08:46:41

    반복문의 인덱스를 역순으로 하면 성능상 이익이 있다는 것이 핵심인 것 같은데 제가 맞게 이해한 것인가요?

    -1이 불편해 보일 수 있겠지만 자바스크립트 같은 변수엑세스에 비용이 좀 들어가는 데서는 특히나 진가를 발휘합니다.

    자바스크립트에서 해당 방식으로 0.001초 이상 실행 성능 차이를 보일 수 있는 예시를 보여주실 수 있는지요?

    저로서는 처음 듣는 내용이고 직감적으로도 쉽게 납득이 되지 않아서 구체적인 사례를 보고 싶어 질문 드립니다.

  • 엡실론
    1k
    2021-03-23 09:26:12 작성 2021-03-23 09:32:47 수정됨

    좀 더 구체적인 설명이 궁금하긴 한데, 느낌상 마이크로 최적화를 이야기하시는 듯 하네요. 요즘 컴파일러, 자바스크립트 엔진등은 그렇게 정직하게 코드를 실행하는게 아니라서 크게 의미는 없을 듯 합니다.

    10~20년전에는 어셈블리에서 0과 비교하는게 0이 아닌 수와 비교하는 것보다 빨라서, 역순으로 쓰기도 했지만, 역시 요즘엔 컴파일러가 충분히 알아서 처리하는 영역이라 큰 의미는 없습니다.

    소환사쇽쇽님 말처럼 반복수행이 문제면, 굳이 역순일 필요 없이 그냥 미리 계산 결과를 저장해두고 쓰면 되지요. 그것도 예제 같은 상수 식은 어차피 항상 결과가 같음이 명백하기 때문에 해당사항이 없고, 좀 더 복잡한 경우에는 의미가 있을 수 있습니다.

  • fender
    21k
    2021-03-23 10:29:26 작성 2021-03-23 10:37:46 수정됨

    한 번 간단하게 크롬에서 검증해봤습니다:



    내용은 단순 반복문을 1억 번 각기 다른 순서로 돌려서 소요 시간을 측정한 것입니다. 반복문 내부의 내용은 혹시라도 JIT 같은 동적 최적화가 개입해서 의미없는 반복문을 제거할까봐 넣은 것입니다.

    여기서 알 수 있는 것은 대략 두 가지입니다. 우선 요새는 자바스크립트 같은 언어도 어마무시하게 빨라서 단순 반복문의 변수할 당 같은 것이 의미있는 성능차를 내기 어렵다는 것이고, 두 번 째는 이유는 모르겠지만 역순으로 반복문을 했을 때 오히려 미세하게(1억 회 당 0.023초) 실행 속도가 떨어졌다는 점입니다.

    물론 제대로 된 실험은 아니기 때문에 크게 의미있는 내용은 아니겠습니다만, 아무래도 본문 내용이 반복문 실행 순서에 대한 지적이 맞다면 최소한 자바스크립트 같은 언어에서 큰 효과를 낸다는 건 잘못된 주장일 가능성이 높다고 봅니다.

    설사 결과가 반대로 나왔다 쳐도 저건 윗분 말씀대로 마이크로 최적화의 영역에 속하는 내용입니다. 이제까지 자바스크립트로 1억 번 반복하는 코드를 작성해보신 적이 있나요? 설사 그렇다 쳐도 그런 코드 실행을 0.023초 빠르게 하는 것이 큰 의미가 있는 일인지 따져볼 문제일 듯 합니다.

  • 상상초월
    281
    2021-03-23 12:10:02

    불편합니다.

    굳이 예제를 아래처럼 해도 되는데.. 음주후에는 게시글을 자제하시는게..

    int a[1000] = {};
    for (int i = 0size = (sizeof(a)/sizeof(*a)); i < sizei++) {
      //code
    }


  • rezigrene
    1k
    2021-03-23 12:22:35

    케티님의 예시를 구현한다면, 아래의 코드에서 `hardWork1()`, `hardWork2()` 와 같은 형태가 되겠습니다만, 보통은 반복회수를 결정하는 비교구문에서 `hardWork3()` 처럼 쓰는게 보통이고, `array.length`의 접근속도가 문제되던 시절에는 그게 권장되기도 하였으나, 현재는 실험에서 보시는 것처럼(`hardWork4()`) 엔진에서 최적화를 해주어서 문제가 없습니다.

     자바스크립트에 한하여서는 시대가 바뀌었으니 해당지적은 이제 잊으셔도 될듯합니다..



    function getSize(ary) { return Math.floor(ary.length + Math.random()) } function timeCheck(messge, func) { let startTime = performance.now() let ret = func() let executeTime = performance.now() - startTime console.log(ret) console.log(messge, executeTime) } function hardWork1(ary) { for (let i = 0; i < getSize(ary); i++) { ary[i] = i } return ary[Math.floor(Math.random() * getSize(ary))] } function hardWork2(ary) { for (let i = getSize(ary) - 1; i >= 0; i--) { ary[i] = i } return ary[Math.floor(Math.random() * getSize(ary))] } function hardWork3(ary) { let len = getSize(ary) for (let i = 0; i < len; i++) { ary[i] = i } return ary[Math.floor(Math.random() * getSize(ary))] } function hardWork4(ary) { for (let i = 0; i < ary.length; i++) { ary[i] = i } return ary[Math.floor(Math.random() * getSize(ary))] } let arySize = 1000 * 1000 * 20 ary = new Array(arySize).fill(1) timeCheck('for (let i = 0; i < getSize(ary); i++) {', () => hardWork1(ary)) timeCheck('for (let i = getSize(ary) - 1; i >= 0; i--) {', () => hardWork2(ary)) timeCheck('let len = getSize(ary);for (let i = 0; i < len; i++) {', () => hardWork3(ary)) timeCheck('for (let i = 0; i < ary.length; i++) {',() => hardWork4(ary))
    VM226:9 8355363
    VM226:10 for (let i = 0; i < getSize(ary); i++) { 576.4900000067428
    VM226:9 19478678
    VM226:10 for (let i = getSize(ary) - 1; i >= 0; i--) { 35.12000001501292
    VM226:9 6420890
    VM226:10 let len = getSize(ary);for (let i = 0; i < len; i++) { 28.960000025108457
    VM226:9 13293411
    VM226:10 for (let i = 0; i < ary.length; i++) { 26.425000047311187
    VM226:49





  • 밋꾸
    506
    2021-03-23 12:26:06

    마이크로 최적화가 무슨 의미인가요!? 처음들어보는 단어에요..ㅎ

  • fender
    21k
    2021-03-23 13:19:26

    rezigrene님 예시를 보니 본문에서 언급한 지적 내용은 배열 크기 계산에 대한 오버헤드가 핵심이라는 걸 뒤늦게 이해했습니다.

    그럼에도 불구하고 자바스크립트 같은 환경에서 그런 고려는 마이크로 최적화의 영역이라는 결론은 변하지 않는 것 같습니다.


    밋꾸 // 가치 중립적으로 쓸 때는 미시적인 수준의 성능 차이에 대한 최적화를 뜻하고, 맥락에 따라 현실적으로 큰 의미없는 최적화 시도를 의미하기도 합니다.

  • JasonH.
    93
    2021-03-23 13:22:16
    오타가 하나 없는거 실홥니까!? ㅋㅋㅋㅋ
  • 캐티
    1k
    2021-03-23 19:40:59

    역시 장인분이 많으시네요.

    JasonH. 게시글까지 오타를 쓰면 도가 심하게 지나치지 않겠나요 ㅎ.

    순수하게 다양한 의견을 종합해보기 위한 글이었습니다.

  • cathei
    217
    2021-03-24 23:43:48

    C의 sizeof 연산자는 컴파일 타임에 값이 결정되므로 sizeof(a) / sizeof(*a) 는 상수로 최적화됩니다. 따라서 별도의 변수에 값을 할당하거나 루프를 역순으로 도는 것이 성능 향상에 도움이 되지 않습니다.

  • 꽃중년보넥스
    -1k
    2021-03-28 21:29:23

    포인트는 3가지네요.

    + 증감연산간의 차이 asm의 ADD, SUB ===> 1tick으로 동일

    + 비교연산이 0과의 비교가 될 경우 asm의 CMP의 상황 ===>

       요즘 CPU는 예측실행을 함으로 이익의 가능성 있음

    + 연속된 메모리공간(배열)을 역순으로 순회할 경우의 상황 ===>

       정순과 랜덤은 확실히 손해보는 것이 있고(2배이상)

       역순도 마찬가지 일 것이라고 생각


    즉, 저의 판단은 같은 상수값 비교일지라도

    0과의 비교를 만들어 내면 다소의 이익은 발생할 것이나

    연속된 메모리공간을 사용하는 부분이 있어서 역순접근은

    손해를 보니까.. 결국 쌤쌤이라고 봅니다. ㅎ


    -2
  • 로그인을 하시면 댓글을 등록할 수 있습니다.