변수 선언 이전과 루프 내 간의 차이?
나는 일반적으로 루프 내부에서 반복적으로 나타나는 것과 달리 루프 전에 버림 변수를 선언하는 것이 (성능) 차이를 만드는 것이 아닌가 항상 생각해 왔다.Java의 A(quite note) 예:
a) 루프 전 선언:
double intermediateResult;
for(int i=0; i < 1000; i++){
intermediateResult = i;
System.out.println(intermediateResult);
}
b) 내부 루프:
for(int i=0; i < 1000; i++){
double intermediateResult = i;
System.out.println(intermediateResult);
}
a와 b 중 어느 것이 더 나은가?
거듭되는 가변 선언(예: b)은 이론상으로는 더 많은 오버헤드를 발생시키지만, 컴파일러는 충분히 영리해서 문제가 되지 않는다고 나는 의심한다.사례 b는 변수의 범위가 더 좁고, 변수의 범위가 사용되는 위치로 제한되는 장점이 있다.그래도 나는 예 a에 따라 코딩을 하는 편이야.
편집: 나는 특히 Java 케이스에 관심이 있다.
a와 b 중 어느 것이 더 나은가?
성능의 관점에서 보면, 그것을 측정해야 할 것이다. (그리고 내 생각에는 차이를 측정할 수 있다면 컴파일러는 별로 좋지 않다.)
유지관리의 관점에서 보면 b가 더 좋다.변수를 동일한 위치에서 가능한 가장 좁은 범위에서 선언하고 초기화하십시오.선언과 초기화 사이에 틈새 구멍을 남기지 말고, 필요 없는 네임스페이스를 오염시키지 마십시오.
네 A와 B를 각각 20번씩 반복해서 1억번씩 반복했어(JVM - 1.5.0)
A: 평균 실행 시간: 0.074초
B: 평균 실행 시간 : 0.067초
놀랍게도 B가 조금 더 빨랐다.컴퓨터처럼 빠른 속도로 이것을 정확하게 측정할 수 있는지 말하기가 어렵다.나도 A방식으로 코디하고 싶지만 별로 중요하지 않다고 말하고 싶다.
언어와 정확한 용도에 따라 다르다.예를 들어, C# 1에서는 아무런 차이가 없었다.C# 2에서는 익명의 방법(또는 C# 3의 람다 표현식)에 의해 로컬 변수가 캡처되는 경우 매우 현저한 차이를 만들 수 있다.
예:
using System;
using System.Collections.Generic;
class Test
{
static void Main()
{
List<Action> actions = new List<Action>();
int outer;
for (int i=0; i < 10; i++)
{
outer = i;
int inner = i;
actions.Add(() => Console.WriteLine("Inner={0}, Outer={1}", inner, outer));
}
foreach (Action action in actions)
{
action();
}
}
}
출력:
Inner=0, Outer=9
Inner=1, Outer=9
Inner=2, Outer=9
Inner=3, Outer=9
Inner=4, Outer=9
Inner=5, Outer=9
Inner=6, Outer=9
Inner=7, Outer=9
Inner=8, Outer=9
Inner=9, Outer=9
차이점은 모든 동작이 동일한 것을 캡처한다는 것이다.outer
변수, 그러나 각각은 고유한 개별적인inner
가변의
다음은 내가 에 쓰고 편찬한 것이다.네트
double r0;
for (int i = 0; i < 1000; i++) {
r0 = i*i;
Console.WriteLine(r0);
}
for (int j = 0; j < 1000; j++) {
double r1 = j*j;
Console.WriteLine(r1);
}
이것이 내가 얻은 것이다.CIL이 다시 코드로 렌더링되는 경우 NET Reflector
for (int i = 0; i < 0x3e8; i++)
{
double r0 = i * i;
Console.WriteLine(r0);
}
for (int j = 0; j < 0x3e8; j++)
{
double r1 = j * j;
Console.WriteLine(r1);
}
그래서 편찬 후에는 둘 다 똑같이 보인다.관리되는 언어에서 코드는 CL/바이트 코드로 변환되고 실행 시 기계 언어로 변환된다.그래서 기계어에서는 스택에 더블이 생성되지 않을 수도 있다.단지 코드에서 에 대한 일시적인 변수라는 것을 반영하기 때문에 등록일 수도 있다.WriteLine
를 위한 루프를 위한 최적화 규칙이 있다.그래서 보통 남자들은 그것에 대해 걱정하지 말아야 하는데, 특히 관리 언어에서는 더욱 그렇다.예를 들어, 예를 들어, 코드 관리를 최적화할 수 있는 경우, 예를 들어, 문자열을 여러 개 연결해야 하는 경우 등이 있다.string a; a+=anotherstring[i]
사용 vs 회사StringBuilder
다 에 큰 양쪽의 성능은 매우 큰 차이가 있다.컴파일러가 당신의 코드를 최적화하지 못하는 경우가 많은데, 그것은 그것이 더 큰 범위에서 의도된 것을 알아낼 수 없기 때문이다.하지만 그것은 당신을 위해 기본적인 것들을 최적화할 수 있다.
이건 VB에 나오는 고트카야.NET. Visual Basic 결과는 이 예에서 변수를 다시 초기화하지 않는다.
For i as Integer = 1 to 100
Dim j as Integer
Console.WriteLine(j)
j = i
Next
' Output: 0 1 2 3 4...
을 출력할 할 때 이하하하면 0을 인쇄할 수 있다. (Visual Basic은 언시시시시시이이!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!).i
그 후엔 매번
a를 추가하면= 0
그러나 다음과 같은 결과를 기대할 수 있다.
For i as Integer = 1 to 100
Dim j as Integer = 0
Console.WriteLine(j)
j = i
Next
'Output: 0 0 0 0 0...
나는 간단한 테스트를 했다:
int b;
for (int i = 0; i < 10; i++) {
b = i;
}
대
for (int i = 0; i < 10; i++) {
int b = i;
}
이 코드들을 gcc - 5.2.0으로 정리했다.그리고 이 두 코드의 메인()을 분해해 보니 그 결과가 다음과 같다.
1º:
0x00000000004004b6 <+0>: push rbp
0x00000000004004b7 <+1>: mov rbp,rsp
0x00000000004004ba <+4>: mov DWORD PTR [rbp-0x4],0x0
0x00000000004004c1 <+11>: jmp 0x4004cd <main+23>
0x00000000004004c3 <+13>: mov eax,DWORD PTR [rbp-0x4]
0x00000000004004c6 <+16>: mov DWORD PTR [rbp-0x8],eax
0x00000000004004c9 <+19>: add DWORD PTR [rbp-0x4],0x1
0x00000000004004cd <+23>: cmp DWORD PTR [rbp-0x4],0x9
0x00000000004004d1 <+27>: jle 0x4004c3 <main+13>
0x00000000004004d3 <+29>: mov eax,0x0
0x00000000004004d8 <+34>: pop rbp
0x00000000004004d9 <+35>: ret
대
2º
0x00000000004004b6 <+0>: push rbp
0x00000000004004b7 <+1>: mov rbp,rsp
0x00000000004004ba <+4>: mov DWORD PTR [rbp-0x4],0x0
0x00000000004004c1 <+11>: jmp 0x4004cd <main+23>
0x00000000004004c3 <+13>: mov eax,DWORD PTR [rbp-0x4]
0x00000000004004c6 <+16>: mov DWORD PTR [rbp-0x8],eax
0x00000000004004c9 <+19>: add DWORD PTR [rbp-0x4],0x1
0x00000000004004cd <+23>: cmp DWORD PTR [rbp-0x4],0x9
0x00000000004004d1 <+27>: jle 0x4004c3 <main+13>
0x00000000004004d3 <+29>: mov eax,0x0
0x00000000004004d8 <+34>: pop rbp
0x00000000004004d9 <+35>: ret
그것은 같은 결과였다.두 코드가 같은 것을 생산한다는 증거가 아닌가?
언어에 의존한다 - IIRC C#는 이것을 최적화하기 때문에 아무런 차이가 없지만, 자바스크립트(예를 들어)는 매번 전체 메모리 할당 셰뱅을 할 것이다.
나는 항상 (컴파일러에 의존하지 않고) A를 사용하며 다음과 같이 다시 쓸 수도 있다.
for(int i=0, double intermediateResult=0; i<1000; i++){
intermediateResult = i;
System.out.println(intermediateResult);
}
이것은 여전히 제한된다.intermediateResult
루프 범위까지 이동하지만 반복할 때마다 다시 덮지 않는다.
내 생각에는 b가 더 나은 구조라고 생각한다.a에서는 루프가 완료된 후 mederalResult의 마지막 값이 고정된다.
편집: 값 유형과 큰 차이가 없지만 참조 유형은 다소 무거울 수 있다.개인적으로, 나는 가능한 한 빨리 변수의 참조가 해제되는 것을 좋아하고, b는 당신을 위해 그렇게 한다.
나는 몇몇 컴파일러들이 둘 다 같은 코드로 최적화할 수 있을 것이라고 의심하지만, 확실히 전부는 아니다.그러니 전자와 함께 있는 게 좋을 거야.후자의 유일한 이유는 선언된 변수가 루프 내에서만 사용되는지 확인하기 위해서입니다.
일반적으로 나는 내 변수를 가장 안쪽의 가능한 범위에서 선언한다.그래서, 만약 당신이 루프를 벗어나서 중간 결과물을 사용하지 않는다면, 나는 B와 함께 갈 것이다.
동료는 첫 번째 양식을 선호하며, 그것이 최적화라고 말하고, 선언문을 다시 사용하는 것을 선호한다.
나는 두 번째 것을 더 선호한다(그리고 내 동료를 설득하려고 노력한다!;-)
- 변수의 범위를 필요한 곳으로 줄여주는 것이 좋은 일이다.
- Java는 성능에서 큰 차이가 없을 정도로 최적화한다.IIRC, 아마도 두번째 형태는 훨씬 더 빠를 것이다.
어쨌든, 컴파일러 및/또는 JVM의 품질에 의존하는 조기 최적화의 범주에 속한다.
람다 등에서 변수를 사용하는 경우에는 C#에 차이가 있다.그러나 일반적으로 컴파일러는 변수가 루프 내에서만 사용된다고 가정할 때 기본적으로 동일한 작업을 수행한다.
기본적으로 동일하다는 점을 고려할 때:버전 b는 이 변수가 루프 후에 사용되지 않으며 사용할 수 없다는 것을 독자들에게 훨씬 더 분명하게 해준다는 점에 유의하십시오.또한 버전 b는 훨씬 쉽게 리팩터링된다. 버전 a에서는 루프 본체를 자체 방법으로 추출하는 것이 더 어렵다.또한 버전 b는 그러한 리팩터링에 부작용이 없음을 보장한다.
그래서, 버전은 나를 끝도 없이 짜증나게 한다. 왜냐하면 그것에는 이점이 없고 그것은 코드에 대해 추론하는 것을 훨씬 더 어렵게 만들기 때문이다.
음, 당신은 항상 그것을 위한 범위를 만들 수 있다.
{ //Or if(true) if the language doesn't support making scopes like this
double intermediateResult;
for (int i=0; i<1000; i++) {
intermediateResult = i;
System.out.println(intermediateResult);
}
}
이런 식으로 변수를 한 번만 선언하면, 루프를 떠나면 그것은 죽게 된다.
나는 컴파일러에 따라 다르며 일반적인 대답을 하기가 어렵다고 생각한다.
나는 항상 당신이 당신의 루프 안에서 당신의 변수를 선언한다면 당신은 기억을 낭비하고 있다고 생각해왔다.이런 게 있으면:
for(;;) {
Object o = new Object();
}
그러면 각 반복에 대해 객체를 생성해야 할 뿐만 아니라 각 객체에 대해 새로운 참조가 할당되어야 한다.쓰레기 수거기가 느리면 청소해야 할 참조 자료들이 널려 있는 것 같아.
단, 다음과 같은 사항이 있을 경우:
Object o;
for(;;) {
o = new Object();
}
그런 다음 단일 참조를 만들고 매번 새 객체를 할당하는 것뿐입니다.물론, 범위를 벗어나는 데 시간이 좀 더 걸릴 수도 있지만, 그 다음엔 한 가지 참고 사항만 다루면 돼.
내 연습은 다음과 같다.
변수의 유형이 단순하다면(int, double, ...) 변종 b(내부)를 선호한다.
이유: 변수의 범위 축소.변수의 유형이 단순하지 않으면(어떤 종류의 또는 ) 변종 a(외부)를 선호한다.
이유: ctor-dtor 통화 횟수 감소.
나는 아주 오랫동안 똑같은 질문을 받았다.그래서 나는 더 간단한 코드를 시험해 보았다.
결론:그러한 경우 성능 차이가 없다.
외부 루프 케이스
int intermediateResult;
for(int i=0; i < 1000; i++){
intermediateResult = i+2;
System.out.println(intermediateResult);
}
내부 루프 케이스
for(int i=0; i < 1000; i++){
int intermediateResult = i+2;
System.out.println(intermediateResult);
}
IntelliJ의 디컴파일러에 있는 컴파일된 파일을 확인했는데, 두 경우 모두 동일한 파일을 받았다. Test.class
for(int i = 0; i < 1000; ++i) {
int intermediateResult = i + 2;
System.out.println(intermediateResult);
}
나는 또한 이 답변에서 주어진 방법을 사용하여 두 케이스에 대한 코드를 분해했다.답변과 관련된 부분만 보여주겠다.
외부 루프 케이스
Code:
stack=2, locals=3, args_size=1
0: iconst_0
1: istore_2
2: iload_2
3: sipush 1000
6: if_icmpge 26
9: iload_2
10: iconst_2
11: iadd
12: istore_1
13: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
16: iload_1
17: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
20: iinc 2, 1
23: goto 2
26: return
LocalVariableTable:
Start Length Slot Name Signature
13 13 1 intermediateResult I
2 24 2 i I
0 27 0 args [Ljava/lang/String;
내부 루프 케이스
Code:
stack=2, locals=3, args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: sipush 1000
6: if_icmpge 26
9: iload_1
10: iconst_2
11: iadd
12: istore_2
13: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
16: iload_2
17: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
20: iinc 1, 1
23: goto 2
26: return
LocalVariableTable:
Start Length Slot Name Signature
13 7 2 intermediateResult I
2 24 1 i I
0 27 0 args [Ljava/lang/String;
세심한 주의를 기울인다면, 오직...Slot
에 배정된.i
그리고intermediateResult
에LocalVariableTable
외모 순서의 상품으로 교환될 수 있다.슬롯의 동일한 차이는 코드의 다른 라인에 반영된다.
- 추가 작업이 수행되지 않음
intermediateResult
두 경우 모두 여전히 국지적 변수여서 접근 시간 차이가 없다.
보너스
컴파일러는 많은 최적화를 하는데, 이 경우 어떤 일이 일어나는지 살펴보십시오.
제로 워크 케이스
for(int i=0; i < 1000; i++){
int intermediateResult = i;
System.out.println(intermediateResult);
}
분해된 제로 작업
for(int i = 0; i < 1000; ++i) {
System.out.println(i);
}
공연의 관점에서 보면, 밖이 더 좋다.
public static void outside() {
double intermediateResult;
for(int i=0; i < Integer.MAX_VALUE; i++){
intermediateResult = i;
}
}
public static void inside() {
for(int i=0; i < Integer.MAX_VALUE; i++){
double intermediateResult = i;
}
}
나는 두 기능을 각각 10억 번 실행했다.아웃소싱은 65밀리초가 걸렸다.인사이드는 1.5초가 걸렸다.
관심 있는 사람이 있으면 노드 4.0.0으로 JS 테스트를 했다.루프 외부에 선언하면 평균 1,000회 이상의 시험에서 시험당 1억 회 반복으로 ~.5 ms의 성능 향상이 나타났다.그래서 나는 그것을 읽기/유지관리하기 가장 쉬운 방법으로 쓰라고 말할 것이다. B, imo.나는 내 코드를 바이올린에 넣겠지만, 나는 성능-현재의 노드 모듈을 사용했다.암호는 다음과 같다.
var now = require("../node_modules/performance-now")
// declare vars inside loop
function varInside(){
for(var i = 0; i < 100000000; i++){
var temp = i;
var temp2 = i + 1;
var temp3 = i + 2;
}
}
// declare vars outside loop
function varOutside(){
var temp;
var temp2;
var temp3;
for(var i = 0; i < 100000000; i++){
temp = i
temp2 = i + 1
temp3 = i + 2
}
}
// for computing average execution times
var insideAvg = 0;
var outsideAvg = 0;
// run varInside a million times and average execution times
for(var i = 0; i < 1000; i++){
var start = now()
varInside()
var end = now()
insideAvg = (insideAvg + (end-start)) / 2
}
// run varOutside a million times and average execution times
for(var i = 0; i < 1000; i++){
var start = now()
varOutside()
var end = now()
outsideAvg = (outsideAvg + (end-start)) / 2
}
console.log('declared inside loop', insideAvg)
console.log('declared outside loop', outsideAvg)
A)는 B)보다 안전한 내기다.........만약 당신이 'int'나 'float'가 아닌 루프에서 구조를 초기화하고 있다면, 그러면 무엇이겠습니까?
맘에 들다
typedef struct loop_example{
JXTZ hi; // where JXTZ could be another type...say closed source lib
// you include in Makefile
}loop_example_struct;
//then....
int j = 0; // declare here or face c99 error if in loop - depends on compiler setting
for ( ;j++; )
{
loop_example loop_object; // guess the result in memory heap?
}
기억력 유출로 인한 문제에 직면하게 될 것이 분명하다!그러므로 나는 'A'가 더 안전한 반면 'B'는 가까운 소스 라이브러리에 작용하는 메모리 축적 esp에 취약하다고 믿는다.Linux에서 특별히 하위 툴인 'Helgrind'에서 'Valgrind' Tool을 확인하십시오.
재미있는 질문이다.내 경험으로 볼 때 코드를 위해 이 문제에 대해 토론할 때 고려해야 할 궁극적인 질문이 있다.
변수가 글로벌해야 하는 이유가 있는가?
코드를 구성하는데 더 좋고 코드 라인이 더 적게 필요하기 때문에 변수를 여러 번 국지적으로 선언하는 것과 달리 전 세계적으로 한 번만 선언하는 것이 타당하다.단, 한 가지 방법 내에서 국지적으로만 선언하면 된다면, 변수가 그 방법에만 전적으로 관련성이 있다는 것이 분명하도록 나는 그 방법으로 초기화할 것이다.이 변수를 후자 옵션을 선택할 경우 초기화되는 방법 이외의 다른 방법으로 호출하지 않도록 주의하십시오. - 코드는 현재 말한 내용을 알지 못하며 오류를 보고할 것이다.
또한, 참고로, 다른 방법들 간에 지역 변수 이름을 복제하지 마십시오. 목적이 거의 동일하더라도 혼란스러울 뿐이지요.
이것이 더 나은 형태다.
double intermediateResult;
int i = byte.MinValue;
for(; i < 1000; i++)
{
intermediateResult = i;
System.out.println(intermediateResult);
}
1) 이러한 방식으로 두 변수를 모두 한 번 선언하고 각 변수를 사이클에 대해 선언하지 않는다.2) 다른 모든 선택사항보다 더 뚱뚱한 과제3) 따라서 모범 사례 규칙은 반복 이외의 선언이다.
Go에서도 동일한 작업을 시도하고, 다음 작업을 사용하여 컴파일러 출력을 비교go tool compile -S
1.9.4로
조립기 출력에 따른 영차.
루프를 종료한 후 변수의 내용을 보고 싶을 때 (A)를 사용한다.디버깅할 때만 문제가 된다.코드 한 줄을 저장하기 때문에 코드를 좀 더 컴팩트하게 하고 싶을 때 (B)를 사용한다.
내 컴파일러가 충분히 똑똑하다는 것을 안다고 해도, 나는 그것에 의존하는 것을 좋아하지 않을 것이고, a) 변종을 사용할 것이다.
b) 변종은 루프 본체 후에 반드시 중간 결과물을 사용할 수 없게 해야 하는 경우에만 내게 의미가 있다.하지만 어쨌든 이렇게 절박한 상황은 상상조차 할 수 없다.......
편집: Jon Sket은 루프 내부의 가변 선언이 실제 의미 차이를 만들 수 있다는 것을 보여주면서 매우 좋은 지적을 했다.
참조URL: https://stackoverflow.com/questions/407255/difference-between-declaring-variables-before-or-in-loop
'IT이야기' 카테고리의 다른 글
Vue.js Safari 모바일 및 호환성 (0) | 2022.05.03 |
---|---|
Vue.js 2.0 모듈 빌드 실패:구문 오류:웹 팩, Elixir 및 꿀꺽을 사용한 예상치 못한 토큰 (0) | 2022.05.03 |
C 또는 C++의 좋은 gotos 예 (0) | 2022.05.03 |
Java 동기화된 메서드 잠금 객체 또는 메서드? (0) | 2022.05.03 |
뷰피 테이블에서 상세 행 전환 (0) | 2022.05.03 |