IT TIP

루프 내부 또는 외부에 개체를 선언 하시겠습니까?

itqueen 2021. 1. 7. 20:14
반응형

루프 내부 또는 외부에 개체를 선언 하시겠습니까?


다음 코드 스 니펫에 대한 성능 저하가 있습니까?

for (int i=0; i<someValue; i++)
{
    Object o = someList.get(i);
    o.doSomething;
}

아니면이 코드가 실제로 더 의미가 있습니까?

Object o;
for (int i=0; i<someValue; i++)
{
    o = someList.get(i);
    o.doSomething;
}

바이트 코드에서이 두 가지가 완전히 동일하다면 분명히 첫 번째 방법이 스타일 측면에서 더 좋아 보이지만 이것이 사실인지 확인하고 싶습니다.


오늘날의 컴파일러에서는 그렇지 않습니다. 다음 사람에게 훨씬 더 읽기 쉽기 때문에 가능한 가장 작은 범위의 개체를 선언합니다.


Hoare를 인용 할 수있는 Knuth를 인용하면 다음과 같습니다.

조기 최적화는 모든 악의 근원입니다.

컴파일러가 루프 외부에서 변수를 정의하여 약간 더 빠른 코드를 생성할지 여부는 논쟁의 여지가 있으며, 그렇지 않을 것이라고 생각합니다. 나는 그것이 동일한 바이트 코드를 생성 할 것이라고 생각 합니다.

루프 내 선언을 사용하여 변수의 범위를 올바르게 지정하여 방지 할 수있는 오류 수와이를 비교하십시오.


루프 내에서 Object o를 선언해도 성능이 저하되지 않습니다. 컴파일러는 매우 유사한 바이트 코드를 생성하고 올바른 최적화를 수행합니다.

유사한 예제에 대해서는 Myth-루프 내부에서 루프 변수를 정의하는 것이 성능좋지 않음 문서를 참조하십시오 .


javap -c로 코드를 디스 어셈블하고 컴파일러가 실제로 내보내는 내용을 확인할 수 있습니다. 내 설정에서 (Java 1.5 / mac 이클립스로 컴파일 됨) 루프의 바이트 코드는 동일합니다.


첫 번째 코드는 o변수 범위를 for블록으로 제한하므로 더 좋습니다 . 성능 관점에서 보면 Java에는 영향을 미치지 않지만 하위 수준의 컴파일러에는 영향을 미칠 수 있습니다. 첫 번째 작업을 수행하면 변수를 레지스터에 넣을 수 있습니다.

사실, 어떤 사람들은 컴파일러가 멍청하면 두 번째 조각이 성능면에서 더 좋다고 생각할 수 있습니다. 이것은 어떤 강사가 대학에서 나에게 말한 것이고 나는이 제안에 대해 그를 비웃었습니다! 기본적으로 컴파일러는 메서드 시작시 (스택 포인터를 조정하여) 메서드의 로컬 변수에 대해 스택에 메모리를 할당하고 메서드 끝에서 해제합니다 (다시 스택 포인터를 조정하여 C ++가 아니라고 가정). 또는 호출 할 소멸자가 없습니다). 따라서 메서드의 모든 스택 기반 지역 변수는 선언 된 위치와 필요한 메모리 양에 관계없이 한 번에 할당됩니다. 사실 컴파일러가 멍청하면 성능면에서 차이가 없지만 충분히 똑똑하다면 첫 번째 코드가 실제로 더 좋을 수 있습니다. 컴파일러가 변수의 범위와 수명을 이해하는 데 도움이됩니다! 그건 그렇고, 정말 똑똑하다면 실제 범위를 추론하므로 성능에 전혀 차이가 없어야합니다.

new물론 사용하는 객체의 구성은 선언하는 것과 완전히 다릅니다.

나는 가독성이 성능보다 더 중요하다고 생각하며 가독성 관점에서 첫 번째 코드가 확실히 더 좋습니다.


나는 자바를 모른다는 것을 인정해야한다. 그러나이 두 가지는 동등합니까? 개체 수명이 동일합니까? 첫 번째 예에서는 o가 루프가 종료되는 즉시 가비지 콜렉션에 적합하다고 가정합니다 (Java를 알지 못함).

그러나 두 번째 예에서는 외부 범위 (표시되지 않음)가 종료 될 때까지 가비지 수집에 적합하지 않을까요?


너무 일찍 최적화하지 마십시오. 다음 중 하나보다 낫습니다.

for(Object o : someList) {
    o.doSomething();
}

상용구를 제거하고 의도를 명확히하기 때문입니다.

임베디드 시스템에서 작업하지 않는 한 모든 베팅이 꺼집니다. 그렇지 않으면 JVM을 능가하려고하지 마십시오.


나는 요즘 대부분의 컴파일러가 후자의 옵션을 수행하기에 충분히 똑똑하다고 항상 생각했습니다. 그것이 사실이라고 가정하면 첫 번째 것도 더 좋아 보인다고 말할 것입니다. 루프가 매우 커지면 o가 선언 된 곳을 둘러 볼 필요가 없습니다.


