메모리 관리에 관한 엔트리 레벨 엔지니어 질문


9

엔트리 레벨 소프트웨어 개발자로 지위를 시작한 지 몇 달이 지났습니다. 이제 학습 곡선 (예 : 언어, 전문 용어, VB 및 C #의 구문)을 지나쳤으므로 더 나은 소프트웨어를 작성하기 위해보다 난해한 주제에 초점을 맞추기 시작했습니다.

동료 동료에게 제시 한 간단한 질문에 "저는 잘못된 것에 집중하고 있습니다"라고 대답했습니다. 나는이 동료를 존중하지만 이것이 "잘못 된 것"이라는 것에 동의하지 않습니다.

여기에 코드 (VB)와 질문이 있습니다.

참고 : GenerateAlert () 함수는 정수를 반환합니다.

Dim alertID as Integer = GenerateAlert()
_errorDictionary.Add(argErrorID, NewErrorInfo(Now(), alertID))    

대 ...

 _errorDictionary.Add(argErrorID, New ErrorInfo(Now(), GenerateAlert()))

나는 원래 후자를 쓰고 "Dim alertID"로 다시 써서 다른 사람이 쉽게 읽을 수있게 만들었습니다. 그러나 여기 내 관심사와 질문이있었습니다.

Dim AlertID로 이것을 쓰면 실제로 더 많은 메모리를 차지합니다. 유한하지만 더 많으며이 방법을 여러 번 호출해야 문제가 발생할 수 있습니까? .NET은이 개체 AlertID를 어떻게 처리합니까? .NET 외부에서는 사용 후 (서브의 끝 근처) 객체를 수동으로 처리해야합니다.

가비지 수집에만 의존하지 않는 지식이 풍부한 프로그래머가되고 싶습니다. 나는 이것을 생각하고 있는가? 내가 잘못된 것에 집중하고 있습니까?


1
첫 번째 버전이 더 읽기 쉽기 때문에 그가 100 %라는 것을 쉽게 알 수 있습니다. 컴파일러가 관심있는 것을 돌볼 수도 있습니다. 그렇지 않은 경우에도 조기 최적화 중입니다.
Rig

6
익명의 정수 대 명명 된 정수로 더 많은 메모리를 실제로 사용할 것이라고 확신하지 못합니다. 어쨌든 이것은 실제로 조기 최적화입니다. 이 수준에서 효율성에 대해 걱정할 필요가 있다면 (거의 확실하지는 않습니다) C # 대신 C ++이 필요할 수 있습니다. 성능 문제와 후드 아래에서 발생하는 상황을 이해하는 것이 좋지만 이것은 큰 숲의 작은 나무입니다.
psr

5
명명 된 대 익명 정수는 더 많은 메모리를 사용하지 않습니다. 특히 익명의 정수는 이름을 지정하지 않은 명명 된 정수이기 때문에 (컴파일러는 여전히 이름을 지정해야합니다). 기껏해야 명명 된 정수는 다른 범위를 가지므로 더 오래 살 수 있습니다. 익명의 정수는 메소드가 필요로하는 한만 살며, 명명 된 정수는 부모가 필요로하는 한 살았습니다.
Joel Etherton

보자 ... Integer가 클래스이면 힙에 할당됩니다. 로컬 변수 (스택에서 가장 가능성이 높은)는 이에 대한 참조를 보유합니다. 참조는 errorDictionary 객체로 전달됩니다. 런타임이 참조 계산 (또는 이와 같은)을 수행하는 경우 더 이상 참조가 없으면 힙 (객체)이 힙에서 할당 해제됩니다. 메소드가 종료되면 스택의 모든 것이 자동으로 "할당 해제"됩니다. 프리미티브 인 경우 스택에있게됩니다.
Paul

동료가 옳았습니다 : 귀하의 질문에 의해 제기 된 문제는 최적화가 아니라 가독성에 관한 것이 었습니다 .
haylem

답변:


25

"조기 최적화는 프로그래밍에서 모든 악 (또는 적어도 대부분)의 근원입니다." -도널드 크 누스

첫 번째 단계에서는 정확하고 깨끗하게 코드를 작성하십시오. 나중에 코드가 성능에 중요하다고 판단되면 (프로파일 러라고하는 도구가 있음) 다시 작성할 수 있습니다. 코드가 성능에 중요한 것으로 판단되지 않으면 가독성이 훨씬 중요합니다.

이러한 성능 및 최적화 주제를 파는 것이 가치가 있습니까? 불필요하게 회사의 달러에는 해당되지 않습니다.


1
다른 사람의 달러는 누구인가? 고용주는 실력보다는 기술의 증가로 혜택을받습니다.
Marcin

현재 과제에 직접적으로 기여하지 않는 과목? 당신은 자신의 시간에 이러한 것들을 추구해야합니다. 하루 종일 호기심을 불러 일으키는 모든 CompSci 아이템을 앉아서 연구했다면 아무것도 할 수 없었습니다. 그것이 저의 저녁입니다.
Christopher Berman

2
기묘한. 우리 중 일부는 개인적인 삶을 가지고 있으며, 고용주가 주로 연구를 통해 혜택을받습니다. 열쇠는 실제로 하루 종일 지출하지 않는 것입니다.
Marcin

6
잘 됐네요. 그러나 실제로 그것을 보편적 인 규칙으로 만들지는 않습니다. 또한 근로자가 직장에서 배우지 못하도록 방해하는 경우, 배우는 것을 방해하고 실제로 직원 개발 비용을 지불하는 다른 고용주를 찾도록 격려하기 만하면됩니다.
Marcin

2
위 의견에 언급 된 의견을 이해합니다. 점심 시간에 물어 봤습니다. :). 다시 한 번, 여기와 Stack Exchange 사이트 전체에 입력 해 주셔서 감사합니다. 귀중합니다!
Sean Hobbs

