Range Loop는 엑셀 시트에 입력되어 있는 데이터를 VBA 코드로 읽어서 처리할 때 자주 사용하는 코딩 패턴이다. 시작 cell부터 마지막 cell까지 순차적으로 읽는 기본적인 패턴과, 순차적으로 읽지 않고 한번에 읽는 성능 개선 패턴을 아래에 소개하고 성능을 비교한다.
Test data, Test VBA Code
설명을 위해 예시 데이터를 10만개 행, 10개 열, 총 100만개 cell로 생성하고, 각 cell의 값은 (행, 열)의 좌표값으로 설정하였다.
아래 github 파일 Link에서 예시 데이터, “B2” cell을 시작점으로 10행(100 cell) 부터 10만행(100만 cell)까지 Range를 탐색하는 코딩 패턴과 실행결과를 확인할 수 있다.
'기본 패턴
Public Sub NestedLoop(aTargetRowCnt As Long)
Dim lRowOffset As Long, lColOffset As Long, oBaseRange As Range, sVal As String
Set oBaseRange = Range("B2")
lRowOffset = 0
For lRowOffset = 0 To aTargetRowCnt '읽는 행의 수(충분히 큰 수로 지정하고 Loop 내부에서 종료 조건 설정)
If Trim(oBaseRange.Offset(lRowOffset, 0).Value) = "" Then Exit For 'Loop 종료 조건
For lColOffset = 0 To 9 '읽는 열의 수
'cell 단위로 읽고 처리하는 코드 예시
'Debug.Print oBaseRange.Offset(lRowOffset, lColOffset).Value
sVal = oBaseRange.Offset(lRowOffset, lColOffset).Text
Next lColOffset
Next lRowOffset
End Sub
'기본 패턴
Public Sub NestedLoop(aTargetRowCnt As Long)
Dim lRowOffset As Long, lColOffset As Long, oBaseRange As Range, sVal As String
Set oBaseRange = Range("B2")
lRowOffset = 0
For lRowOffset = 0 To aTargetRowCnt '읽는 행의 수(충분히 큰 수로 지정하고 Loop 내부에서 종료 조건 설정)
If Trim(oBaseRange.Offset(lRowOffset, 0).Value) = "" Then Exit For 'Loop 종료 조건
For lColOffset = 0 To 9 '읽는 열의 수
'cell 단위로 읽고 처리하는 코드 예시
'Debug.Print oBaseRange.Offset(lRowOffset, lColOffset).Value
sVal = oBaseRange.Offset(lRowOffset, lColOffset).Text
Next lColOffset
Next lRowOffset
End Sub
내부 For Loop의 다음 코드는 Cell 값을 읽는 가장 간단한 코드를 예시로 작성하였다.
For lRowOffset = 0To lRowCount - 1'읽는 행의 수 (Offset은 0부터 시작하므로 Count -1 까지 Loop 실행해야 함)
sVal = oBaseRange.Offset(lRowOffset, 0).Text
sVal = oBaseRange.Offset(lRowOffset, 1).Text
sVal = oBaseRange.Offset(lRowOffset, 2).Text
sVal = oBaseRange.Offset(lRowOffset, 3).Text
sVal = oBaseRange.Offset(lRowOffset, 4).Text
sVal = oBaseRange.Offset(lRowOffset, 5).Text
sVal = oBaseRange.Offset(lRowOffset, 6).Text
sVal = oBaseRange.Offset(lRowOffset, 7).Text
sVal = oBaseRange.Offset(lRowOffset, 8).Text
sVal = oBaseRange.Offset(lRowOffset, 9).Text
Next lRowOffset
EndSub
'단일 Loop를 사용하는 패턴: Single Loop(1), Text 사용
Public Sub SingleLoop1(aTargetRowCnt As Long)
Dim lRowOffset As Long, lColOffset As Long, oBaseRange As Range, sVal As String
Set oBaseRange = Range("B2")
lRowOffset = 0
Dim lRowCount As Long, lColCount As Long, oHeaderRange As Range
Set oHeaderRange = oBaseRange.Offset(-1, 0)
'lRowCount = Range(oBaseRange, oBaseRange.End(xlDown)).Rows.Count
lRowCount = aTargetRowCnt
lColCount = Range(oHeaderRange, oHeaderRange.End(xlToRight)).Columns.Count
For lRowOffset = 0 To lRowCount - 1 '읽는 행의 수 (Offset은 0부터 시작하므로 Count -1 까지 Loop 실행해야 함)
sVal = oBaseRange.Offset(lRowOffset, 0).Text
sVal = oBaseRange.Offset(lRowOffset, 1).Text
sVal = oBaseRange.Offset(lRowOffset, 2).Text
sVal = oBaseRange.Offset(lRowOffset, 3).Text
sVal = oBaseRange.Offset(lRowOffset, 4).Text
sVal = oBaseRange.Offset(lRowOffset, 5).Text
sVal = oBaseRange.Offset(lRowOffset, 6).Text
sVal = oBaseRange.Offset(lRowOffset, 7).Text
sVal = oBaseRange.Offset(lRowOffset, 8).Text
sVal = oBaseRange.Offset(lRowOffset, 9).Text
Next lRowOffset
End Sub
'단일 Loop를 사용하는 패턴: Single Loop(1), Text 사용
Public Sub SingleLoop1(aTargetRowCnt As Long)
Dim lRowOffset As Long, lColOffset As Long, oBaseRange As Range, sVal As String
Set oBaseRange = Range("B2")
lRowOffset = 0
Dim lRowCount As Long, lColCount As Long, oHeaderRange As Range
Set oHeaderRange = oBaseRange.Offset(-1, 0)
'lRowCount = Range(oBaseRange, oBaseRange.End(xlDown)).Rows.Count
lRowCount = aTargetRowCnt
lColCount = Range(oHeaderRange, oHeaderRange.End(xlToRight)).Columns.Count
For lRowOffset = 0 To lRowCount - 1 '읽는 행의 수 (Offset은 0부터 시작하므로 Count -1 까지 Loop 실행해야 함)
sVal = oBaseRange.Offset(lRowOffset, 0).Text
sVal = oBaseRange.Offset(lRowOffset, 1).Text
sVal = oBaseRange.Offset(lRowOffset, 2).Text
sVal = oBaseRange.Offset(lRowOffset, 3).Text
sVal = oBaseRange.Offset(lRowOffset, 4).Text
sVal = oBaseRange.Offset(lRowOffset, 5).Text
sVal = oBaseRange.Offset(lRowOffset, 6).Text
sVal = oBaseRange.Offset(lRowOffset, 7).Text
sVal = oBaseRange.Offset(lRowOffset, 8).Text
sVal = oBaseRange.Offset(lRowOffset, 9).Text
Next lRowOffset
End Sub
단일 Loop를 사용하는 패턴-Single Loop(2), Text 대신 Value2 사용
Single Loop 구문을 유지하고 Range의 값을 꺼낼 때 Text 대신 Value2를 사용하였다. 성능 비교는 가장 아래쪽에 작성해 두었다.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
' 행만 Loop, Text 대신 Value2 사용
PublicSubSingleLoop(aTargetRowCnt AsLong)
Dim lRowOffset AsLong, lColOffset AsLong, oBaseRange As Range, sVal AsString
Set oBaseRange = Range("B2")
lRowOffset = 0
Dim lRowCount AsLong, lColCount AsLong, oHeaderRange As Range
For lRowOffset = 0To lRowCount - 1'읽는 행의 수 (Offset은 0부터 시작하므로 Count -1 까지 Loop 실행해야 함)
sVal = oBaseRange.Offset(lRowOffset, 0).Value2
sVal = oBaseRange.Offset(lRowOffset, 1).Value2
sVal = oBaseRange.Offset(lRowOffset, 2).Value2
sVal = oBaseRange.Offset(lRowOffset, 3).Value2
sVal = oBaseRange.Offset(lRowOffset, 4).Value2
sVal = oBaseRange.Offset(lRowOffset, 5).Value2
sVal = oBaseRange.Offset(lRowOffset, 6).Value2
sVal = oBaseRange.Offset(lRowOffset, 7).Value2
sVal = oBaseRange.Offset(lRowOffset, 8).Value2
sVal = oBaseRange.Offset(lRowOffset, 9).Value2
Next lRowOffset
EndSub
' 행만 Loop, Text 대신 Value2 사용
Public Sub SingleLoop(aTargetRowCnt As Long)
Dim lRowOffset As Long, lColOffset As Long, oBaseRange As Range, sVal As String
Set oBaseRange = Range("B2")
lRowOffset = 0
Dim lRowCount As Long, lColCount As Long, oHeaderRange As Range
Set oHeaderRange = oBaseRange.Offset(-1, 0)
'lRowCount = Range(oBaseRange, oBaseRange.End(xlDown)).Rows.Count
lRowCount = aTargetRowCnt
lColCount = Range(oHeaderRange, oHeaderRange.End(xlToRight)).Columns.Count
For lRowOffset = 0 To lRowCount - 1 '읽는 행의 수 (Offset은 0부터 시작하므로 Count -1 까지 Loop 실행해야 함)
sVal = oBaseRange.Offset(lRowOffset, 0).Value2
sVal = oBaseRange.Offset(lRowOffset, 1).Value2
sVal = oBaseRange.Offset(lRowOffset, 2).Value2
sVal = oBaseRange.Offset(lRowOffset, 3).Value2
sVal = oBaseRange.Offset(lRowOffset, 4).Value2
sVal = oBaseRange.Offset(lRowOffset, 5).Value2
sVal = oBaseRange.Offset(lRowOffset, 6).Value2
sVal = oBaseRange.Offset(lRowOffset, 7).Value2
sVal = oBaseRange.Offset(lRowOffset, 8).Value2
sVal = oBaseRange.Offset(lRowOffset, 9).Value2
Next lRowOffset
End Sub
' 행만 Loop, Text 대신 Value2 사용
Public Sub SingleLoop(aTargetRowCnt As Long)
Dim lRowOffset As Long, lColOffset As Long, oBaseRange As Range, sVal As String
Set oBaseRange = Range("B2")
lRowOffset = 0
Dim lRowCount As Long, lColCount As Long, oHeaderRange As Range
Set oHeaderRange = oBaseRange.Offset(-1, 0)
'lRowCount = Range(oBaseRange, oBaseRange.End(xlDown)).Rows.Count
lRowCount = aTargetRowCnt
lColCount = Range(oHeaderRange, oHeaderRange.End(xlToRight)).Columns.Count
For lRowOffset = 0 To lRowCount - 1 '읽는 행의 수 (Offset은 0부터 시작하므로 Count -1 까지 Loop 실행해야 함)
sVal = oBaseRange.Offset(lRowOffset, 0).Value2
sVal = oBaseRange.Offset(lRowOffset, 1).Value2
sVal = oBaseRange.Offset(lRowOffset, 2).Value2
sVal = oBaseRange.Offset(lRowOffset, 3).Value2
sVal = oBaseRange.Offset(lRowOffset, 4).Value2
sVal = oBaseRange.Offset(lRowOffset, 5).Value2
sVal = oBaseRange.Offset(lRowOffset, 6).Value2
sVal = oBaseRange.Offset(lRowOffset, 7).Value2
sVal = oBaseRange.Offset(lRowOffset, 8).Value2
sVal = oBaseRange.Offset(lRowOffset, 9).Value2
Next lRowOffset
End Sub
데이터를 한번에 memory로 읽는 방법-Variant Array 사용
데이터를 읽을 범위를 정하고 2차원 Variant Array로 한번에 읽는 방법으로 상세 코드는 다음과 같다.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
' 한번에 읽는 패턴
PublicSubVariantArray(aTargetRowCnt AsLong)
Dim vRngArr AsVariant, oBaseRange As Range, oHeaderRange As Range, lColCount AsLong, sVal AsString
' 한번에 읽는 패턴
Public Sub VariantArray(aTargetRowCnt As Long)
Dim vRngArr As Variant, oBaseRange As Range, oHeaderRange As Range, lColCount As Long, sVal As String
Set oBaseRange = Range("B2")
Set oHeaderRange = oBaseRange.Offset(-1, 0)
lColCount = Range(oHeaderRange, oHeaderRange.End(xlToRight)).Columns.Count
vRngArr = oBaseRange.Resize(aTargetRowCnt, lColCount).Value2
Dim lCol As Long, lRow As Long
For lRow = LBound(vRngArr, 1) To UBound(vRngArr, 1)
For lCol = LBound(vRngArr, 2) To UBound(vRngArr, 2)
'Debug.Print vRngArr(lRow, lCol)
sVal = vRngArr(lRow, lCol)
Next lCol
Next lRow
End Sub
' 한번에 읽는 패턴
Public Sub VariantArray(aTargetRowCnt As Long)
Dim vRngArr As Variant, oBaseRange As Range, oHeaderRange As Range, lColCount As Long, sVal As String
Set oBaseRange = Range("B2")
Set oHeaderRange = oBaseRange.Offset(-1, 0)
lColCount = Range(oHeaderRange, oHeaderRange.End(xlToRight)).Columns.Count
vRngArr = oBaseRange.Resize(aTargetRowCnt, lColCount).Value2
Dim lCol As Long, lRow As Long
For lRow = LBound(vRngArr, 1) To UBound(vRngArr, 1)
For lCol = LBound(vRngArr, 2) To UBound(vRngArr, 2)
'Debug.Print vRngArr(lRow, lCol)
sVal = vRngArr(lRow, lCol)
Next lCol
Next lRow
End Sub
Resize는 시작 범위(Range)로부터 지정한 행, 열의 수만큼 범위를 확장하여 값을 읽을 대상을 설정한다. Value2는 Range의 값을 형식없이 원래의 값으로 꺼내는 속성이다. 값을 꺼낼 Range에 여러 행과 열을 포함하고 있다면 그 값은 2차원 Variant Array로 꺼내진다. 그래서 위 코드는 읽을 범위 전체를 한번에 memory로 읽게 된다.
성능비교
10부터 10만까지 Row Count를 증가시키면서 각 방법의 실행시간을 정리하면 다음과 같다. (실행환경: 2019 LG Gram, CPU i5, Ram 16GB, 256GB SSD)
표2. 입력 Count와 소요시간(초)
그림1. 입력 Count와 소요시간(초)
실행 성능은 다음과 같이 요약할 수 있다.
Nested Loop, Single Loop 반복 구문 자체의 실행 성능은 거의 차이가 없다.