- Вопрос или проблема
- Правка для добавления заметки о ручном перемещении одного пустого листа
- Правка для добавления заметок о использовании сортировки O(N * lg N) и использовании массива
- Упрощение проблемы
- Ответ или решение
- Оптимизация работы с сортировкой листов в Excel VBA
- Причины медленной сортировки листов
- Стратегии оптимизации
- Заключение
Вопрос или проблема
Я использую VBA в Excel (Microsoft 365). В моей книге 50 листов. У меня есть макрос для сортировки листов по имени, реализованный следующим образом:
Private Sub set_speed_optimizations(enable As Boolean)
If enable = True Then
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Else
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
End If
End Sub
Sub sort_sheets_by_name()
On Error GoTo handle_error
set_speed_optimizations True
Dim iSheets As Integer
iSheets = ThisWorkbook.Sheets.Count
Dim i As Integer
Dim j As Integer
' Сортируем все листы в алфавитном порядке
For i = 1 To iSheets - 1
For j = i + 1 To iSheets
If ThisWorkbook.Sheets(j).Name < ThisWorkbook.Sheets(i).Name Then
ThisWorkbook.Sheets(j).Move Before:=ThisWorkbook.Sheets(i)
End If
Next j
Next i
On Error Resume Next
' Вручную перемещаем несколько определенных листов в начало панели листов
ThisWorkbook.Sheets("First_0").Move Before:=ThisWorkbook.Sheets(1)
ThisWorkbook.Sheets("First_1").Move Before:=ThisWorkbook.Sheets(1)
' Вручную перемещаем несколько определенных листов в конец панели листов
ThisWorkbook.Sheets("Last_N-1").Move After:=ThisWorkbook.Sheets(iSheets)
ThisWorkbook.Sheets("Last_N").Move After:=ThisWorkbook.Sheets(iSheets)
ThisWorkbook.Sheets("Main").Activate
Beep
' Не выходите из Sub до этого обработчика ошибок, потому что то, что следует, должно быть
' выполнено в любом случае
handle_error:
set_speed_optimizations False
End Sub
Этот макрос работает, но выполняется крайне медленно. Сортировка 50 листов по имени занимает около 15 минут.
Листы большие и сложные и содержат связи с книгами в общем доступе (с более чем 170000 ссылок на эти связанные книги). В дополнение к тем действиям, которые я предпринял в set_speed_optimizations
, чтобы попытаться ускорить процесс, я также разорвал все связи с книгами.
После всего этого сортировка листов в алфавитном порядке по имени все еще очень, очень медленная. Как я могу определить, что вызывает эту медлительность, и решить эту проблему?
Правка для добавления заметки о ручном перемещении одного пустого листа
Один из листов в моей книге пуст. Если я устанавливаю Формулы–>Параметры расчета–>Ручной и затем вручную перетаскиваю свой пустой лист на одно место влево или вправо на панели листов, Excel зависает на около 11 секунд, прежде чем лист будет перемещен.
Правка для добавления заметок о использовании сортировки O(N * lg N) и использовании массива
Учитывая мою предыдущую правку о том, что даже ручное перемещение одного (пустого) листа происходит медленно и учитывая мой (маленький) случай N = 50, я опасался, что проблема может заключаться не в использовании неэффективного O(N^2) алгоритма сортировки. Тем не менее, я попробовал реализовать рекомендуемый O(N * lg N) алгоритм сортировки и, к сожалению, я теперь показал, что алгоритм сортировки не вносит значительного вклада в проблему.
В дополнение к реализации Быстрой сортировки я также внедрил другую рекомендацию: хранение имен в массиве, сортировка массива и затем использование этого массива для перемещения каждого листа в его правильное место.
Вот мой обновленный код:
Sub quick_sort(v_array As Variant, in_low As Long, in_high As Long)
Dim pivot As Variant
Dim tmp_low As Long
Dim tmp_high As Long
Dim tmp_swap As Variant
tmp_low = in_low
tmp_high = in_high
pivot = v_array((in_low + in_high) \ 2)
While (tmp_low <= tmp_high)
While (v_array(tmp_low) < pivot And tmp_low < in_high)
tmp_low = tmp_low + 1
Wend
While (pivot < v_array(tmp_high) And tmp_high > in_low)
tmp_high = tmp_high - 1
Wend
If (tmp_low <= tmp_high) Then
tmp_swap = v_array(tmp_low)
v_array(tmp_low) = v_array(tmp_high)
v_array(tmp_high) = tmp_swap
tmp_low = tmp_low + 1
tmp_high = tmp_high - 1
End If
Wend
If (in_low < tmp_high) Then quick_sort v_array, in_low, tmp_high
If (tmp_low < in_high) Then quick_sort v_array, tmp_low, in_high
End Sub
Private Sub set_speed_optimizations(enable As Boolean)
If enable = True Then
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Else
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
End If
End Sub
Sub sort_sheets_by_name()
On Error GoTo handle_error
set_speed_optimizations True
Dim i As Long
Dim num_sheets As Long
Dim sheet_names()
num_sheets = ThisWorkbook.Sheets.Count
ReDim sheet_names(1 To num_sheets)
For i = LBound(sheet_names) To UBound(sheet_names)
sheet_names(i) = ThisWorkbook.Sheets(i).Name
Next
quick_sort sheet_names, LBound(sheet_names), UBound(sheet_names)
Dim old_timer As Double
For i = LBound(sheet_names) To UBound(sheet_names)
Debug.Print sheet_names(i) & ",";
old_timer = Timer()
ThisWorkbook.Sheets(sheet_names(i)).Move After:=ThisWorkbook.Sheets(num_sheets)
Debug.Print (Timer() - old_timer)
Next
ThisWorkbook.Sheets("Main").Activate
Beep
' Не выходите из Sub до этого обработчика ошибок, потому что то, что следует, должно быть
' выполнено в любом случае
handle_error:
set_speed_optimizations False
End Sub
С момента моего оригинального поста количество листов, с которыми я работаю, увеличилось с N = 50 до N = 52.
Я также внедрил временные показатели по листам, как рекомендуется, и представлю сводную статистику по всем 52 листам ниже. Все временные показатели имеют единицы секунд, затраченные на перемещение одного листа.
O(N^2) Сортировка (оптимизации скорости) | Быстрая сортировка (оптимизации скорости) | Быстрая сортировка (без оптимизаций скорости) | |
---|---|---|---|
MAX | 13.59375 | 13.28515625 | 16.05859375 |
MIN | 4.00390625 | 3.9140625 | 5.24609375 |
MEAN | 12.90857873 | 12.72092849 | 14.98764273 |
MEDIAN | 13.01953125 | 12.84765625 | 15.1875 |
STDDEV | 1.275054623 | 1.249827753 | 1.417386677 |
Что меня удивило, так это то, что оптимизации скорости, реализованные в set_speed_optimizations()
, дали лишь около 15% улучшения. Я утверждаю это, потому что быстрый эксперимент показал, что перемещение листа приводит к перерасчету (если автоматический перерасчет включен). Поскольку мои листы очень сложные, я бы подумал, что ненужный перерасчет будет львиной долей проблемы. Однако это, похоже, не так.
Упрощение проблемы
Возможно, упростить проблему, сосредоточившись на моем прежнем эксперименте с перемещением одного (пустого) листа на одно место на панели листов. Как уже было сказано, при выключенном автоматическом перерасчете это занимает около 11 секунд.
Что может вызвать перемещение пустого листа на одно место на панели листов, при выключенном автоматическом перерасчете, занять 11 секунд?
После всего этого сортировка листов в алфавитном порядке по имени все еще очень, очень медленная. Как я могу определить, что вызывает эту медлительность, и решить эту проблему?
Чтобы определить приблизительную оценку времени обработки, используйте функцию таймера – функцию timer(). Она возвращает вам значение счетчика, основанное на секундах.
Если вы вставите две строки кода в ваш цикл и инициализируете переменную old_timer перед вложенным циклом
DIM oldtimer as double
If ThisWorkbook.Sheets(j).Name < ThisWorkbook.Sheets(i).Name Then
Debug.Print timer()-oldtimer
oldtimer=timer()
ThisWorkbook.Sheets(j).Move Before:=ThisWorkbook.Sheets(i)
End If
вы найдете время для каждой операции перемещения в окне вывода в редакторе VBA.
По какой-то причине, которую я не знаю, я предполагаю, что функция перемещения листа довольно медленная.
Вы должны минимизировать такие операции. Чтобы проследить за этим, вставьте счетчик для циклов:
перед вашим циклом:
DIM oldtimer as double
DIM movcount as long
movcount=0
внутри вашего цикла:
If ThisWorkbook.Sheets(j).Name < ThisWorkbook.Sheets(i).Name Then
movcount=movcount+1
Debug.Print movcount, timer()-oldtimer
oldtimer=timer()
ThisWorkbook.Sheets(j).Move Before:=ThisWorkbook.Sheets(i)
End If
Cybernetic.nomad описывает решение в своем комментарии!
Вы должны перемещать лист только один раз.
Если вы разделите movcount на iSheets, вы получите представление о том, на сколько вы можете улучшить время выполнения вашего конкретного листа Excel.
Кроме того, ваш код реализует очень медленный алгоритм сортировки, его скорость составляет порядка iSheet^2, специалисты по IT говорят, что ваш код находится в O(n^2).
Обычно класс скорости важен только для больших n (в вашем случае: iSheet), но в вашем случае каждая операция перемещения, похоже, является тяжелым бременем.
Подсказка:
Эффективная сортировка данных в VBA для Excel без использования функций рабочего листа требует некоторого программирования с использованием не фиксированных массивов, а динамических структур на основе коллекций VBA. Хорошие алгоритмы сортировки являются членами класса O(n * log(n)). O(n * n), однако, считается медленным.
Я буду рад помочь вам оптимизировать процесс сортировки листов в вашем коде Excel VBA. Вот несколько дополнительных соображений и потенциальных решений на основе предоставленной информации:
1. Структура и сложность листа:
- Связи с книгами: Хотя разрыв связей с книгами может улучшить производительность, важно учитывать их необходимость. Если они критичны для ваших расчетов, рассмотрите альтернативные способы их управления или оптимизации связанных формул.
- Сложные формулы и расчеты: Проанализируйте формулы и расчеты в ваших листах. Если они вычислительно затратные, упростите их или рассмотрите возможность использования более эффективных функций или методов.
- Условное форматирование и проверка данных: Эти функции могут значительно повлиять на производительность, особенно если они включают сложные правила или большие наборы данных. Минимизируйте их использование или оптимизируйте их условия.
- Сводные таблицы: Сводные таблицы могут медленно обновляться, особенно с большими наборами данных или сложными расчетами. Если возможно, упростите их структуру или рассмотрите возможность использования других методов анализа данных.
2. Оптимизация кода VBA:
- Избегайте ненужных циклов: Минимизируйте количество циклов и вложенных циклов в вашем коде. Если возможно, используйте операции на основе массивов или встроенные функции для более эффективных расчетов.
- Условия раннего выхода: Если условие выполняется раньше в цикле, используйте
Exit For
илиExit Do
, чтобы избежать ненужных итераций. - Объявление переменных: Объявляйте переменные с соответствующими типами данных, чтобы оптимизировать использование памяти и производительность.
- Обработка ошибок: Используйте
On Error GoTo
экономно и только когда это необходимо. Чрезмерная обработка ошибок может замедлить ваш код. - Ссылки на объекты: Избегайте ненужных ссылок на объекты. Используйте
Set
, чтобы назначить объекты, и освободите их, используяSet obj = Nothing
, когда закончите.
3. Настройки Excel:
- Режим расчета: Убедитесь, что
xlCalculationManual
установлен в вашей подпрограммеset_speed_optimizations
. Это предотвращает ненужные перерасчеты во время процесса сортировки. - ScreenUpdating и EnableEvents: Установите эти свойства в
False
, чтобы избежать визуальных обновлений и событий во время сортировки. - Параметры расчета: Изучите параметры расчета в настройках Excel. Например, вы можете рассмотреть возможность отключения итеративных расчетов или настройки параметров точности, если это необходимо.
4. Дополнительные советы:
- Временная книга: Если ваша книга крайне велика или сложна, рассмотрите возможность создания временной книги для выполнения сортировки, а затем копирования результатов в оригинальную книгу.
- Внешние инструменты сортировки: Если VBA все еще слишком медленно, изучите внешние инструменты или библиотеки сортировки, которые могут предложить лучшую производительность.
Специфические соображения для вашего случая:
- Ручное перемещение листа: Замедление ручного перемещения пустого листа может быть связано с основными расчетами или связанными данными в книге, даже если сам лист пуст. Попробуйте изолировать проблему, создав новую пустую книгу и переместив лист в ней.
- Сортировка на основе массивов: Как вы уже реализовали, использование алгоритма сортировки на основе массива, такого как Быстрая сортировка, может быть более эффективным, чем вложенные циклы.
- Профилирование производительности: Рассмотрите возможность использования инструмента профилирования производительности для выявления конкретных узких мест в вашем коде. Это может помочь вам сосредоточить ваши усилия по оптимизации на самых значительных областях.
Тщательно учитывая эти факторы и реализуя соответствующие оптимизации, вы сможете значительно улучшить производительность процесса сортировки листов в Excel VBA.
Ответ или решение
Оптимизация работы с сортировкой листов в Excel VBA
Сортировка листов в Excel VBA может быть затруднительной задачей, особенно когда файл включает в себя большое количество листов и сложную структуру данных. В вашем случае, сортировка 52 листов занимает слишком много времени, и даже использование алгоритмов, таких как быстрая сортировка, не значительно улучшает производительность. Рассмотрим основные факторы, которые могут влиять на производительность, а также стратегии для улучшения.
Причины медленной сортировки листов
-
Структура и сложность листов:
- Ссылки на другие книги: Большое количество ссылок на другие книги может привести к снижению производительности при выполнении операций перемещения листов. Даже если вы разорвали некоторые ссылки, стоит проверить, насколько они критичны для общего функционала.
- Сложные формулы и вычисления: Если на листах используются сложные формулы, это может существенно замедлить их обработку. Рассмотрите возможность упрощения или оптимизации формул.
- Условное форматирование и валидация данных: Эти функции могут значительно замедлять работу, особенно при использовании сложных правил. Оптимизируйте их применение или минимизируйте использование, если это возможно.
- Сводные таблицы: Часто они требуют значительных ресурсов при обновлении, особенно если данные обширны или вычисления сложны.
-
Код VBA и его оптимизация:
- Избегание ненужных циклов: Старайтесь минимизировать количество и уровень вложенности циклов. Вместо этого используйте операции с массивами или встроенные функции.
- Режим вычислений и обновления экрана: Убедитесь, что в процессе сортировки установлены
Application.Calculation = xlCalculationManual
иApplication.ScreenUpdating = False
, чтобы предотвратить лишние перерасчеты и обновления интерфейса. - Оптимизация обработки ошибок: Используйте обработку ошибок там, где это действительно необходимо. Чрезмерное включение может замедлить выполнение программы.
-
Изоляция проблемы:
- Перемещение пустого листа: Замедление даже при перемещении пустого листа может указывать на проблемы в структуре файла. Попробуйте создать новый пустой файл и переместить листы внутри него для тестирования.
Стратегии оптимизации
- Оптимизация алгоритма сортировки:
- Рассмотрите возможность сортировки листов с помощью более эффективных алгоритмов сортировки (например, быстрая сортировка), при этом сохраняйте имена листов в массиве и перемещайте их по окончании сортировки.
- Также можно текущие листы перемещать только один раз, чтобы избежать множественных операций перемещения в цикле.
Sub optimized_sort_sheets()
set_speed_optimizations True
Dim i As Long, j As Long
Dim sheet_names() As Variant
Dim temp As Worksheet
Dim num_sheets As Long
num_sheets = ThisWorkbook.Sheets.Count
ReDim sheet_names(1 To num_sheets)
For i = 1 To num_sheets
sheet_names(i) = ThisWorkbook.Sheets(i).Name
Next i
' Ваша функция быстрого сортирования здесь для получения отсортированных имен
Application.DisplayAlerts = False
For i = 1 To num_sheets
ThisWorkbook.Sheets(sheet_names(i)).Move After:=ThisWorkbook.Sheets(num_sheets)
Next i
Application.DisplayAlerts = True
set_speed_optimizations False
End Sub
-
Использование временной книги:
- Если производительность все еще неудовлетворительна, рассмотрите вариант создания временной книги, в которой будут сортироваться листы, а затем результаты будут скопированы обратно в исходную книгу.
-
Профилирование производительности:
- Используйте встроенные средства для профилирования и анализа производительности вашего кода, чтобы выявить узкие места и проблемы.
Заключение
Оптимизация сортировки листов в Excel VBA может существенно улучшить производительность и сделать пользовательский опыт более приятным. Следуя предложенным стратегиям и стремясь к упрощению структуры ваших Workbook и кода, вы сможете значительно сократить время, необходимое для сортировки листов, и минимизировать нагрузку на систему.
Проводя тесты при каждой оптимизации, вы сможете эффективно оценить влияние изменений и добиться желаемых результатов.