LabVIEW는 텍스트 기반 프로그래밍 언어에서 다루어야만 하는 많은 세부사항을 처리해 줍니다. 텍스트 기반의 언어의 주요한 어려운 점 중 하나가 메모리 사용입니다. 텍스트를 기반으로한 언어에서는 프로그래머가 메모리를 사용하기 전에 할당하고 그리고 끝마치면 할당을 해제하는 것을 관리해야 합니다. 또한 처음에 할당한 메모리의 한계를 넘어서 쓰지 않도록 주의을 기울여야 합니다. 메모리를 할당하지 못하거나 충분한 메모리를 할당하지 못하는 것이 프로그래머가 텍스트 기반의 언어에서 저지르는 가장 큰 실수 중의 하나입니다. 또한 부적절한 메모리 할당은 디버그하기 어려운 문제입니다.
LabVIEW의 데이터 흐름 패러다임은 메모리 관리의 어려움을 상당 부분 제거합니다. LabVIEW에서는 변수를 할당하지도 않고, 변수에 값을 지정하거나 변수로부터 값을 받지도 않습니다. 대신, 데이터의 전달을 보여주는 연결이 있는 블록다이어그램을 생성합니다.
데이터를 생성하는 함수가 그 데이터의 스토리지 할당을 관리합니다. 데이터가 더 이상 사용되지 않을 때, 관련된 메모리는 할당을 해제합니다. 새로운 정보를 배열이나 문자열에 추가할 때, 충분한 메모리가 자동적으로 할당되어 새로운 정보를 관리합니다.
이 자동 메모리 핸들링은 LabVIEW의 주요한 장점 중의 하나입니다. 그러나 자동이기 때문에, 사용자는 언제 발생하는지 제어하기 어렵습니다. 프로그램이 큰 데이터 세트를 가지고 작동할 때, 언제 메모리 할당이 발생할지를 이해하는 것은 중요합니다. 관련된 원리를 이해하면 약간 더 작은 메모리를 요구하는 프로그램도 만들 수 있습니다. 또한, 메모리 할당과 데이터를 복사하는 일은 상당한 시간이 걸릴 수 있기 때문에, 메모리 사용을 최소화하는 방법을 이해하면 VI 실행 속도를 증가시킬 수 있습니다.
가상 메모리
OS는 버추얼 메모리를 사용하여 어플리케이션이 물리적인 RAM 용량 이상의 메모리에 접근할 수 있도록 합니다. OS는 물리적 RAM을 페이지라고 불리는 블록으로 나눕니다. 어플리케이션이나 프로세스가 주소를 메모리 블록에 할당하면 주소는 물리적 RAM의 직접적인 위치를 참조하지 않고 페이지의 메모리를 참조합니다. OS는 이러한 페이지를 물리적 RAM과 하드 디스크 사이에서 스왑할 수 있습니다.
어플리케이션이나 프로세스가 물리적 RAM에 없는 특정 블록이나 메모리 페이지에 접근해야 하는 경우, OS는 어플리케이션이나 프로세스가 사용하지 않는 물리적 RAM의 페이지를 하드 디스크로 옮기고 그 자리를 필요한 페이지로 채웁니다. OS는 메모리에 있는 페이지를 계속 추적하며 어플리케이션이나 프로세스가 특정한 메모리에 접근해야 할 때 페이지의 버추얼 주소를 물리적 RAM의 실제 주소로 해석합니다.
다음 이미지는 두 개의 프로세스가 어떻게 물리적 RAM의 안팎으로 페이지를 교환하는지 보여줍니다. 이 예제에서 프로세스 A와 프로세스 B는 동시에 실행됩니다.
1 프로세스 A | 3 프로세스 B | 5 프로세스 B에서 메모리의 페이지 |
2 물리적 RAM | 4 버추얼 메모리의 페이지 | 6 프로세스 A에서 메모리의 페이지 |
어플리케이션이나 프로세스가 사용하는 페이지의 개수는 사용 가능한 물리적 RAM이 아니라 사용 가능한 디스크 공간에 따라 달라지기 때문에 어플리케이션은 물리적 RAM의 실제 메모리보다 더 많은 메모리를 사용할 수 있습니다. 어플리케이션이 사용하는 메모리 주소의 크기가 어플리케이션이 접근할 수 있는 버추얼 메모리의 양을 제한합니다.
(Windows) LabVIEW 버추얼 메모리 사용
LabVIEW (64비트)는 Windows 버전에 따라 최대 8 TB 또는 128 TB까지 버추얼 메모리를 사용할 수 있습니다. 또한 LabVIEW (64비트)가 사용할 수 있는 버추얼 메모리의 실제 크기는 물리적 RAM의 크기와 최대 페이징 파일 크기에 따라 달라집니다.
VI 구성요소 메모리 관리
- 프런트패널
- 블록다이어그램
- 코드(기계 코드로 컴파일되는 다이어그램)
- 데이터(컨트롤과 인디케이터 값, 기본 데이터, 다이어그램 상수 데이터 등)
VI를 로드할 때, 그 VI의 프런트패널, 코드(플랫폼에 일치한다면), 데이터가 메모리에 로드됩니다. VI가 플랫폼에 있어서 변경이나 SubVI의 인터페이스에서 변경 때문에 리컴파일할 필요가 있다면, 블록다이어그램 또한 메모리에 로드됩니다.
또한 VI는 메모리에 그 SubVI의 코드와 데이터 공간도 로드합니다. 특정 상황에서, 일부 SubVI의 프런트패널도 메모리에 로드될 수 있습니다. 예를 들면, 프로퍼티 노드는 프런트패널 컨트롤의 상태 정보를 다루기 때문에, 이는 SubVI가 프로퍼티 노드를 사용하는 경우 발생할 수 있습니다.
VI 일반적으로 VI의 한 섹션을 SubVI로 변경할 때, 그다지 많은 메모리를 사용하지 않는다는 것입니다. SubVI가 없는 거대한 단일 VI를 생성할 때, 메모리에 그 상위 레벨 VI를 위한 프런트패널, 코드, 데이터를 가지게 됩니다. 그러나, VI를 여러 SubVI로 나누면, 상위 레벨 VI를 위한 코드는 더 작게 되고 SubVI의 코드와 데이터는 메모리에 있게 됩니다. 일부 경우에, 더 낮은 런타임 메모리 사용을 볼 수 있습니다.
큰 VI는 편집에 걸리는 시간도 더 깁니다. 다음의 방법으로 이 문제를 피할 수 있습니다:
- (권장) VI를 여러 SubVI로 나눕니다. 편집기는 작은 VI를 더 효율적으로 다룰 수 있을 뿐 아니라, 일반적으로 계층구조에 계층이 많을 수록 VI 조직이 유지하고 읽기 쉽습니다.
- 큰 VI의 컴파일된 코드 복잡도에 근거하여 LabVIEW가 실행 시간보다 편집기 응답을 우선하도록 설정합니다.
노트 주어진 VI의 프런트패널이나 블록다이어그램이 화면보다 훨씬 더 크면, 더 쉽게 접근할 수 있도록 VI를 여러 SubVI로 나눌 수 있습니다. |
데이터 흐름 프로그래밍과 데이터 버퍼
LabVIEW는 inplaceness를 사용하여 언제 메모리를 다시 사용할지, 또한 각 출력 터미널의 복사본을 만들지를 결정합니다. LabVIEW가 데이터를 입력에서 출력으로 복사하지 않을 때, 해당 입력과 출력의 데이터는 in-place라고 할 수 있습니다.
버퍼 할당 보이기 윈도우를 사용하여 LabVIEW가 어디에 데이터의 복사본을 생성할 수 있는지 알려줍니다.
예를 들면, 컴파일러에 대한 더 전통적인 접근 방식으로, 다음 블록다이어그램은 두 개의 데이터 메모리 블록을 사용하는데, 하나는 입력으로 또 다른 하나는 출력으로 사용합니다.
입력 배열과 출력 배열은 같은 개수의 원소를 가지고 있고, 이 두 배열의 데이터 타입은 같습니다. 입력 배열을 데이터의 버퍼로서 생각합니다. 출력에 대해 새로운 버퍼를 생성하는 대신에, 컴파일러는 입력 버퍼의 메모리를 출력 버퍼에 다시 사용합니다. 이와 같이 inplaceness를 이용하면 실행 시 메모리를 할당할 필요가 없기 때문에 메모리가 절약되고 더 빠른 실행이 가능합니다.
그러나 컴파일러는 다음 블록다이어그램에서 보는 것처럼 모든 경우에 메모리 버퍼를 다시 사용할 수는 없습니다.
신호는 단일 데이터 소스를 여러 대상으로 전달합니다. [배열 부분 대체] 함수는 입력 배열을 변경하여 출력 배열을 생성합니다. 이 경우, 이 함수 중 한 개의 데이터는 in-place이며, 이 때 출력은 다른 출력과는 달리 입력 배열의 데이터를 다시 사용합니다. 이렇게 하여, 컴파일러는 두 개의 함수에 대해 새로운 데이터 버퍼를 생성하고 버퍼에 배열 데이터를 복사합니다. 이 블록다이어그램은 약 12 KB(원래 배열에 4KB 그리고 2개의 여분의 데이터 버퍼 각각에 4KB)를 사용합니다.
이제 다음의 블록다이어그램을 살펴봅니다.
이전처럼, 입력은 세 개의 함수로 갈라집니다. 그러나, 이 경우에 [배열 인덱스] 함수는 입력 배열을 변경하지 않습니다. 데이터를 여러 위치로 전달하고 그 위치에서 데이터를 변경하지 않고 읽으면, LabVIEW는 데이터의 복사본을 만들지 않습니다. 이 결과, 모든 데이터가 in-place입니다. 이 블록다이어그램은 약 4 KB의 메모리를 사용합니다.
마지막으로, 다음의 블록다이어그램을 살펴봅니다.
이 경우에, 입력은 두 개의 함수로 나뉘는데, 그 중 하나는 데이터를 변경합니다. 그 두 개의 함수 사이에 의존성은 없습니다. 그러므로, 적어도 하나의 복사본은 만들어 둘 필요가 있다는 것을 예상할 수 있으므로 [배열 부분 대체] 함수가 안전하게 데이터를 변경할 수 있습니다. 그러나 이 경우에, 컴파일러는 데이터를 읽는 함수가 먼저 실행되고 데이터를 변경하는 함수가 마지막으로 실행되는 방식으로 함수의 실행 일정을 잡습니다. 이러한 방식으로 [배열 부분 대체] 함수는 배열 복사를 생성하지 않고, 입력 배열 버퍼를 다시 사용하여 이 함수의 데이터를 in-place로 만듭니다. 노드의 순서는 중요하기 때문에, 다른 노드의 입력을 위해 한 노드의 순서나 출력을 사용하여 순서를 명시해 놓습니다.
실제로, 컴파일러에 의한 블록다이어그램의 분석은 완전하지 않습니다. 몇가지 경우에, 컴파일러는 블록다이어그램 메모리를 다시 사용하기 위한 최적의 방법을 결정할 수 없을 수 있습니다.
조건적 인디케이터와 데이터 버퍼
사용자가 블록다이어그램을 만드는 방법은 LabVIEW가 데이터 버퍼를 다시 사용하는 것을 막을 수 있습니다. SubVI에서 조건적 인디케이터를 사용하면 LabVIEW가 데이터 버퍼 사용을 최적화하는 것을 막습니다. 조건적 인디케이터는 케이스 구조나 For 루프 안의 인디케이터입니다. 조건적으로 실행되는 코드 경로에 인디케이터를 놓으면 시스템을 통한 데이터의 흐름을 깨고 LabVIEW는 입력으로부터 데이터 버퍼를 다시 사용할 수 없을 것입니다. 그러나 그 대신 데이터를 인디케이터에 강제로 복사해 놓도록 합니다. 케이스 구조와 For 루프의 밖에 인디케이터를 놓을 때, LabVIEW는 루프나 구조 안에서 데이터를 직접 변경하고, 데이터 복사본을 생성하는 대신에 인디케이터에 데이터를 전달합니다. 케이스 구조 안에 인디케이터를 놓는 대신에 다른 경우에 대해 상수를 생성할 수 있습니다.
메모리 사용 모니터하기
현재 VI에서 메모리 사용을 보려면, 파일≫VI 프로퍼티를 선택하고 최상위 풀다운 메뉴에서 메모리 사용을 선택합니다. 이때, SubVI의 메모리 사용에 대한 정보는 포함하지 않습니다. 성능과 메모리 프로파일 윈도우를 사용하여 메모리에 있는 모든 VI가 사용하는 메모리를 모니터할 수 있고, 성능에 문제가 있는 SubVI를 파악할 수 있습니다. VI 성능과 메모리 프로파일 윈도우는 매번 실행에서 각 VI가 사용하는 바이트와 블록의 최소, 최대, 평균의 통계값을 나타냅니다.
노트 정확한 VI 런타임 성능을 모니터하려면 컴파일된 코드를 분리하지 않고 모든 VI를 LabVIEW의 현재 버전에 저장해야 하며, 그 이유는 다음과 같습니다:
|
버퍼 할당 보이기 윈도우를 사용하여 블록다이어그램에서 LabVIEW가 버퍼의 형태로 메모리 공간을 할당하는 영역을 파악할 수 있습니다.
노트 버퍼 할당 보이기 윈도우는 LabVIEW Full과 Professional Development Systems에서만 제공됩니다. |
도구≫프로파일≫버퍼 할당 보이기를 선택하여 버퍼 할당 보이기 윈도우를 디스플레이합니다. 버퍼를 확인할 데이터 타입의 옆에 확인 표시를 하고 새로 고침 버튼을 클릭하십시오. 블록다이어그램에 나타나는 검은 사각형은 LabVIEW가 데이터 공간 할당을 위해 버퍼를 생성하는 위치를 나타냅니다.
LabVIEW가 각 버퍼에 할당하는 메모리의 크기는 가장 큰 데이터 타입의 크기와 같습니다. VI를 실행할 때, LabVIEW는 생성된 버퍼를 사용하여 데이터를 저장할 수도 있으나, 항상 그렇지는 않습니다. LabVIEW가 데이터의 복사본을 만들지는 런 타임 때 결정 지어지고, 때로는 VI가 다이내믹 데이터에 의존하기 때문에 복사본을 만들지 미리 알 수는 없습니다.
VI가 버퍼 할당에 메모리를 필요로하면, LabVIEW는 해당 버퍼의 데이터 복사본을 만듭니다. 버퍼가 데이터의 복사본을 필요로 하는지 확실치 않을 때에도, LabVIEW는 버퍼의 복사본을 만들기도 합니다.
노트 버퍼 할당 보이기 윈도우는 블록다이어그램 상에서 검정색 사각형을 사용, 실행 버퍼만을 식별합니다. 이 윈도우를 사용하여 프런트패널에서 동작 버퍼를 식별할 수는 없습니다. |
LabVIEW의 버퍼 생성 위치를 알면, LabVIEW가 보다 적은 양의 메모리로 VI를 실행할 수 있도록 VI를 편집할 수 있습니다. LabVIEW는 VI를 실행하기 위해 메모리를 할당해야 하므로 모든 버퍼를 지울 수는 없습니다.
LabVIEW가 VI를 다시 컴파일할 만큼 VI를 변경하게 되면, 버퍼 정보가 더 이상 정확하지 않으므로 검정색 사각형은 사라집니다. VI를 다시 컴파일하고 검정색 사각형을 디스플레이하기 위해서 버퍼 할당 보이기 윈도우의 새로 고침 버튼을 클릭하십시오. 버퍼 할당 보이기 윈도우를 닫게 되면 검정색 사각형은 사라집니다.
노트 또한 LabVIEW Desktop Execution Trace Toolkit 을 이용하여 스레드 사용과 메모리 손실, 기타 LabVIEW 프로그래밍의 영역을 모니터할 수 있습니다. |
도움말≫LabVIEW에 대하여를 선택하여 어플리케이션에 의해 사용되는 총 메모리의 양을 요약하는 통계값을 보여줍니다. 이것은 VI를 위한 메모리와 어플리케이션이 사용하는 메모리를 포함합니다. VI 세트의 실행 전과 후에 이 메모리 양을 검사하여 VI가 얼마나 많은 메모리를 사용하게 되는가를 대략적으로 알 수 있습니다.
더 효과적인 메모리 사용을 위한 규칙
- VI를 SubVI로 나누어도 일반적으로 메모리 사용에 나쁜 영향을 주지는 않습니다. 많은 경우에, SubVI가 실행되지 않을 때 실행 시스템이 SubVI 데이터 메모리를 다시 요청할 수 있기 때문에 메모리 사용은 향상됩니다.
- 스칼라 값의 복사본에 대해 너무 염려하지 마십시오. 메모리 사용에 부정적인 영향을 미칠 정도가 되려면 스칼라가 엄청나게 많아야 합니다.
- 배열이나 문자열을 가지고 작업할 때 글로벌과 로컬 변수를 지나치게 사용하지 않습니다. 글로벌이나 로컬 변수를 읽으면 LabVIEW는 데이터를 복사하는 일이 발생합니다.
- 필요하지 않다면 열려 있는 프런트패널에 큰 배열과 문자열을 디스플레이하지 않습니다. 열려 있는 프런트패널의 컨트롤과 인디케이터는 디스플레이하는 데이터를 복사해서 가지고 있습니다.
팁 차트 인디케이터가 필요한 경우, 차트 히스토리가 디스플레이되는 데이터의 복사본을 보유하게 된다는 점을 염두에 두십시오. 차트 히스토리가 채워질 때까지 지속되는데, 이후 LabVIEW는 더이상의 메모리를 차지하지 않게 됩니다. LabVIEW는 VI가 다시 시작될때 자동으로 차트 히스토리를 지우지 않고, 프로그램이 실행되는 과정에서 차트 히스토리가 지워질 수 있도록 합니다. 이를 위해, 차트에 대한 히스토리 데이터 속성 노드에 빈 배열을 작성하십시오. |
- 패널 업데이트 연기 프로퍼티를 사용합니다. 이 프로퍼티를 참으로 설정할 때, 컨트롤 값을 변경할지라도 프런트패널 인디케이터 값은 바뀌지 않습니다. 인디케이터를 새 값으로 채우기 위해 OS는 메모리를 사용할 필요는 없습니다.
노트 일반적으로 LabVIEW는 SubVI를 호출할 때, SubVI의 프런트패널을 열지 않습니다. |
- SubVI의 프런트패널이 디스플레이되지 않는다면, SubVI에 사용되지 않은 프로퍼티 노드를 남겨놓지 마십시오. 프로퍼티 노드는 SubVI의 프런트패널이 메모리에 남아있도록 하여 불필요한 메모리 사용을 만들 수 있습니다.
- 블록다이어그램을 만들 때, 입력의 크기가 출력의 크기와 다른 위치를 확인합니다. 예를 들면, [배열 만들기] 또는 [문자열 연결] 함수를 사용하여 자주 배열이나 문자열의 크기를 증가시키는 위치에서는 데이터의 복사본을 생성하는 것입니다.
- 데이터를 SubVI와 함수로 전달할 때 배열에 대해 일관된 데이터 타입을 사용하고 강제 변환점을 찾습니다. 데이터 타입을 변경할 때, 실행 시스템은 데이터의 복사본을 만듭니다.
- 큰 배열이나 문자열을 가진 클러스터나 클러스터의 배열과 같은 복잡하고 계층구조적인 데이터 타입을 사용하지 마십시오. 더 많은 메모리를 사용하게 될 것입니다. 가능하면, 더 효과적인 데이터 타입을 사용합니다.
- 필요하지 않는 한 투명하고 겹치는 프런트패널 객체를 사용하지 않습니다. 그런 객체는 더 많은 메모리를 사용할 수 있습니다.
프런트패널에서 메모리 문제
다음 블록다이어그램은 프런트패널 컨트롤과 인디케이터가 추가된 증가 함수를 보여줍니다.
VI를 실행할 때, 프런트패널 컨트롤의 데이터가 블록다이어그램에 전달됩니다. 증가 함수가 입력 버퍼를 다시 사용합니다. 그리고 나서 인디케이터는 디스플레이할 목적으로 데이터의 복사본을 만듭니다. 이렇게 해서 버퍼의 3개의 복사본이 생깁니다.
프런트패널 컨트롤의 데이터 보호는 이어지는 노드에 데이터가 전달될 때 데이터를 컨트롤에 입력하는 것, 관련된 VI를 실행하는 것, 컨트롤에서 데이터 변경을 보는 것을 막습니다. 마찬가지로, 인디케이터의 경우에도 데이터는 보호되므로 인디케이터가 새로운 데이터를 받을 때까지 이전 내용을 디스플레이할 수 있습니다.
SubVI와 함께, 입력과 출력으로서 컨트롤과 인디케이터를 사용할 수 있습니다. 다음 조건에 따라 실행 시스템은 SubVI의 컨트롤과 인디케이터 데이터의 복사본을 만듭니다:
- 프런트패널이 메모리에 있습니다. 이것은 다음의 이유 때문에 발생할 수 있습니다:
- 프런트패널이 열려 있습니다.
- VI가 변경되었지만 저장되지 않았습니다(VI의 모든 구성요소가 VI가 저장될 때까지 메모리에 있습니다).
- 패널이 데이터 인쇄를 사용합니다.
- 블록다이어그램이 프로퍼티 노드를 사용합니다.
- VI가 로컬 변수를 사용합니다.
- 패널이 데이터 로깅을 사용합니다.
프로퍼티 노드가 닫힌 패널을 가진 SubVI에서 차트 히스토리를 읽을 수 있도록 하기 위해서, 컨트롤이나 인디케이터는 전달되는 데이터를 디스플레이해야 합니다. 이와 같이 다른 많은 속성들이 있기 때문에, SubVI가 프로퍼티 노드를 사용한다면 실행 시스템은 SubVI 패널을 메모리에 두어야 합니다.
프런트패널이 프런트패널 데이터 로깅이나 데이터 데이터 인쇄를 사용한다면, 컨트롤과 인디케이터가 그 데이터의 복사본을 유지합니다. 또한, 프런트패널은 데이터 인쇄를 위해 메모리에 남아있기 때문에 패널을 인쇄할 수 있습니다.
VI 프로퍼티 대화 상자나 SubVI 노드 설정 대화 상자를 사용하여 SubVI를 호출할 때 그 프런트패널을 디스플레이하도록 설정하면, 그 SubVI를 호출할 때 프런트패널이 메모리에 로드됩니다. 원래 닫혀있으면 실행 후 닫기 아이템을 설정한다면, LabVIEW는 SubVI가 실행을 끝낼 때 메모리에서 그 프런트패널을 제거합니다.
데이터 메모리를 다시 사용할 수 있는 SubVI
언제 메모리의 할당이 해제되는지 이해하기
평균 VI를 실행한 후에 데이터의 배열은 더 이상 필요 없습니다. 데이터가 더 이상 필요 없는 때를 결정하는 것은 더 큰 블록다이어그램에서 매우 복잡할 수 있기 때문에, 실행되는 동안 특정한 VI의 데이터 버퍼 할당을 해제하지 않습니다.
macOS에서 실행 시스템이 작은 메모리를 차지하면, 현재 실행되지 않는 모든 VI에 의해 사용되는 데이터 버퍼의 할당을 해제합니다. 실행 시스템은 프런트패널 컨트롤, 인디케이터, 글로벌 변수, 초기화되지 않은 시프트 레지스터에 의해 사용된 메모리의 할당을 해제하지 않습니다.
이제 같은 VI를 더 큰 VI의 SubVI로 생각합니다. 데이터의 배열은 SubVI에서만 생성되고 사용됩니다. macOS에서, SubVI를 실행하지 않고 시스템이 낮은 메모리를 차지하면, SubVI에서 데이터의 할당을 해제할 수 있습니다. 이러한 경우에 SubVI를 사용하여 메모리 사용을 절약할 수 있습니다.
Windows와 Linux 플랫폼에서, VI가 닫히고 메모리로부터 제거되지 않으면 데이터 버퍼는 정상적으로 할당을 해제하지 않습니다. 메모리는 필요할 때마다 OS로부터 할당받고 가상 메모리가 이 플랫폼에서 잘 작동합니다. 조각난 메모리 때문에, 어플리케이션은 실제 사용하는 것보다 더 많은 메모리를 사용하는 것으로 나타날 수 있습니다. 메모리를 할당받고 비워가면서, 어플리케이션은 메모리 사용을 통합하여 사용되지 않은 블록을 OS에 반환할 수 있습니다.
[할당 해제 요청] 함수를 사용하여 이 함수를 포함하는 VI가 실행된 후에 사용하지 않는 메모리 할당을 해제할 수 있습니다. 이 함수는 고급 성능 최적화에만 사용됩니다. 일부 경우, 사용되지 않은 메모리의 할당을 해제하면 성능이 향상됩니다. 그러나 지나치게 메모리의 할당을 해제하면 LabVIEW가 해당 메모리를 재사용하기보다는 반복해서 메모리 공간을 재할당하게 됩니다. 사용자 VI가 많은 양의 데이터를 할당하지만 이러한 할당 분을 재사용하지 않을 때 이 함수를 사용하십시오. 최상위 VI가 SubVI를 호출할 때, LabVIEW는 해당 SubVI가 실행되는 메모리의 데이터 공간을 할당합니다.
SubVI가 실행을 완료해도, LabVIEW는 최상위 VI가 실행을 마치거나 전체 어플리케이션이 종료될 때까지 데이터 공간 할당을 해제하지 않기 때문에 메모리 부족 상태가 되거나 성능이 저하될 수 있습니다. [할당 해제 요청] 함수를 메모리 할당 해제하려는 SubVI에 위치시키십시오. 플래그 불리언 입력을 참으로 설정하면, LabVIEW는 subVI의 데이터 공간 할당을 해제하여 메모리 사용을 감소시킵니다.
출력이 입력 버퍼를 언제 다시 사용할 수 있는지 결정하기
출력이 입력과 같은 크기와 데이터 타입을 가지고 다른 곳에서 입력이 요청이 없으면, 출력은 입력 버퍼를 다시 사용할 수 있습니다. 이전에 언급했던 것처럼, 몇몇 경우에는 입력이 다른 곳에서 사용될 때에도 컴파일러와 실행 시스템이 출력 버퍼를 위해 입력을 다시 사용할 수 있는 방식으로 코드 실행을 명령할 수 있습니다. 그러나, 이러한 규칙은 복잡합니다. 그것에 의존하지 마십시오.
버퍼 할당 보이기 윈도우를 사용하여 출력 버퍼가 입력 버퍼를 다시 사용할 것인지 알 수 있습니다. 다음의 블록다이어그램에서, 케이스 구조의 각 경우에 인디케이터를 놓으면 데이터 흐름을 막게 되는데 그 이유는 LabVIEW가 각 인디케이터에서 데이터의 복사본을 생성하기 때문입니다. LabVIEW는 입력 배열을 위해 생성한 버퍼를 사용하지 않지만 대신에 출력 배열을 위한 데이터의 복사본을 생성합니다.
인디케이터를 케이스 구조 밖으로 이동시키면, 출력 버퍼는 입력 버퍼를 다시 사용하는데, 그 이유는 LabVIEW가 인디케이터가 디스플레이하는 데이터의 복사본을 만들 필요가 없기 때문입니다. LabVIEW는 VI에서 나중에 입력 배열의 값을 요청하지 않기 때문에, 증가 함수는 직접적으로 입력 배열을 변경하고 출력 배열에 그 값을 전달할 수 있습니다. 이 때, LabVIEW는 데이터를 복사할 필요가 없으므로 다음 블록다이어그램에서 보는 것처럼 버퍼가 출력 배열에 나타나지 않습니다.
데이터 타입 일치로 메모리 사용 최적화하기
예를 들어 32 비트 정수를 16 비트 정수에 더하는 경우, LabVIEW는 16 비트 정수를 32 비트 정수로 강제 변환시킵니다. 컴파일러는 강제 변환된 데이터에 대해 새로운 버퍼를 생성하고, LabVIEW는 [더하기] 함수에 강제 변환점을 놓습니다. 입력이 다른 모든 요구사항을 충족한다고 가정하면, 이 예에서 LabVIEW는 32비트 정수 입력을 출력 버퍼로 사용할 수 있습니다. 그러나 LabVIEW가 16 비트 정수를 강제 변환하였기 때문에, LabVIEW는 해당 입력에 할당된 메모리를 다시 사용할 수 없습니다.
메모리 사용을 최소화하기 위해, 가능한 모든 곳에서 일관된 데이터 타입을 사용합니다. 데이터 타입을 일관성있게 사용하면, 더 큰 크기의 데이터로 강제 변환해야 하는 경우를 피하게 되어 데이터의 복제본을 더 적게 생성하게 됩니다. 또한 일부 어플리케이션에서는 더 작은 데이터 타입의 사용으로 데이터 사용을 최소화시킬 수도 있습니다. 예를 들면 8 바이트 배정도 숫자 대신에 4 바이트 단정도 숫자를 사용하는 것을 고려할 수 있습니다. 그러나 SubVI에 연결하려는 데이터 타입과 SubVI가 받기를 예상하는 데이터 타입을 일치시키면 불필요한 강제 변환을 피할 수 있습니다.
특정 타입의 데이터 생성시 메모리 사용 최적화하기
다음의 예에서 다른 VI 입력의 데이터 타입과 일치시키려면 출력 데이터가 단정도 부동소수가 되어야 합니다. LabVIEW는 1000개의 임의의 값의 배열을 생성하고 그 배열을 스칼라 값에 더합니다. LabVIEW는 단정도 부동소수 스칼라 데이터 타입을 임의의 배정도 부동소수와 일치시키기 위해 강제 변환시키고, [더하기] 함수에 강제 변환점을 놓습니다.
다음의 블록다이어그램에서는 [단정도 부동소수로] 함수를 사용하여 배정도 부동소수를 변환하여 이 문제를 해결하려 합니다.
이 함수는 LabVIEW가 배열을 생성한 이후 큰 배열의 데이터 타입을 변환하기 때문에, VI가 사용하는 메모리의 양은 강제 변환점이 있는 경우와 같은 양입니다.
다음 블록다이어그램은 LabVIEW가 배열을 생성하기 전에 임의의 수를 변환하여 메모리 사용 및 실행 속도를 최적화하는 방법을 보여줍니다.
변환이 불가피한 경우 LabVIEW에서 큰 배열이 생성되기 전에 [변환] 함수를 사용하여, 배열에 할당된 큰 데이터 버퍼의 데이터 타입이 변환되는 것을 피합니다. LabVIEW에서 큰 배열이 생성되기 전에 데이터를 변환하면 VI의 메모리 사용을 최적화할 수 있습니다.
계속해서 데이터 크기가 바뀌는 것을 피하기
출력 크기가 입력 크기와 다르면, 출력은 입력 데이터 버퍼를 다시 사용하지 않습니다. 이것이 배열이나 문자열의 크기를 증가시키거나 감소시키는 [배열 만들기], [문자열 연결], [배열 잘라내기]와 같은 함수의 경우입니다. 배열과 문자열을 가지고 작동할 때, 이 함수를 계속 사용하는 것은 피해야 합니다. 왜냐하면 이들은 데이터를 계속해서 복사하기 때문에 사용자 프로그램이 더 많은 데이터 메모리를 사용하고 더 느리게 실행되기 때문입니다.
예제 1: 배열 만들기
노트 배열, 클러스터, 웨이브폼, 배리언트를 조작할 때, In Place 원소 구조를 사용하여 VI에서의 메모리 사용을 향상시킬 수 있습니다. |
루프의 매 반복마다 배열에 값을 추가하려면, 루프 경계선의 오토인덱싱을 사용하는 것이 가장 좋은 성능을 냅니다. For 루프를 가지고 VI는 배열의 크기를 미리 결정하고(N에 연결된 값을 기본으로) 버퍼의 크기를 한번만 바꿉니다.
While 루프에서 오토인덱싱은 그리 효과적이지가 못하는데, 그 이유는 배열의 마지막 크기를 알 수 없기 때문입니다. 그러나 While 루프에서 오토인덱싱은 출력 배열 크기를 상당히 많이 증가시켜 출력 배열의 크기 조절을 피하도록 해줍니다. 루프가 끝날 때, 출력 배열의 크기는 적당한 크기로 조절됩니다. While 루프 오토인덱싱의 성능은 For 루프 오토인덱싱과 거의 일치합니다.
오토인덱싱은 루프의 매 반복마다 그 배열 결과에 값을 추가할 것이라고 가정합니다. 조건적으로 값을 배열에 추가해야 하지만 배열 크기에서 상위 리미트를 결정할 수 있는 경우, 배열을 미리 할당하고 배열을 채우기 위해 [배열 부분 대체]를 사용하는 것을 고려해 볼 수 있습니다.
배열 값을 채우기를 끝마칠 때, 배열의 크기를 올바른 크기로 조절할 수 있습니다. 배열은 한번만 생성되고 [배열 부분 대체]는 출력 버퍼를 위해 입력 버퍼를 다시 사용할 수 있습니다. 이것의 성능은 오토인덱싱을 사용하는 루프의 성능과 매우 비슷합니다. 이 기술을 사용할 때, [배열 부분 대체]는 배열의 크기를 조절하지 않기 때문에 값을 대체하는 배열이 그 결과 데이터를 가지고 있을 만큼 충분히 커야 합니다.
이 과정의 예제가 다음 블록다이어그램에 나타나 있습니다.
예제 2: 문자열을 통해 검색하기
문자열에서 정수를 일치시키려고 하는 경우, [0―9]+를 이 함수에 대한 정규식 입력으로 사용할 수 있습니다. 문자열에서 모든 정수 배열을 생성하려면, 루프를 사용하고 반환되는 오프셋 값이 –1이 될 때까지 [정규식 일치]를 반복해서 호출합니다.
다음 블록다이어그램은 문자열에서 정수의 모든 어커런스를 스캔하는 한가지 방법입니다. 빈 배열을 생성한 후 루프의 각 반복마다 남아 있는 문자열에서 숫자 패턴을 검색합니다. 패턴이 발견되면(오프셋이 –1이 아님), 이 블록다이어그램은 [배열 만들기]를 사용하여 결과 숫자 배열에 숫자를 추가합니다. 문자열에 값이 남아 있지 않으면, [정규식 일치]는 –1을 반환하고 블록다이어그램은 실행을 완료합니다.
이 블록다이어그램에서 한가지 문제점은 루프에서 [배열 만들기]를 사용하여 새 값을 이전 값에 연결한다는 것입니다. 대신에, 오토인덱싱을 사용하여 값을 루프의 경계선에 축적할 수 있습니다. 배열에서 [정규식 일치]가 일치되는 것을 찾지 못한 루프의 마지막 반복으로부터 온 여분의 원하지 않는 값을 보게 됩니다. 해결책은 [배열 잘라내기]를 사용하여 여분의 원하지 않는 값을 제거하는 것입니다. 이것이 다음 블록다이어그램에 나와 있습니다.
이 블록다이어그램의 다른 문제는 루프를 통해 매번 남아있는 문자열의 불필요한 복사본을 생성한다는 것입니다. [정규식 일치]는 어디에서 검색을 시작할 것인지 나타내는데 사용하는 입력을 가지고 있습니다. 이전 반복으로부터 오프셋을 기억한다면, 이 숫자를 사용하여 다음 반복에서 어디부터 검색을 시작하는지 나타낼 수 있습니다. 이 기술은 다음 블록다이어그램에 나와 있습니다.
효과적인 데이터 구조를 개발하기
복잡한 데이터 구조가 가지고 있는 문제는 생성하려고 접근하는 원소를 복사하지 않고 데이터 구조 안에서 원소에 접근하고 변경하기는 어렵다는 것입니다. 원소 그 자체가 배열이나 문자열인 경우와 같이 이 원소가 크다면, 이러한 여분의 복사본이 더 많은 메모리를 사용하고 메모리를 복사하는데 시간이 걸립니다.
일반적으로 스칼라 데이터 타입은 매우 효과적으로 다룰 수 있습니다. 마찬가지로, 작은 문자열과 원소가 스칼라인 배열은 효과적으로 다룰 수 있습니다. 스칼라 배열의 경우에, 다음 코드는 배열에서 값을 증가시키기 위해서 무엇을 해야하는지 보여줍니다.
노트 배열, 클러스터, 웨이브폼, 배리언트를 조작할 때, In Place 원소 구조를 사용하여 VI에서의 메모리 사용을 향상시킬 수 있습니다. 많은 LabVIEW 작업의 경우, LabVIEW가 메모리에 데이터를 복사하고 저장해야 합니다. 따라서 실행 속도가 떨어지고 메모리 사용이 늘어납니다. In Place 원소 구조는 LabVIEW가 메모리에 데이터 값을 여러번 복사하지 않고도 일반적인 LabVIEW 작업을 수행합니다. 대신 In Place 원소 구조는 같은 메모리 위치에 있는 데이터 원소에서 작업을 수행하며 해당 원소를 배열, 클러스터, 배리언트, 웨이브폼의 같은 위치에 반환합니다. LabVIEW가 데이터 원소를 메모리의 같은 위치에 반환하므로 LabVIEW 컴파일러는 메모리에 여분의 데이버 복사본을 저장할 필요가 없습니다. |
전체 배열의 여분의 복사본을 생성할 필요가 없기 때문에 이것은 꽤 효과적입니다. 또한, [배열 인덱스] 함수에 의해 생성된 원소는 효과적으로 생성되고 다루어지는 스칼라입니다.
클러스터가 스칼라만을 가지고 있다고 가정하면 배열 클러스터도 마찬가지로 해당됩니다. 다음 블록다이어그램에서, 원소를 다루는 것이 약간 더 복잡해지는데, 그 이유는 [풀기]와 [묶기]를 사용해야 하기 때문입니다. 그러나 이 클러스터는 대부분 작으므로(스칼라는 매우 작은 메모리를 사용), 클러스터 원소에 접근하고 원래 클러스터로 원소를 대체하는데 큰 오버헤드가 발생하지 않습니다.
다음 블록다이어그램은 풀기, 수행하기, 다시 묶기를 위한 효과적인 패턴을 보여줍니다. 데이터 소스에서 온 와이어는 2가지 대상인 [풀기] 함수 입력과 [묶기] 함수의 중간 터미널을 가져야 합니다. LabVIEW는 이 패턴을 인식하고 더 성능이 좋은 수행 코드를 생성할 수 있습니다.
각 클러스터가 큰 부분배열이나 문자열을 포함하는 곳에 배열 클러스터가 있다면, 클러스터에서 원소의 값을 인덱스하고 변경하는 것은 메모리와 속도 면에서 더 많은 로드가 걸릴 수 있습니다.
전체 배열에서 원소를 인덱스할 때, 그 원소의 복사본을 만듭니다. 이렇게 해서, 클러스터의 복사본과 이에 상응하는 큰 부분배열이나 문자열이 만들어집니다. 문자열과 배열의 크기가 다양하기 때문에, 복사하는 과정은 적당한 크기의 문자열이나 부분배열을 만들기 위한 메모리 할당 호출을 포함하고 문자열이나 부분배열의 데이터를 실제로 복사하는 오버헤드를 포함합니다. 이렇게 몇 번만 하려는 계획이면 그리 심각하지는 않을 수 있습니다. 그러나 어플리케이션이 이 데이터 구조에 자주 접근해야하는 경우, 메모리와 실행 오버헤드는 빠르게 쌓입니다.
그 해법은 데이터를 다른 형태로 보는 것입니다. 다음의 세 가지 사례 연구는 각 경우에 최선의 데이터 구조를 제안하는 것과 함께 세 가지 다른 어플리케이션을 제시합니다.
사례 연구 1: 복잡한 데이터 타입 피하기
배열에서 원소를 변경하려면 전체 배열 원소에 인덱스를 주어야 합니다. 이제 그 클러스터에 대해 배열을 만들기 위해 원소를 풀어야 합니다. 그 후 배열의 원소를 대체하고 클러스터에 그 결과 배열을 저장합니다. 마지막으로 결과 클러스터를 원래 배열에 저장합니다. 이의 예제가 다음 블록다이어그램에 나타나 있습니다.
풀기/인덱싱의 각 레벨은 생성되는 데이터를 복사하게 됩니다. 복사본이 반드시 생성될 필요는 없습니다. 데이터를 복사하는 것은 시간과 메모리가 소모됩니다. 해법은 데이터 구조를 가능한 한 평평하게 만드는 것입니다. 예를 들면, 이 사례 연구에서 데이터 구조를 두 개의 배열로 나눕니다. 첫번째 배열은 문자열 배열입니다. 두번째 배열은 2D 배열인데, 여기에서 각 행은 주어진 테스트의 결과입니다. 이 결과가 다음 프런트패널에 나와 있습니다.
이 데이터 구조가 주어져 있으면 다음 블록다이어그램에서 볼 수 있는 것처럼 [배열 부분 대체] 함수를 사용하여 배열 원소를 직접 대체할 수 있습니다.
사례 연구 2: 혼합된 데이터 타입의 글로벌 테이블
어플리케이션을 통해 데이터에 접근 가능하도록, 다음에 보이는 Change Channel Info VI, Remove Channel Info VI와 같은 SubVI 세트를 생성하여 테이블의 데이터에 접근하는 방법을 고려해볼 수 있습니다.
다음 섹션은 이 VI에 대한 세 가지 다른 실행을 나타냅니다.
명백한 실행
이전 섹션에서 설명된 것처럼 이 데이터 구조는 효과적으로 다루는 것이 어려운데, 그 이유는 일반적으로 데이터에 접근하기 위해 인덱싱하고 풀기하는 몇 개의 단계를 통과해야 하기 때문입니다. 또한, 데이터 구조는 몇가지 정보가 응축되어 있기 때문에 채널을 찾기 위해 [1D 배열 검색] 함수를 사용할 수 없습니다. [1D 배열 검색]을 사용하여 배열 클러스터에서 특정한 클러스터를 검색할 수 있지만, 단일 클러스터 배열에 일치하는 원소를 찾기 위해 [1D 배열 검색]을 사용할 수 없습니다.
다른 실행 1
문자열 배열이 데이터로부터 분리되기 때문에 채널을 검색하기 위해 [1D 배열 검색] 함수를 사용할 수 있습니다.
실제로 Change Channel Info VI를 사용하여 1,000 채널의 배열을 생성하려 한다면, 이 실행은 이전 버전보다 거의 2배는 빠릅니다. 이 변경은 성능에 영향을 주는 다른 오버헤드가 있기 때문에 그다지 중요하지는 않습니다.
글로벌 변수로부터 읽어 들일 때, 글로벌 변수의 데이터의 복사본이 생성됩니다. 이렇게해서 배열에 있는 데이터의 완전한 복사본이 원소에 접근할 때마다 생성됩니다. 다음의 방법은 이 오버헤드를 피하는 훨씬 더 효과적인 방법을 제시합니다.
다른 실행 2
LabVIEW 컴파일러는 시프트 레지스터에 대한 접근을 효과적으로 다룹니다. 시프트 레지스터의 값을 읽는 것이 데이터의 복사본을 반드시 생성하는 것은 아닙니다. 실제로 시프트 레지스터에 저장된 배열에 인덱스를 줄 수 있고 전체 배열의 여분의 복사본을 생성하지 않고도 그 값을 변경하고 업데이트할 수 있습니다. 시프트 레지스터의 문제점은 시프트 레지스터가 있는 VI만이 시프트 레지스터 데이터에 접근할 수 있다는 것입니다. 반면에, 시프트 레지스터는 모듈화라는 이점이 있습니다.
채널을 읽거나 변경하거나 제거하는지 지정하거나 모든 채널에 대해 데이터를 제로로 놓을 것인지 지정하는 모드 입력을 가진 단일 subVI를 만들 수 있습니다.
SubVI에 2 개의 시프트 레지스터를 가진 While 루프가 있습니다. 하나는 채널 데이터를 위한 것이고 다른 하나는 채널 이름을 위한 것입니다. 이 시프트 레지스터는 둘 다 초기화되어 있지 않습니다. 그리고 While 루프 안에 모드 입력에 연결된 케이스 구조를 놓습니다. 모드의 값에 따라 시프트 레지스터에서 데이터를 읽고 변경할 수 있습니다.
다음은 이 3 개의 다른 모드를 다루는 인터페이스를 가진 SubVI의 개략적인 그림입니다. 단지 Change Channel Info(채널 정보 변경하기) 코드만이 나타나 있습니다.
1,000개의 원소에 대해 이 실행은 이전 실행보다 2배가 빠릅니다. 그리고 원래 실행보다 4배가 더 빠릅니다.
사례 연구 3: 문자열의 정적 글로벌 테이블
이 경우에 다음의 2가지 함수를 구성하여 구현합니다. 그 두 함수는 Initialize Table From File과 Get Record From Table입니다.
테이블을 구현하는 한가지 방법은 문자열의 2D 배열을 사용하는 것입니다. 컴파일러는 메모리의 분리된 블록의 문자열 배열에 각 문자열을 저장합니다. 많은 문자열이 있으면(예를 들면, 5,000개 문자열 이상), 메모리 관리자에게 로드를 줍니다. 각 객체의 수가 증가함에 따라 이 로드는 성능에 상당한 손실을 줄 수 있습니다.
큰 테이블을 저장하는 다른 방법은 단일 문자열로서 테이블을 읽는 것입니다. 그리고 나서 문자열에 각 기록의 오프셋이 있는 분리된 배열을 만듭니다. 이것은 수천 개의 작은 메모리 블록을 만드는 대신에 하나의 큰 메모리 블록(문자열)과 더 작게 분리된 메모리 블록(오프셋의 배열)이 생기도록 조직을 변경합니다.
이 방법은 실행하기에 더 복잡할 수 있지만, 큰 테이블에서는 훨씬 더 빨라질 수 있습니다.
'프로그래밍 > 랩뷰 기술자료' 카테고리의 다른 글
NI OPC를 이용하여 랩뷰와 PLC 통신 구현 (0) | 2023.04.08 |
---|---|
측정 시스템 개요―하드웨어와 NI-DAQmx (0) | 2023.04.02 |
랩뷰에서의 멀티프로세싱과 하이퍼스레딩 (0) | 2023.03.28 |
랩뷰를 이용한 여러 CPU 병렬 실행 (0) | 2023.03.28 |
랩뷰 VI 실행 속도 향상방법 (0) | 2023.03.26 |