이것들은 다른 의미를 가지고 있습니다. 어느 것이 더 의미가 있습니까?

"성능상의 이유로"객체를 재사용하는 것은 종종 잘못된 것입니다.

문제는 객체가 "의미"하는 것입니다. 당신은 그것을 왜 만들고 있습니까? 그것은 무엇을 상징합니까? 객체는 실제 사물과 평행해야합니다. 사물이 생성되고 상태가 변경되며 이유에 따라 상태를보고합니다.

그 이유는 무엇입니까? 개체 모델은 어떻게 그 이유를 반영합니까?


이 질문의 중심을 잡으려면 ... [JLS가 허용하는 경우 비 JVM 구현은 다른 작업을 수행 할 수 있습니다 ...]

먼저, 예제에서 로컬 변수 "o"는 실제 객체가 아니라 포인터라는 점을 명심하십시오.

모든 로컬 변수는 4 바이트 슬롯의 런타임 스택에 할당됩니다. 복식과 장수에는 두 개의 슬롯이 필요합니다. 다른 프리미티브와 포인터는 하나를 사용합니다. (부울조차도 전체 슬롯을 차지합니다)

메서드 호출마다 고정 된 런타임 스택 크기를 만들어야합니다. 이 크기는 메서드의 특정 지점에서 필요한 최대 로컬 변수 "슬롯"에 의해 결정됩니다.

위의 예에서 코드의 두 버전 모두 메서드에 대해 동일한 최대 수의 지역 변수를 필요로합니다.

두 경우 모두 동일한 바이트 코드가 생성되어 런타임 스택에서 동일한 슬롯을 업데이트합니다.

즉, 성능 저하가 전혀 없습니다.

그러나 메서드의 나머지 코드에 따라 "루프 외부 선언"버전에는 실제로 더 큰 런타임 스택 할당이 필요할 수 있습니다. 예를 들어, 비교

for (...) { Object o = ... }
for (...) { Object o = ... }

Object o;
for (...) {  /* loop 1 */ }
for (...) { Object x =...; }

첫 번째 예에서 두 루프 모두 동일한 런타임 스택 할당이 필요합니다.

두 번째 예에서 "o"는 루프를 지나서 있기 때문에 "x"에는 추가 런타임 스택 슬롯이 필요합니다.

이것이 도움이되기를 바랍니다 .-- Scott


In both cases the type info for the object o is determined at compile time.In the second instance, o is seen as being global to the for loop and in the first instance, the clever Java compiler knows that o will have to be available for as long as the loop lasts and hence will optimise the code in such a way that there wont be any respecification of o's type in each iteration. Hence, in both cases, specification of o's type will be done once which means the only performance difference would be in the scope of o. Obviously, a narrower scope always enhances performance, therefore to answer your question: no, there is no performance penalty for the first code snip; actually, this code snip is more optimised than the second.

In the second snip, o is being given unnecessary scope which, besides being a performance issue, can be also a security issue.


The first makes far more sense. It keeps the variable in the scope that it is used in. and prevents values assigned in one iteration being used in a later iteration, this is more defensive.

The former is sometimes said to be more efficient but any reasonable compiler should be able to optimise it to be exactly the same as the latter.


As someone who maintains more code than writes code.

Version 1 is much preferred - keeping scope as local as possible is essential for understanding. Its also easier to refactor this sort of code.

As discussed above - I doubt this would make any difference in efficiency. In fact I would argue that if the scope is more local a compiler may be able to do more with it!


When using multiple threads (if your doing 50+) then i found this to be a very effective way of handling ghost thread problems:

Object one;
Object two;
Object three;
Object four;
Object five;
try{
for (int i=0; i<someValue; i++)
{
o = someList.get(i);
o.doSomething;
}
}catch(e){
e.printstacktrace
}
finally{
one = null;
two = null;
three = null;
four = null;
five = null;
System.gc();
}

The answer depends partly on what the constructor does and what happens with the object after the loop, since that determines to a large extent how the code is optimized.

If the object is large or complex, absolutely declare it outside the loop. Otherwise, the people telling you not to prematurely optimize are right.


I've actually in front of me a code which looks like this:

for (int i = offset; i < offset + length; i++) {
    char append = (char) (data[i] & 0xFF);
    buffer.append(append);
}
...
for (int i = offset; i < offset + length; i++) {
    char append = (char) (data[i] & 0xFF);
    buffer.append(append);
}
...
for (int i = offset; i < offset + length; i++) {
    char append = (char) (data[i] & 0xFF);
    buffer.append(append);
}

So, relying on compiler abilities, I can assume there would be only one stack allocation for i and one for append. Then everything would be fine except the duplicated code.

As a side note, java applications are known to be slow. I never tried to do profiling in java but I guess the performance hit comes mostly from memory allocation management.

ReferenceURL : https://stackoverflow.com/questions/377763/declare-an-object-inside-or-outside-a-loop

반응형