사소한 조건문이 루프의 초기화 섹션으로 이동해야합니까?


21

stackoverflow.com 의이 질문 에서이 아이디어를 얻었습니다.

다음과 같은 패턴이 일반적입니다.

final x = 10;//whatever constant value
for(int i = 0; i < Math.floor(Math.sqrt(x)) + 1; i++) {
  //...do something
}

내가 만들려고하는 요점은 조건문이 복잡하고 변경되지 않는다는 것입니다.

루프의 초기화 섹션에서 선언하는 것이 더 낫습니까?

final x = 10;//whatever constant value
for(int i = 0, j = Math.floor(Math.sqrt(x)) + 1; i < j; i++) {
  //...do something
}

이것이 더 명확합니까?

조건식이 다음과 같은 단순한 경우

final x = 10;//whatever constant value
for(int i = 0, j = n*n; i > j; j++) {
  //...do something
}

47
루프 의 줄로 옮기는 것만으로 도 이름을 알 수 있습니다.
jonrsharpe

2
@Mehrdad : 동일하지 않습니다. 경우 x크기가 큰 경우, Math.floor(Math.sqrt(x))+1동일합니다 Math.floor(Math.sqrt(x)). :-)
R ..

5
@jonrsharpe 변수의 범위가 넓어지기 때문입니다. 나는 그것이 반드시 그렇게하지 않는 좋은 이유 라고 말하지는 않지만, 그것이 어떤 사람들이하지 않는 이유입니다.
Kevin Krumwiede

3
@KevinKrumwiede 스코프가 문제라면 코드를 자체 블록에 넣는 등의 방법으로 제한하십시오 { x=whatever; for (...) {...} }.
Blrfl

2
@jonrsharpe init 섹션에서 선언 할 때 적절한 이름을 지정할 수 있습니다. 내가 거기에 넣겠다고 말하지 않고; 별도라면 읽기가 더 쉽습니다.
JollyJoker

답변:


62

내가하는 일은 다음과 같습니다.

void doSomeThings() {
    final x = 10;//whatever constant value
    final limit = Math.floor(Math.sqrt(x)) + 1;
    for(int i = 0; i < limit; i++) {
         //...do something
    }
}

솔직히 루프 헤더에 초기화 j(현재 limit)를 넣는 유일한 이유 는 올바르게 범위를 유지하는 것입니다. 문제가 아닌 것이 좋은 밀폐 범위라는 것을 확인하는 데 필요한 모든 것.

나는 빠른 욕망에 감사 할 수 있지만 진정한 이유없이 가독성을 희생하지는 않습니다.

물론 컴파일러가 최적화하여 여러 변수를 초기화하는 것이 합법적 일 수 있지만 루프는 디버그하기가 어렵습니다. 인간에게 친절하십시오. 이것이 실제로 우리 속도를 늦추는 것으로 판명되면 그것을 고칠만큼 충분히 이해하는 것이 좋습니다.


좋은 지적. 표현식이 간단한 경우 다른 변수를 신경 쓰지 않는다고 가정합니다. 예를 들어 변수에 for(int i = 0; i < n*n; i++){...}할당하지 n*n않겠습니까?
Celeritas

1
나는 할 것이다. 그러나 속도가 아닙니다. 가독성을 위해. 읽을 수있는 코드는 그 자체로는 빠르다.
candied_orange

1
범위를 지정하는 문제조차도 상수 ( final) 로 표시하면 사라집니다 . 컴파일러 강제 변경을 방지하는 상수가 나중에 함수에서 액세스 가능한지 누가 신경 쓰나요?
jpmc26

큰 일이 일어날 것이라고 생각합니다. 나는 예제가 sqrt를 사용한다는 것을 알고 있지만 다른 함수라면 어떨까요? 기능이 순수합니까? 항상 같은 가치를 기대하고 있습니까? 부작용이 있습니까? 부작용이 반복 할 때마다 발생하는 영향입니까?
Pieter B

38

좋은 컴파일러는 두 가지 방법으로 동일한 코드를 생성하므로 성능을 원한다면 중요한 루프에 있고 실제로 프로파일 링하여 차이를 발견 한 경우에만 변경하십시오 . 컴파일러가 최적화 할 수 없더라도 사람들이 함수 호출 사례에 대한 의견에서 지적했듯이 대다수의 상황에서 성능 차이는 너무 작아 프로그래머가 고려할 가치가 없습니다.

하나...

우리는 코드가 주로 인간 간의 의사 소통 수단이라는 것을 잊지 말아야합니다. 두 옵션 모두 다른 사람과 잘 소통하지는 않습니다. 첫 번째는 매 반복마다 표현식을 계산해야한다는 인상을주고, 초기화 섹션에있는 두 번째는 루프 내 어딘가에 업데이트되어 실제로는 일정하다는 것을 의미합니다.

