Ускоряем работу VBA в Excel

Время, занимаемое выполнением макроса, может охватывать десятки минут, а то и несколько часов, поэтому ускоряемся!
rocket_excel

Предисловие

Так уж сложилось, что на сегодняшний день много кому приходится работать(писать макросы) на VBA в Excel. Некоторые макросы содержат сотни строк кода, которые приходится выполнять каждый день (неделю, месяц, квартал и так далее) и, при этом, они занимают изрядное количество времени. Вроде бы и и процесс автоматизирован и человеческого вмешательства не нужно, но время, занимаемое выполнением макроса, может охватывать десятки минут, а то и несколько часов. Время, как говориться, — деньги и в этом посте я постараюсь значительно ускорить время выполнения Вашего макроса и, возможно, это положительно скажется на ваших делах, а в итоге и деньгах.

Ускоряем работу макроса

Итак, к сути… Для того что бы реально ускорить работу VBA в Ecxel нужно понимать, что обращение к ячейке на листе — занимает значительно время. Если Вы хотите записать в ячейку одно значение, то это не займет значительного времени, но если Вам потребуется записать(прочитать, обратиться) к тысячам ячеек, то это потребует гораздо большего времени. Что же делать в таких случаях? На помощь приходят массивы. Массивы хранятся в памяти, а операции в памяти VBA выполняет в сотни, а то и в тысячи раз быстрее. Поэтому, если у Вас в данных тысячи, сотни тысяч значений, то время выполнения макроса может занимать от нескольких минут до нескольких часов, а если эти данные перенести в массив, то выполнение макроса может сократиться до нескольких секунд (минут).

Я наведу пример кода и в комментариях объясню что к чему, так будет яснее. К тому же, могут пригодиться некоторые строки кода, не относящееся прямо к процессу ускорения.

Пример

Предположим, что у нас есть данные на “Лист1” (“Sheet1”). Данные содержаться в 50 колонках (колонки содержат названия) и 10 000 строк. К примеру, нам нужно в последнюю колонку внести значение, которое равно значению во второй колонке, деленное на значение в третьей колонке (начиная со 2-й строки, так как первая содержит заглавие). Потом мы возьмем первые 10 колонок и скопируем их на “Лист2” (“Sheet2”), для дальнейшей обработки (для других потребностей). Пусть пример и банальный, но, как мне кажется, он может отобразить всю суть данного поста.

'Для явной инициализации переменных, включаем эту опцию 'Это поможет избежать многих ошибок
 Option Explicit  Sub Test()
'К листам будем обращаться через переменны
е Dim Sheet1_WS, Sheet2_WS As Worksheet
'Переменная для прохождения срок на листе (в массиве)
Dim i As Long
'Массив, в котором будут храниться наши данные 
Dim R_data As Variant
'Переменные последней строки и колонки
Dim FinalRow, FinalColumn As Long
'Можно инициализировать лист не по названию, а по порядковому номеру '
Set Sheet1_WS = Application.ThisWorkbook.Worksheet("Sheet1")
Set Sheet1_WS = Application.ThisWorkbook.Sheets(1) 
Set Sheet2_WS = Application.ThisWorkbook.Sheets(2)  
'Поиск последней не пустой строки в первой колонке 
'Нужно, что бы данные не были отфильтрованы, иначе последняя строка будет последней строкой в фильтре 
'Также в последней строке, в первой колонке, не должно быть пустой ячейки. Конечно, если в этой строке вообще есть данные. Иначе последней строкой будет последняя не пустая ячейка.
 FinalRow = Sheet1_WS.Cells(Rows.Count, 1).End(xlUp).Row '=10 000
  'Поиск последней не пустой колонки в первой строке FinalColumn = Sheet1_WS.Cells(1, Columns.Count).End(xlToLeft).Column '=50  
'Присваиваем массиву диапазон данных на Листе 1 R_data = Sheet1_WS.Range(Sheet1_WS.Cells(1, 1), Sheet1_WS.Cells(FinalRow, FinalColumn))  For i = 2 To FinalRow     
'Выполняем нужные нам операции с данными.     'Проверяем, что бы не было деления на ноль.     'Предполагается, что в колонке 2 и 3 стоят числовые данные     
'Иначе потребуется обработка ошибок     If R_data(i, 3) <> 0 Then         R_data(i, FinalColumn) = R_data(i, 2) / R_data(i, 3)     End If Next i  
'Копируем данные из массива обратно на Лист1 
'Перед этим очищаем данные на листе (если есть форматирование или формулы, то лучше 
Sheet1_WS.Cells.ClearContents) Sheet1_WS.Cells.Delete 
Sheet1_WS.Range(Sheet1_WS.Cells(1, 1), Sheet1_WS.Cells(FinalRow, FinalColumn)) = R_data  
'Копируем данные на Лист2, копируем первые 10 колонок. 
Sheet2_WS.Range(Sheet2_WS.Cells(1, 1), Sheet2_WS.Cells(FinalRow, 10)) = R_data
'Закрываем книгу и сохраняем её 
Workbooks(Application.ThisWorkbook.Name).Close SaveChanges:=True
End Sub 

В данном примере массив заполняется указанным диапазоном. Если у нас будет явно заданный двумерный массив, то скопировать его значение на лист можно таким образом:

Dim R_new() As Variant .....
' Явно указываем размер массива  
ReDim R_new(1 To FinalRow, 1 To 50) As Variant ..... 
Sheet1_WS.Range(Sheet1_WS.Cells(1, 1), Sheet1_WS.Cells(FinalRow, 50)) = R_new() 

 

Заключение

Большинство операций над данными можно выполнять в массиве, при этом, отображать на лист только результат. Иногда целесообразным бывает показать результат на лист, потом выполнить некоторые действия (например, сортировку) и снова загрузить данные в массив.

Для меня было большой неожиданностью ускорения работы макроса за счет массивов, так как данные на листах, на самом деле, итак представляют собой двумерный массив. Но, оказывается, обращение к памяти происходит гораздо быстрей, чем к ячейкам на листе.