5

평범한 .NET 프로그램의 경우에는 너무 생각합니다. .NET 내부에서 일어나는 일의 너트와 볼트로 내려 가고 싶을 때가 있지만 상대적으로 드문 경우입니다.

내가 어려웠던 전환 중 하나는 C와 MASM 사용에서 90 년대의 클래식 VB 프로그래밍으로 전환하는 것이 었습니다. 크기와 속도에 맞게 모든 것을 최적화하는 데 익숙했습니다. 나는이 생각을 대부분 포기하고 VB가 효과를 발휘하도록해야만했다.


5

동료가 항상 다음과 같이 말했듯이

  1. 작동하게 만들다
  2. 모든 버그를 수정하여 완벽하게 작동
  3. SOLID로 만드십시오
  4. 성능이 느리면 최적화 적용

다시 말해 항상 KISS 를 염두에 두십시오 (간단한 바보 유지). 과도한 엔지니어링 때문에 일부 코드 로직을 지나치게 생각하면 다음에 로직을 변경하는 것이 문제가 될 수 있습니다. 그러나 코드를 깨끗하고 단순하게 유지하는 것은 항상 좋은 습관 입니다.

그러나 시간과 경험에 따라 어떤 코드가 냄새가 나고 곧 최적화가 필요한지 알 수 있습니다.


3

Dim AlertID로 이것을 써야합니까?

가독성이 중요합니다. 당신의 예에서하지만, 나는 당신이 정말로 일을하고 있다는 것을 확실하지 않다 더 읽기. GenerateAlert ()는 좋은 이름을 가지고 있으며 많은 노이즈를 추가하지 않습니다. 아마도 당신의 시간을 더 잘 사용할 것입니다.

실제로 더 많은 메모리를 차지합니다.

나는 그렇지 않다고 생각합니다. 이는 컴파일러가 비교적 간단한 최적화 방법입니다.

이 메소드를 여러 번 호출해야 문제가 발생할 수 있습니까?

로컬 변수를 중개자로 사용하면 가비지 수집기에 영향을 미치지 않습니다. GenerateAlert () new up-up 메모리이면 문제가됩니다. 그러나 지역 변수에 관계없이 중요합니다.

.NET은이 개체 AlertID를 어떻게 처리합니까?

AlertID는 개체가 아닙니다. GenerateAlert ()의 결과는 객체입니다. AlertID는 변수입니다. 지역 변수 인 경우 단순히 물건을 추적하는 방법과 관련된 공간입니다.

.NET 외부에서는 사용 후 수동으로 객체를 폐기해야합니다.

이것은 관련 컨텍스트 및 GenerateAlert ()가 제공하는 인스턴스의 소유권 의미에 따라 더 까다로운 질문입니다. 일반적으로 인스턴스를 생성 한 모든 인스턴스를 삭제해야합니다. 수동 메모리 관리를 염두에두고 설계된 프로그램은 크게 다르게 보일 수 있습니다.

가비지 수집을 중계하지 않는 지식이 풍부한 프로그래머가되고 싶습니다. 나는 이것을 생각하고 있는가? 내가 잘못된 것에 집중하고 있습니까?

훌륭한 프로그래머는 가비지 수집기를 포함하여 사용 가능한 도구를 사용합니다. 망각 적으로 사는 것보다 물건을 지나치게 생각하는 것이 좋습니다. 당신은 잘못된 것에 집중하고 있을지도 모르지만, 우리가 여기 있기 때문에 그것에 대해 배울 수도 있습니다.


2

작동하고 깨끗하게 만들고 SOLID로 만들고 작동해야하는 속도만큼 빠르게 작동 합니다.