실제로 루프에서 꺼내어 final코드를 읽는 사람에게 즉시 그리고 풍부하게 명확하게하기를 원합니다 . 변수의 범위를 넓히기 때문에 이상적이지는 않지만 둘러싸는 함수에는 그 루프보다 많은 것이 포함되어서는 안됩니다.


5
함수 호출을 포함하기 시작하면 컴파일러가 최적화하기가 훨씬 어려워집니다. 컴파일러에는 Math.sqrt에 부작용이 없다는 특별한 지식이 있어야합니다.
Peter Green

@PeterGreen 이것이 Java 인 경우 JVM이이를 알아낼 수 있지만 시간이 걸릴 수 있습니다.
chrylis

질문은 C ++과 Java 모두로 태그가 지정됩니다. 나는 고급 Java의 JVM이 얼마나되는지 알 수 없으며 언제 그것을 알아낼 수 없으며 이해할 수는 없지만 C ++ 컴파일러는 일반적으로 그것을 알아낼 수는 없지만 컴파일러에 알리는 비표준 주석이 없으면 함수가 순수하다는 것을 알고 있습니다. 또는 함수 본문이 표시되고 모든 간접적으로 호출 된 함수는 동일한 기준으로 순수한 것으로 감지 될 수 있습니다. 참고 : 부작용이 없다고해서 루프 상태에서 벗어날 수는 없습니다. 루프 본체가 상태를 수정할 수있는 경우 전역 상태에 의존하는 함수는 루프 밖으로 이동할 수 없습니다.
hvd

Math.sqrt (x)가 Mymodule.SomeNonPureMethodWithSideEffects (x)로 바뀌면 재미 있습니다.
Pieter B

9

@Karl Bielefeldt의 답변에서 언급했듯이 이것은 일반적으로 문제가 아닙니다.

그러나 한때 C와 C ++의 일반적인 문제였으며 코드 가독성을 줄이지 않고 문제를 회피하는 트릭이 생겼 습니다0 .

final x = 10;//whatever constant value
for(int i = Math.floor(Math.sqrt(x)); i >= 0; i--) {
  //...do something
}

이제 모든 반복의 조건은 모든 >= 0컴파일러가 1 또는 2 개의 어셈블리 명령어로 컴파일하는 것입니다. 지난 수십 년 동안 이루어진 모든 CPU에는 이와 같은 기본 점검이 있어야합니다. 내 x64 컴퓨터에서 빠른 검사를 수행하면 예상대로 cmpl $0x0, -0x14(%rbp)(long-int-comare value 0 vs. 레지스터 rbp offsetted -14) 및 jl 0x100000f59(2nd-arg에 대해 이전 비교가 참이면 루프 뒤의 명령으로 점프 함 ) <1 차 인수”) .

+ 1에서 Math.floor(Math.sqrt(x)) + 1;를 제거했습니다 . 수학이 운동하기 위해서는 시작 값이이어야합니다 int i = «iterationCount» - 1. 또한 반복자에 서명해야합니다. unsigned int작동하지 않으며 컴파일러 경고가 발생합니다.

~ 20 년 동안 C 기반 언어로 프로그래밍 한 후 앞으로 색인 색인을 작성해야하는 특별한 이유가없는 한 역 색인 색인 루프 만 작성합니다. 조건부에서 간단한 확인 외에도 역 반복은 종종 반복적 인 배열 돌연변이 동안 문제가 될 수있는 것을 회피합니다.


1
당신이 쓰는 모든 것은 기술적으로 정확합니다. 모든 최신 CPU는 특별한 명령이 있는지 여부에 관계없이 순방향 반복 루프와 잘 작동하도록 설계되었으므로 명령에 대한 의견은 오해의 소지가 있습니다. 어쨌든 대부분의 시간은 대개 반복을 수행하지 않고 루프 내부 에서 소비 됩니다 .
Jørgen Fogh

4
최신 컴파일러 최적화는 "정상적인"코드를 염두에두고 설계되었습니다. 대부분의 경우 컴파일러는 최적화 "트릭"사용 여부에 관계없이 빠른 코드를 생성합니다. 그러나 일부 트릭은 실제로 얼마나 복잡한 지에 따라 최적화를 방해 할 수 있습니다. 특정 트릭을 사용하면 코드를 더 읽기 쉽게 만들거나 일반적인 버그를 잡는 데 도움이되지만, "이런 방식으로 코드를 작성하면 더 빠를 것"이라고 생각하지 마십시오. 일반적인 방법으로 코드를 작성한 다음 프로파일 링하여 최적화해야 할 장소를 찾으십시오.
0x5453

또한 unsigned검사를 수정하면 카운터가 작동합니다 (가장 쉬운 방법은 양쪽에 동일한 값을 추가하는 것입니다). 예를 들어, 어떤 감소를 위해 Dec, 검사가 (i + Dec) >= Dec항상 같은 결과가 있어야 signed검사 i >= 0모두, signed그리고 unsigned한 언어가 잘 정의 된 랩 어라운드 규칙이로, 카운터 unsigned변수 (특히, -n + n == 0모두 사실이어야 signed하고 unsigned). 그러나 >=0컴파일러에 최적화가 없으면 서명 된 검사 보다 효율성이 떨어질 수 있습니다 .
Justin Time 2 복원 상태 Monica

1
@JustinTime 예, 서명 된 요구 사항이 가장 어려운 부분입니다. Dec시작 값과 끝 값 모두 에 상수를 추가하면 작동하지만 훨씬 직관적이지 않으며 i배열 인덱스로 사용 하는 경우 unsigned int arrayI = i - Dec;루프 본문에서 수행해야합니다 . 서명되지 않은 반복자가 붙어있을 때 순방향 반복을 사용합니다. 종종 i <= count - 1논리를 역 반복 루프와 평행하게 유지하기 위한 조건이 있습니다.
Slipp D. Thompson

1
@ SlippD.Thompson Dec구체적으로 시작 값과 끝 값을 더하는 것이 아니라 조건 Dec을 양쪽에서 확인하는 것을 의미했습니다 . for (unsigned i = N - 1; i + 1 >= 1; i--) /*...*/ 이를 통해 i루프 내에서 정상적으로 사용할 수 있으며, 조건의 왼쪽에서 가능한 가장 낮은 값은 0(랩 어라운드가 방해하지 않도록) 보장합니다 . 그건 분명 하지만, 서명되지 않은 카운터로 작업 할 때 앞으로 반복을 사용하는 것이 훨씬 간단합니다.
저스틴 타임 2 복원 상태 모니카

3

Math.sqrt (x)가 Mymodule.SomeNonPureMethodWithSideEffects (x)로 바뀌면 재미 있습니다.

기본적으로 내 modus operandi는 다음과 같습니다. 무언가가 항상 같은 값을 줄 것으로 예상되면 한 번만 평가하십시오. 예를 들어 List.Count와 같이 루프 작동 중에 목록이 변경되지 않으면 루프 외부의 카운트를 다른 변수로 가져옵니다.

이러한 "카운트"중 일부는 특히 데이터베이스를 처리 할 때 놀랍도록 비쌀 수 있습니다. 목록을 반복하는 동안 변경되지 않아야하는 데이터 세트에서 작업하는 경우에도 마찬가지입니다.


개수가 비싸면 전혀 사용하지 않아야합니다. 대신에 다음과 같은 작업을 수행해야합니다.for( auto it = begin(dataset); !at_end(it); ++it )
Ben Voigt

@BenVoigt 반복자를 사용하는 것이 확실히 그 작업에 가장 좋은 방법입니다. 부작용이없는 순수한 방법을 사용하는 것에 대한 요점을 설명하기 위해 언급했습니다.
Pieter B

0

제 생각에는 이것은 언어마다 매우 다릅니다. 예를 들어 C ++ 11을 사용하는 경우 조건 검사가 constexpr함수 인 경우 컴파일러는 매번 동일한 값을 생성한다는 것을 알고 여러 실행을 최적화 할 가능성이 매우 높습니다.

그러나 함수 호출이 아닌 라이브러리 함수 인 경우 constexpr 경우 컴파일러는 인라인이 아니기 때문에 순수하게 추론 할 수없는 한이를 추론 할 수 없으므로 모든 반복에서 거의 확실히 그것을 실행할 것입니다.

Java에 대해서는 잘 알지 못하지만 JIT 컴파일 된 컴파일러는 런타임에 조건을 인라인하고 최적화하기에 충분한 정보가 런타임에 있다고 생각합니다. 그러나 이것은 좋은 컴파일러 디자인에 의존 할 것이며,이 루프를 결정하는 컴파일러는 우리가 추측 할 수있는 최적화 우선 순위였습니다.

개인적으로 가능하다면 for 루프 안에 조건을 넣는 것이 약간 더 우아하다고 생각하지만 복잡하다면 constexpror 또는 inline함수에 작성하거나 함수가 순수하고 최적화 가능하다는 것을 암시하는 언어 로 작성합니다 . 이것은 읽을 수없는 거대한 선을 만들지 않고 의도를 명확하게하고 관용적 루프 스타일을 유지합니다. 또한 독자적인 기능인 경우 조건 점검에 이름을 부여하므로 독자는 복잡한 경우 읽지 않고 점검 내용을 논리적으로 즉시 볼 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.