그것은 정상적인 순서입니다. 최우선 과제는 요구 사항을 벗어나는 합격 테스트를 통과 할 수있는 무언가를 만드는 것입니다. 이것이 고객의 최우선 순위이기 때문에 이것이 최우선입니다. 개발 기한 내에 기능 요구 사항을 충족합니다. 다음 우선 순위는 이해하기 쉽고 깨끗하고 읽을 수있는 코드를 작성하는 것입니다. 이해하기 쉽고 나중에 필요할 때 WTF없이 후손에 의해 유지 될 수 있습니다 ( 'if'에 대한 질문은 거의 없습니다. 다시 돌아가서 변경 / 수정). 세 번째 우선 순위는 코드가 SOLID 방법론 (또는 원하는 경우 GRASP)을 준수하도록하는 것입니다.이 방식은 코드를 모듈 식의 재사용 가능하고 교체 가능한 덩어리로 만들어 유지 보수를 다시 지원합니다 (사용자가 수행 한 작업과 이유를 이해할 수있을뿐만 아니라, 그러나 코드를 외과 적으로 제거하고 바꿀 수있는 깨끗한 줄이 있습니다. 최우선 과제는 성능입니다. 코드가 성능 사양을 준수하기에 충분히 중요한 경우 먼저 정확하고 깨끗하며 SOLID를 만들기에 충분할 것입니다.

크리스토퍼 (도널드 크 누스)에게 "초기 최적화는 모든 악의 근원"이라고합니다. 또한 고려중인 최적화 종류는 사소한 것입니다 (소스 코드에서 이름을 제공하는지 여부에 관계없이 스택에서 새 객체에 대한 참조가 생성됩니다) 및 컴파일 된 차이를 유발하지 않는 유형 IL. 변수 이름은 IL로 전달되지 않으므로 첫 번째 (그리고 아마도 유일한) 사용 직전에 변수를 선언하기 때문에 IL이 두 예제간에 동일하다는 맥주 돈을 걸었습니다. 따라서 동료는 100 % 옳습니다. 명명 된 변수 대 인라인 인스턴스화를 최적화하려는 경우 잘못된 위치를 찾고 있습니다.

.NET의 마이크로 최적화는 그다지 가치가 없습니다 (약 99.99 %의 사례). C / C ++에서 아마도 당신이하고있는 일을 알고 있다면. .NET 환경에서 작업 할 때는 이미 코드 실행에 상당한 오버 헤드가 발생하는 하드웨어와는 거리가 멀습니다. 따라서 .NET 환경에서 무언가가 충분히 빨리 작동하지 않으면 블리 스터링 속도를 포기하고 대신 "올바른"코드를 작성하려고하는 환경에 이미 있다는 점을 감안할 때 복잡성도 너무 높거나 병렬화를 고려해야합니다. 최적화를 위해 따라야 할 몇 가지 기본 사항은 다음과 같습니다. 나는 당신의 최적화 생산성 (시간이 빨라진 속도)이 급등 할 것이라고 보장합니다 :

  • 함수 형태를 바꾸는 것은 계수를 바꾸는 것보다 중요합니다 -WRT Big-Oh 복잡성, N 2 알고리즘 에서 실행해야하는 단계 수를 절반으로 줄일 수 있으며, 2 차 복잡도 알고리즘은 예전의 절반. 이것이 이러한 유형의 문제에 대한 복잡성의 하한 인 경우에도 마찬가지입니다. 그러나 동일한 문제에 대한 NlogN, 선형 또는 로그 솔루션이있는 경우 알고리즘을 전환하여 복잡성을 줄이면서 알고리즘을 전환하면 더 많은 이점을 얻을 수 있습니다.
  • 복잡성을 볼 수 없다고해서 비용이 들지 않는다는 의미는 아닙니다 . 단어에서 가장 우아한 단일 라이너 중 상당수가 끔찍하게 수행됩니다 (예를 들어, Regex 소수 검사기 는 효율적이고 지수 복잡성입니다) 숫자를 제곱근보다 작은 모든 소수로 나누는 소수 평가는 O (Nlog (sqrt (N))) 순서이며 Linq는 코드를 단순화하지만 SQL 엔진과 달리 .Net과 같은 훌륭한 라이브러리입니다. 컴파일러는 쿼리를 실행하는 가장 효율적인 방법을 찾으려고하지 않습니다. 메서드를 사용할 때 어떤 일이 발생할지 알아야하며, 따라서 생성하는 동안 체인에서 더 일찍 (또는 나중에) 배치하면 메소드가 더 빠를 수있는 이유를 알아야합니다. 같은 결과.
  • OTOH, 소스 복잡성과 런타임 복잡성 사이에는 거의 항상 상충 관계가 있습니다 . SelectionSort는 구현하기가 매우 쉽습니다. 아마도 10LOC 이하로 할 수 있습니다. MergeSort는 조금 더 복잡하고 Quicksort는 더 복잡하며 RadixSort는 훨씬 더 복잡합니다. 그러나, 알고리즘이 코딩 복잡성 (따라서 "최초"개발 시간)이 증가함에 따라 런타임 복잡성이 감소합니다. MergeSort 및 QuickSort는 NlogN이며 RadixSort는 일반적으로 선형으로 간주됩니다 (기술적으로 NlogM이며 여기서 M은 N에서 가장 큰 숫자 임).
  • 빠르게 돌파 -저렴하게 수행 할 수있는 확인이 있고 실제로 진행될 수 있다는 의미 인 경우 먼저 확인하십시오. 예를 들어, 알고리즘이 1, 2 또는 3으로 끝나는 숫자 만 신경 쓰는 경우 가장 가능성이 높은 경우 (완전히 임의의 데이터가 주어짐)는 다른 숫자로 끝나는 숫자이므로 숫자가 끝나지 않는지 테스트하십시오. 숫자가 1, 2 또는 3으로 끝나는 지 확인하기 전에 1, 2 또는 3입니다. 논리 조각에 A & B가 필요하고 P (A) = 0.9, P (B) = 0.1이면 B 먼저! A가 다음! B (같은 경우를 제외하고 if(myObject != null && myObject.someProperty == 1)), 또는 B는 평가보다 9 배 이상보다 오래 걸리는 ( if(myObject != null && some10SecondMethodReturningBool())).
  • 이미 답을 알고 있다는 질문을하지 마십시오 . 일련의 "경과"조건이 있고 이러한 조건 중 하나 이상이 점검해야하는 간단한 조건에 의존하는 경우 두 가지 모두를 확인하지 마십시오. 이들은 독립적으로. 예를 들어, A가 필요한 수표와 A && B가 필요한 수표가있는 경우 A를 확인해야하며, true이면 B를 확인해야합니다.! A,! A && B이면 귀찮게하지 마십시오.
  • 더 많은 일을하면할수록 더 많은주의를 기울여야합니다. 이것은 여러 수준에서 개발의 일반적인 주제입니다. 일반적인 개발 의미에서 "일반적인 작업이 시간이 많이 걸리거나 어리석은 일이라면 더 나은 방법을 찾을 수있을만큼 좌절하고 지식이 풍부해질 때까지 계속하십시오". 코드 측면에서 비효율적 인 알고리즘을 더 많이 실행할수록 알고리즘을 최적화하여 전반적인 성능을 더 많이 얻을 수 있습니다. 이진 어셈블리와 해당 디버그 기호를 사용할 수있는 프로파일 링 도구가 있으며, 일부 사용 사례를 실행 한 후 어떤 코드 행이 가장 많이 실행되었는지를 보여줍니다. 이러한 라인과 해당 라인을 실행하는 라인은 효율성을 높이면 증가 할 수 있으므로 가장주의를 기울여야합니다.
  • 더 복잡한 알고리즘은 하드웨어를 충분히 던지면 덜 복잡한 알고리즘처럼 보입니다 . 알고리즘이 실행중인 시스템 (또는 그 일부)의 기술적 한계에 접근하고 있다는 사실을 알아야 할 때가 있습니다. 이 시점에서 더 빨라야하는 경우 더 나은 하드웨어에서 간단히 실행하여 더 많은 것을 얻을 수 있습니다. 이것은 병렬화에도 적용됩니다. N 코어에서 실행될 때 N 2 복잡성 알고리즘은 선형으로 보입니다. 따라서 작성중인 알고리즘의 유형에 대한 복잡성이 줄어든다면 "분할 및 정복"방법을 찾으십시오.
  • 충분히 빠르면 빠릅니다 . 특정 칩을 대상으로하는 핸드 패킹 어셈블리가 아니라면 항상 얻을 수있는 것이 있습니다. 그러나 핸드 패킹 어셈블리를 원치 않는 한 클라이언트가 "충분히 양호"하다고 부르는 것을 항상 명심해야합니다. "조기 최적화는 모든 악의 근원"입니다. 고객이 충분히 빨리 전화하면 더 이상 충분히 빠르지 않을 때까지 완료됩니다.

0

초기 최적화에 대해 걱정할 수있는 유일한 시간은 거대하거나 아는 것이 많은 횟수를 처리한다는 것을 알고있을 때입니다.

"거대한"의 정의는 대상 시스템이 어떤지에 따라 분명히 달라집니다.


0

디버거를 사용하는 것이 더 쉽기 때문에 두 줄 버전을 선호합니다. 몇 개의 내장 된 통화가있는 회선은 더 어려워집니다.

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