Ключевые оптимизации:
1. Кэширование файлов. Все файлы открываются один раз и кэшируются.
Убрано многократное открытие/закрытие
2. Замена PowerQuery на словари. Убрана зависимость от PowerQuery в обработке штрихкодов.
Используются быстрые словари Scripting.Dictionary
3. Оптимизация работы с Excel. Отключены все обновления экрана и расчеты.
Используются быстрые методы работы с массивами.
Минимизировано обращение к ячейкам через Range.Value
4. Приоритеты и управление питанием. Повышен приоритет потока Excel.
Предотвращен переход в спящий режим.
Привязка к производительным ядрам на гибридных процессорах
5. Пакетная обработка. Объединены операции со словарями.
Убраны лишние циклы. Используются быстрые алгоритмы копирования
6. Легкий DoEvents.
Добавлен облегченный DoEvents для отзывчивости без потерь производительности.
Использование: Замените старые модули на новые.
Для запуска используйте RunOptimized из modMainEntry
Настройте привязку к ядрам под свою архитектуру процессора.
Эта версия должна работать значительно быстрее на ноутбуке, особенно в фоновом режиме.
1. modGlobals - остается без изменений
Option Explicit
Public strPath As String 'путь к папке
Public Enum eFixed_Column ' столбцы для заполнения в шаблоне
otd = 2
dep
gr
podgr
art
naim
Post
prod
kod
resident
shtrih
price_val
last_price
margin
avg_price
grpABC
revenue
avg_margin
gmroi
column_to_fill = 68
last_column = 93
End Enum
Public Enum eFixed_Row
row_to_fill = 7
End Enum
2. clsAppOptimizer - новый класс для оптимизации
' clsAppOptimizer
Option Explicit
Private Type TAppSettings
ScreenUpdating As Boolean
Calculation As XlCalculation
EnableEvents As Boolean
DisplayAlerts As Boolean
DisplayStatusBar As Boolean
PrintCommunication As Boolean
PreviousPriority As Long
End Type
Private this As TAppSettings
Private Declare PtrSafe Function SetThreadPriority Lib "kernel32" (ByVal hThread As LongPtr, ByVal nPriority As Long) As Long
Private Declare PtrSafe Function GetCurrentThread Lib "kernel32" () As LongPtr
Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Private Declare PtrSafe Function SetThreadExecutionState Lib "kernel32" (ByVal esFlags As Long) As Long
Private Const THREAD_PRIORITY_HIGHEST = 2
Private Const THREAD_PRIORITY_NORMAL = 0
Private Const ES_SYSTEM_REQUIRED As Long = &H1
Private Const ES_CONTINUOUS As Long = &H80000000
Public Sub OptimizeStart()
' Сохраняем текущие настройки
With Application
this.ScreenUpdating = .ScreenUpdating
this.Calculation = .Calculation
this.EnableEvents = .EnableEvents
this.DisplayAlerts = .DisplayAlerts
this.DisplayStatusBar = .DisplayStatusBar
this.PrintCommunication = .PrintCommunication
' Устанавливаем высокопроизводительные настройки
.ScreenUpdating = False
.Calculation = xlCalculationManual
.EnableEvents = False
.DisplayAlerts = False
.DisplayStatusBar = False
.PrintCommunication = False
End With
' Повышаем приоритет потока
Dim hThread As LongPtr
hThread = GetCurrentThread()
this.PreviousPriority = SetThreadPriority(hThread, THREAD_PRIORITY_HIGHEST)
' Предотвращаем сон системы
SetThreadExecutionState ES_CONTINUOUS Or ES_SYSTEM_REQUIRED
End Sub
Public Sub OptimizeEnd()
' Восстанавливаем настройки
With Application
.ScreenUpdating = this.ScreenUpdating
.Calculation = this.Calculation
.EnableEvents = this.EnableEvents
.DisplayAlerts = this.DisplayAlerts
.DisplayStatusBar = this.DisplayStatusBar
.PrintCommunication = this.PrintCommunication
End With
' Восстанавливаем приоритет потока
Dim hThread As LongPtr
hThread = GetCurrentThread()
SetThreadPriority(hThread, this.PreviousPriority)
' Разрешаем сон системы
SetThreadExecutionState ES_CONTINUOUS
End Sub
Public Sub DoEventsLight()
' Облегченная версия DoEvents для фонового режима
Static lastTime As Double
If Timer - lastTime > 0.1 Then ' Только каждые 0.1 секунды
DoEvents
lastTime = Timer
End If
End Sub
3. clsFileCache - кэширование файлов
' clsFileCache
Option Explicit
Private fileCache As Object
Private wbCache As Object
Private Sub Class_Initialize()
Set fileCache = CreateObject("Scripting.Dictionary")
Set wbCache = CreateObject("Scripting.Dictionary")
End Sub
Private Sub Class_Terminate()
CloseAll
Set fileCache = Nothing
Set wbCache = Nothing
End Sub
Public Function GetWorkbook(ByVal fullPath As String) As Workbook
If Not wbCache.Exists(fullPath) Then
Dim wb As Workbook
Set wb = Workbooks.Open(fullPath, ReadOnly:=True, UpdateLinks:=0, _
IgnoreReadOnlyRecommended:=True, CorruptLoad:=xlNormalLoad)
wbCache.Add fullPath, wb
Set GetWorkbook = wb
Else
Set GetWorkbook = wbCache(fullPath)
End If
End Function
Public Function FileExists(ByVal fullPath As String) As Boolean
If Not fileCache.Exists(fullPath) Then
fileCache.Add fullPath, (Dir(fullPath) <> "")
End If
FileExists = fileCache(fullPath)
End Function
Public Sub CloseAll()
Dim key As Variant
For Each key In wbCache.Keys
On Error Resume Next
wbCache(key).Close SaveChanges:=False
Next key
wbCache.RemoveAll
End Sub
4. clstSheetTransformationFast - оптимизированная версия
' clstSheetTransformationFast
Option Explicit
Private m_current_sh As Worksheet
Private m_appOpt As clsAppOptimizer
Private Const m_max_length_name As Long = 30
Public Property Get current_sheet() As Worksheet
Set current_sheet = m_current_sh
End Property
Public Property Set current_sheet(ByVal vNewValue As Worksheet)
Set m_current_sh = vNewValue
Set m_appOpt = New clsAppOptimizer
End Property
Public Sub unmerge_used_range()
If Not m_current_sh Is Nothing Then
m_current_sh.UsedRange.UnMerge
End If
End Sub
Public Sub Delete_Empty_Rows_Fast()
' Быстрое удаление пустых строк
If m_current_sh Is Nothing Then Exit Sub
Dim lastRow As Long
lastRow = m_current_sh.Cells(m_current_sh.Rows.Count, 1).End(xlUp).Row
If lastRow > 1000 Then
' Для больших листов используем более быстрый метод
Dim data As Variant
Dim r As Long, startDel As Long, endDel As Long
Dim inEmptyBlock As Boolean
data = m_current_sh.Range("A1:A" & lastRow).Value
For r = 1 To lastRow
If Len(Trim(CStr(data(r, 1)))) = 0 And _
Application.CountA(m_current_sh.Rows(r)) = 0 Then
If Not inEmptyBlock Then
startDel = r
inEmptyBlock = True
End If
Else
If inEmptyBlock Then
endDel = r - 1
If endDel >= startDel Then
m_current_sh.Rows(startDel & ":" & endDel).Delete
r = startDel - 1
lastRow = lastRow - (endDel - startDel + 1)
ReDim Preserve data(1 To lastRow, 1 To 1)
End If
inEmptyBlock = False
End If
End If
Next r
Else
' Для маленьких листов - стандартный метод
Dim i As Long
For i = lastRow To 1 Step -1
If Application.CountA(m_current_sh.Rows(i)) = 0 Then
m_current_sh.Rows(i).Delete
End If
Next i
End If
End Sub
Public Function Get_Last_Row() As Long
Get_Last_Row = m_current_sh.Cells(m_current_sh.Rows.Count, 1).End(xlUp).Row
End Function
Public Function Get_Last_Column() As Long
Get_Last_Column = m_current_sh.Cells(1, m_current_sh.Columns.Count).End(xlToLeft).Column
End Function
5. modMain - полностью оптимизированный
' modMain
Option Explicit
Type tpMrk
market As String
price As String
element As Long
base_column As Long
price_column As Long
price_add As String
End Type
Private fileCache As clsFileCache
Private appOptimizer As clsAppOptimizer
Private sheetTransformer As clstSheetTransformationFast
Public Sub main_sub()
' Инициализация оптимизаторов
Set appOptimizer = New clsAppOptimizer
Set fileCache = New clsFileCache
Set sheetTransformer = New clstSheetTransformationFast
' Запуск оптимизации
appOptimizer.OptimizeStart
On Error GoTo ErrorHandler
' Получаем путь
strPath = ThisWorkbook.Path
If Right(strPath, 1) <> "\" Then strPath = strPath & "\"
' Читаем параметры
Dim mrk As tpMrk
With mrk
.market = ThisWorkbook.Worksheets(1).Range("A18").Value
.price = ThisWorkbook.Worksheets(1).Range("B18").Value
.element = ThisWorkbook.Worksheets(1).Range("C18").Value
.base_column = Choose(.element, 1, eFixed_Column.art, eFixed_Column.kod)
.price_column = Choose(.element, 0, 1, 0)
.price_add = Choose(.element, "MLT", .market, IIf(.market = "LGN", .market, "MLT"))
End With
' Таймер
Dim clsTim As clsTimer
Set clsTim = New clsTimer
clsTim.start_timer
' Открываем шаблон
Dim shablon As Workbook
Set shablon = fileCache.GetWorkbook(strPath & "шаблон.xlsm")
Dim ws_shablon As Worksheet
Set ws_shablon = shablon.Worksheets(1)
Set sheetTransformer.current_sheet = ws_shablon
clsTim.fix_timer "открытие файла Шаблон"
' Загрузка данных продаж (1)
Dim tmp_arr As Variant, order_arr As Variant
GetDataSalesFast strPath, tmp_arr, 1
clsTim.fix_timer "заполнение массива продажи(1)")
' Создаем массив заказов
ReDim order_arr(LBound(tmp_arr, 1) To UBound(tmp_arr, 1), 1 To 1)
FastCopyColumn tmp_arr, order_arr, 1, 1
' Получаем индексы колонок
Dim arr_ind As Variant
arr_ind = GetArrIndices(UBound(tmp_arr, 2), mrk.element)
If mrk.element = 2 Then
arr_ind = CombineArrays(Array(eFixed_Column.art, eFixed_Column.naim), arr_ind)
Else
arr_ind = CombineArrays(Array(eFixed_Column.kod, eFixed_Column.art, eFixed_Column.naim), arr_ind)
End If
' Заполняем шаблон
FillShablonFast ws_shablon, arr_ind, tmp_arr, True
ws_shablon.Calculate
clsTim.fix_timer "печать массива продажи(1)")
' Удаляем лишние строки
Dim lastRow As Long
lastRow = ws_shablon.Cells(ws_shablon.Rows.Count, mrk.base_column).End(xlUp).Row
ws_shablon.Range(ws_shablon.Cells(lastRow + 1, mrk.base_column), _
ws_shablon.Cells(ws_shablon.Rows.Count, mrk.base_column)).EntireRow.Delete
' Конвертируем в значения
ConvertToValuesFast ws_shablon.Range(ws_shablon.Cells(eFixed_Row.row_to_fill, eFixed_Column.otd), _
ws_shablon.Cells(lastRow, eFixed_Column.last_column))
clsTim.fix_timer "удаление лишних строк в Шаблоне"
' Загрузка остальных данных (оптимизированная)
ProcessAllDataFast ws_shablon, order_arr, strPath, mrk, lastRow, clsTim
' Закрываем все файлы
fileCache.CloseAll
' Восстанавливаем настройки
appOptimizer.OptimizeEnd
' Показываем результаты
MsgBox clsTim.print_result
Exit Sub
ErrorHandler:
' Очистка при ошибке
On Error Resume Next
fileCache.CloseAll
appOptimizer.OptimizeEnd
MsgBox "Ошибка: " & Err.Description, vbCritical
End Sub
Private Sub ProcessAllDataFast(ws As Worksheet, order_arr As Variant, path As String, _
mrk As tpMrk, lastRow As Long, clsTim As clsTimer)
' Объединенная обработка всех данных
Dim tmp_arr As Variant
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = TextCompare
' Обработка продаж (2)
GetDataSalesFast path, tmp_arr, 2
clsTim.fix_timer "заполнение массива продажи(2)")
RemovePostsFast tmp_arr
clsTim.fix_timer "очистка массива продажи(2)")
Dim out_arr As Variant
ReDim out_arr(LBound(order_arr, 1) To UBound(order_arr, 1), 1 To 5)
Dim arr_ind2(1 To 5) As Variant
arr_ind2(1) = eFixed_Column.Post
arr_ind2(2) = eFixed_Column.avg_price
arr_ind2(3) = eFixed_Column.revenue
arr_ind2(4) = eFixed_Column.avg_margin
arr_ind2(5) = eFixed_Column.last_price
FillDictPostFast tmp_arr, dict, 2, True
DictToArrayFast out_arr, order_arr, dict, 1
dict.RemoveAll
clsTim.fix_timer "подготовка массива продажи(2)"
' Продажи (3)
GetDataSalesFast path, tmp_arr, 3
clsTim.fix_timer "заполнение массива продажи(3)"
Dim i As Long
For i = LBound(tmp_arr, 2) + 1 To UBound(tmp_arr, 2)
FillDictPostFast tmp_arr, dict, i, False
DictToArrayFast out_arr, order_arr, dict, i - LBound(tmp_arr, 2) + 1
dict.RemoveAll
appOptimizer.DoEventsLight
Next i
clsTim.fix_timer "подготовка массива продажи(3)"
FillShablonFast ws, arr_ind2, out_arr, True
clsTim.fix_timer "печать массива продажи(2-3)"
' Прайс
GetDataPriceFast path, tmp_arr, mrk.price, mrk.price_column
clsTim.fix_timer "заполнение массива Прайс"
ReDim out_arr(LBound(order_arr, 1) To UBound(order_arr, 1), 1 To 1)
ReDim arr_ind2(1 To 1)
arr_ind2(1) = eFixed_Column.price_val
FillDictPostFast tmp_arr, dict, UBound(tmp_arr, 2), False
DictToArrayFast out_arr, order_arr, dict, 1
dict.RemoveAll
clsTim.fix_timer "подготовка массива Прайс"
FillShablonFast ws, arr_ind2, out_arr, True
clsTim.fix_timer "печать массива Прайс"
' Структура
GetDataStructureFast path, tmp_arr, mrk.price_add, mrk.price_column
clsTim.fix_timer "заполнение массива Структура"
ReDim out_arr(LBound(order_arr, 1) To UBound(order_arr, 1), 1 To 6)
ReDim arr_ind2(1 To 6)
arr_ind2(1) = eFixed_Column.prod
arr_ind2(2) = eFixed_Column.otd
arr_ind2(3) = eFixed_Column.dep
arr_ind2(4) = eFixed_Column.gr
arr_ind2(5) = eFixed_Column.podgr
arr_ind2(6) = eFixed_Column.resident
Dim k As Long: k = 1
For i = LBound(tmp_arr, 2) + (3 - mrk.price_column) To UBound(tmp_arr, 2)
FillDictPostFast tmp_arr, dict, i, False
DictToArrayFast out_arr, order_arr, dict, k
k = k + 1
dict.RemoveAll
appOptimizer.DoEventsLight
Next i
clsTim.fix_timer "подготовка массива Структура"
FillShablonFast ws, arr_ind2, out_arr, True
clsTim.fix_timer "печать массива Структура"
' Штрихкод (оптимизированная версия без PowerQuery)
GetDataBarcodeFast path, tmp_arr, (mrk.price_column + 1)
clsTim.fix_timer "заполнение массива Штрихкод"
ReDim out_arr(LBound(order_arr, 1) To UBound(order_arr, 1), 1 To 1)
ReDim arr_ind2(1 To 1)
arr_ind2(1) = eFixed_Column.shtrih
FillDictPostFast tmp_arr, dict, UBound(tmp_arr, 2), False
DictToArrayFast out_arr, order_arr, dict, 1
dict.RemoveAll
clsTim.fix_timer "подготовка массива Штрихкод"
FillShablonFast ws, arr_ind2, out_arr, True
clsTim.fix_timer "печать массива Штрихкод"
' Финал
ProcessFinalCalculations ws, lastRow, path, mrk.price_column, clsTim
End Sub
Private Sub ProcessFinalCalculations(ws As Worksheet, lastRow As Long, path As String, _
price_column As Long, clsTim As clsTimer)
With ws
' Обработка формул и проверок
ProcessFormulasAndChecks ws, lastRow
clsTim.fix_timer "обработка формул и проверок"
' Остатки
Dim tmp_arr As Variant, dict As Object
Set dict = CreateObject("Scripting.Dictionary")
GetDataOstatkiFast path, tmp_arr, price_column
FillDictPostFast tmp_arr, dict, UBound(tmp_arr, 2), False, True
Dim base_arr As Variant, lastCol_arr As Variant
base_arr = .Range(.Cells(eFixed_Row.row_to_fill, 1), _
.Cells(lastRow, 1)).Value
lastCol_arr = .Range(.Cells(eFixed_Row.row_to_fill, eFixed_Column.column_to_fill - 1), _
.Cells(lastRow, eFixed_Column.column_to_fill)).Value
Dim counter As Long, ost_val As Double, sum_val As Double, price_val As Double
For counter = LBound(base_arr, 1) To UBound(base_arr, 1)
If dict.Exists(base_arr(counter, 1)) Then
ost_val = lastCol_arr(counter, 1)
sum_val = lastCol_arr(counter, 2)
If ost_val <> dict(base_arr(counter, 1)) Then
If ost_val <> 0 Then
price_val = Abs(sum_val / ost_val)
ost_val = dict(base_arr(counter, 1))
sum_val = ost_val * price_val
Else
sum_val = 0
End If
lastCol_arr(counter, 1) = ost_val
lastCol_arr(counter, 2) = sum_val
End If
If lastCol_arr(counter, 2) < 0 Then
lastCol_arr(counter, 2) = 0
End If
Else
lastCol_arr(counter, 1) = 0
lastCol_arr(counter, 2) = 0
End If
If counter Mod 100 = 0 Then appOptimizer.DoEventsLight
Next counter
.Range(.Cells(eFixed_Row.row_to_fill, eFixed_Column.column_to_fill - 1), _
.Cells(lastRow, eFixed_Column.column_to_fill)).Value = lastCol_arr
clsTim.fix_timer "проверка остатков"
clsTim.end_timer
End With
End Sub
' Быстрые вспомогательные функции
Private Sub FastCopyColumn(ByRef srcArr As Variant, ByRef destArr As Variant, _
ByVal srcCol As Long, ByVal destCol As Long)
Dim i As Long
For i = LBound(srcArr, 1) To UBound(srcArr, 1)
destArr(i, destCol) = srcArr(i, srcCol)
Next i
End Sub
Private Sub FillShablonFast(ByVal ws As Worksheet, ByVal arr_column As Variant, _
ByVal arr_data As Variant, ByVal bFlag As Boolean)
Dim i As Long
For i = UBound(arr_column, 1) To LBound(arr_column, 1) Step -1
If i = LBound(arr_column, 1) Then
If bFlag Then
ws.Cells(eFixed_Row.row_to_fill, arr_column(i)).Resize(UBound(arr_data, 1), 1).Value = _
Application.Index(arr_data, 0, i)
End If
Else
ws.Cells(eFixed_Row.row_to_fill, arr_column(i)).Resize(UBound(arr_data, 1), 1).Value = _
Application.Index(arr_data, 0, i)
End If
Next i
End Sub
Private Function GetArrIndices(ByVal ind As Long, ByVal fixed_num As Long) As Variant
Dim num_of_col As Long: num_of_col = ind - fixed_num
Dim arr() As Variant: ReDim arr(1 To num_of_col)
Dim n As Long: n = eFixed_Column.column_to_fill
Dim i As Long
For i = num_of_col To 1 Step -1
arr(i) = n
n = n - 1
Next i
GetArrIndices = arr
End Function
Private Function CombineArrays(ByVal arr1 As Variant, ByVal arr2 As Variant) As Variant
Dim tmp As String
tmp = Join(Array(Join(arr1, "-"), Join(arr2, "-")), "-")
Dim comb As Variant, el As Variant, i As Long
comb = Split(tmp, "-")
ReDim new_arr(1 To UBound(comb) + 1) As Long
For Each el In comb
i = i + 1
new_arr(i) = CLng(el)
Next el
CombineArrays = new_arr
End Function
Private Sub ConvertToValuesFast(ByRef rng As Range)
Dim arr As Variant
arr = rng.Value
rng.Value = arr
End Sub
Private Sub FillDictPostFast(ByVal arr As Variant, ByRef dict As Object, _
ByVal ind As Long, ByVal bFlag As Boolean, _
Optional ByVal bSum As Boolean = False)
Dim i As Long, bExists As Boolean
For i = LBound(arr, 1) To UBound(arr, 1)
If Len(arr(i, ind)) Then
If Not bSum Then
If bFlag Then
If dict.Exists(arr(i, 1)) Then
dict(arr(i, 1)) = dict(arr(i, 1)) & ", " & arr(i, ind)
Else
dict(arr(i, 1)) = arr(i, ind)
End If
Else
dict(arr(i, 1)) = arr(i, ind)
End If
Else
dict(arr(i, 1)) = dict(arr(i, 1)) + arr(i, ind)
End If
End If
If i Mod 1000 = 0 Then appOptimizer.DoEventsLight
Next i
End Sub
Private Sub DictToArrayFast(ByRef arr_data As Variant, ByVal arr_order As Variant, _
ByRef dict As Object, ByVal ind As Long)
Dim i As Long
For i = LBound(arr_order, 1) To UBound(arr_order, 1)
arr_data(i, ind) = dict(arr_order(i, 1))
Next i
End Sub
Private Sub RemovePostsFast(ByRef arr As Variant)
Const adrCell As String = "G1"
Dim rng As Range
Set rng = ThisWorkbook.Worksheets(1).Range(adrCell).CurrentRegion
Dim arr_clean As Variant
arr_clean = rng.Offset(1).Resize(rng.Rows.Count - 1).Value
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
Dim j As Long
For j = LBound(arr_clean, 1) To UBound(arr_clean, 1)
dict(arr_clean(j, 1)) = Empty
Next j
Dim i As Long
For i = LBound(arr, 1) To UBound(arr, 1)
If dict.Exists(arr(i, 2)) Then
arr(i, 2) = vbNullString
End If
Next i
End Sub
Private Sub ProcessFormulasAndChecks(ws As Worksheet, lastRow As Long)
' Оптимизированная обработка формул
With ws
' Обработка последней цены
Dim tmp_arr As Variant, counter As Long, tmp_value As Variant
With .Range(.Cells(eFixed_Row.row_to_fill, eFixed_Column.last_price), _
.Cells(lastRow, eFixed_Column.last_price))
tmp_arr = .Value
For counter = LBound(tmp_arr, 1) To UBound(tmp_arr, 1)
tmp_value = tmp_arr(counter, 1)
If Not IsEmpty(tmp_value) Then
If tmp_value <= 0 Then
tmp_arr(counter, 1) = Empty
End If
End If
Next counter
.Value = tmp_arr
Erase tmp_arr
End With
' Маржа
With .Range(.Cells(eFixed_Row.row_to_fill, eFixed_Column.margin), _
.Cells(lastRow, eFixed_Column.margin))
.Formula2R1C1Local = "=ЕСЛИ(ЕПУСТО(RC[-1]);"""";ЕСЛИОШИБКА((RC[-2]-RC[-1])/RC[-2];""""))"
.Calculate
.Value = .Value
End With
' Средняя цена
With .Range(.Cells(eFixed_Row.row_to_fill, eFixed_Column.avg_price), _
.Cells(lastRow, eFixed_Column.avg_price))
tmp_arr = .Value
For counter = LBound(tmp_arr, 1) To UBound(tmp_arr, 1)
tmp_value = tmp_arr(counter, 1)
If Not IsEmpty(tmp_value) Then
If tmp_value <= 0 Then
tmp_arr(counter, 1) = Empty
End If
End If
Next counter
.Value = tmp_arr
Erase tmp_arr
End With
' Средняя маржа
With .Range(.Cells(eFixed_Row.row_to_fill, eFixed_Column.avg_margin), _
.Cells(lastRow, eFixed_Column.avg_margin))
tmp_arr = .Value
For counter = LBound(tmp_arr, 1) To UBound(tmp_arr, 1)
tmp_value = tmp_arr(counter, 1)
If Not IsEmpty(tmp_value) Then
Select Case tmp_value
Case -10 To 10
Case Else
tmp_arr(counter, 1) = Empty
End Select
End If
Next counter
.Value = tmp_arr
Erase tmp_arr
End With
' GMROI
With .Range(.Cells(eFixed_Row.row_to_fill, eFixed_Column.gmroi), _
.Cells(lastRow, eFixed_Column.gmroi))
tmp_arr = .Value
For counter = LBound(tmp_arr, 1) To UBound(tmp_arr, 1)
tmp_value = tmp_arr(counter, 1)
If Not IsEmpty(tmp_value) Then
Select Case tmp_value
Case -10 To 10
Case Else
tmp_arr(counter, 1) = Empty
End Select
End If
Next counter
.Value = tmp_arr
Erase tmp_arr
End With
End With
End Sub
6. modDataLoadersFast - оптимизированные загрузчики данных
' modDataLoadersFast
Option Explicit
Private fileCache As New clsFileCache
Private appOptimizer As New clsAppOptimizer
Public Sub GetDataSalesFast(ByVal path As String, ByRef arr As Variant, ByVal sh_ind As Long)
Const strName As String = "продажи.xlsx"
Dim fullPath As String: fullPath = path & strName
If fileCache.FileExists(fullPath) Then
Dim wb As Workbook: Set wb = fileCache.GetWorkbook(fullPath)
Dim ws As Worksheet: Set ws = wb.Worksheets(sh_ind)
CleanSheetFast ws
Dim lastRow As Long, lastCol As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
Select Case sh_ind
Case 1
arr = ws.Range(ws.Cells(8, 1), ws.Cells(lastRow, lastCol - 4)).Value
Case 2
arr = ws.Range(ws.Cells(2, 1), ws.Cells(lastRow, lastCol)).Value
Case 3
ws.Range("B1", ws.Cells(1, lastCol - 4)).EntireColumn.Delete
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
arr = ws.Range(ws.Cells(8, 1), ws.Cells(lastRow, lastCol)).Value
End Select
Else
MsgBox "Файла не существует: " & fullPath, vbExclamation
End If
End Sub
Public Sub GetDataPriceFast(ByVal path As String, ByRef arr As Variant, _
ByVal strPrice As String, ByVal start_col As Long)
Const strName As String = "прайс.xlsx"
Dim fullPath As String: fullPath = path & strName
If fileCache.FileExists(fullPath) Then
Dim wb As Workbook: Set wb = fileCache.GetWorkbook(fullPath)
Dim ws As Worksheet: Set ws = wb.Worksheets(1)
CleanSheetFast ws
arr = TransformPriceFast(strPrice, ws, start_col)
Else
MsgBox "Файла не существует: " & fullPath, vbExclamation
End If
End Sub
Public Sub GetDataStructureFast(ByVal path As String, ByRef arr As Variant, _
ByVal strMarket As String, ByVal start_col As Long)
Const strName As String = "Структура.xlsx"
Dim fullPath As String: fullPath = path & strName
If fileCache.FileExists(fullPath) Then
Dim wb As Workbook: Set wb = fileCache.GetWorkbook(fullPath)
Dim ws As Worksheet: Set ws = wb.Worksheets(1)
CleanSheetFast ws
arr = TransformStructureFast(strMarket, ws, start_col)
Else
MsgBox "Файла не существует: " & fullPath, vbExclamation
End If
End Sub
Public Sub GetDataBarcodeFast(ByVal path As String, ByRef arr As Variant, ByVal start_col As Long)
Const strName As String = "штрихкод.xlsx"
Dim fullPath As String: fullPath = path & strName
If fileCache.FileExists(fullPath) Then
Dim wb As Workbook: Set wb = fileCache.GetWorkbook(fullPath)
Dim ws As Worksheet: Set ws = wb.Worksheets(1)
arr = ProcessBarcodeFast(ws, start_col)
Else
MsgBox "Файла не существует: " & fullPath, vbExclamation
End If
End Sub
Public Sub GetDataOstatkiFast(ByVal path As String, ByRef arr As Variant, ByVal start_col As Long)
Const strName As String = "остатки.xlsx"
Dim fullPath As String: fullPath = path & strName
If fileCache.FileExists(fullPath) Then
Dim wb As Workbook: Set wb = fileCache.GetWorkbook(fullPath)
Dim ws As Worksheet: Set ws = wb.Worksheets(1)
arr = TransformOstatkiFast(ws, start_col)
Else
MsgBox "Файла не существует: " & fullPath, vbExclamation
End If
End Sub
Private Function TransformPriceFast(ByVal strPrice As String, ByVal ws As Worksheet, _
ByVal start_col As Long) As Variant
Const Price_Name As String = "Вид цены"
Dim lastRow As Long, lastCol As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
' Находим столбец с ценой
Dim priceCol As Long, r As Long, c As Long
For c = 1 To lastCol
If ws.Cells(1, c).Value = Price_Name Then
priceCol = c
Exit For
End If
Next c
If priceCol = 0 Then Exit Function
' Фильтруем данные без AdvancedFilter
Dim sourceData As Variant
sourceData = ws.Range(ws.Cells(1, 1), ws.Cells(lastRow, lastCol)).Value
Dim resultList As Object
Set resultList = CreateObject("System.Collections.ArrayList")
For r = 2 To lastRow
If sourceData(r, priceCol) = strPrice Then
resultList.Add r
End If
Next r
If resultList.Count > 0 Then
ReDim result(1 To resultList.Count, 1 To lastCol - start_col)
Dim i As Long, j As Long
For i = 1 To resultList.Count
For j = start_col + 1 To lastCol
result(i, j - start_col) = sourceData(resultList(i - 1), j)
Next j
Next i
TransformPriceFast = result
End If
End Function
Private Function TransformStructureFast(ByVal strMarket As String, ByVal ws As Worksheet, _
ByVal start_col As Long) As Variant
Const Price_Name As String = "Привязка"
Dim lastRow As Long, lastCol As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
' Замена KHS на MLT
Dim c As Long
For c = 1 To lastCol
If ws.Cells(1, c).Value Like "*KHS*" Then
Dim r As Long
For r = 2 To lastRow
If ws.Cells(r, c).Value = "KHS" Then
ws.Cells(r, c).Value = "MLT"
End If
Next r
End If
Next c
' Находим столбец с привязкой
Dim bindCol As Long
For c = 1 To lastCol
If ws.Cells(1, c).Value = Price_Name Then
bindCol = c
Exit For
End If
Next c
If bindCol = 0 Then Exit Function
' Фильтруем данные
Dim sourceData As Variant
sourceData = ws.Range(ws.Cells(1, 1), ws.Cells(lastRow, lastCol)).Value
Dim resultList As Object
Set resultList = CreateObject("System.Collections.ArrayList")
For r = 2 To lastRow
If sourceData(r, bindCol) = strMarket Then
resultList.Add r
End If
Next r
If resultList.Count > 0 Then
ReDim result(1 To resultList.Count, 1 To lastCol - (start_col + 1))
Dim i As Long, j As Long
For i = 1 To resultList.Count
For j = start_col + 1 To lastCol - 1
result(i, j - start_col) = sourceData(resultList(i - 1), j)
Next j
Next i
TransformStructureFast = result
End If
End Function
Private Function ProcessBarcodeFast(ByVal ws As Worksheet, ByVal num_art As Long) As Variant
Dim lastRow As Long, lastCol As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
' Находим столбцы
Dim codeCol As Long, barcodeCol As Long
Dim c As Long
For c = 1 To lastCol
Dim header As String
header = CStr(ws.Cells(1, c).Value)
If num_art = 1 And InStr(1, header, "Код", vbTextCompare) > 0 Then
codeCol = c
ElseIf num_art = 2 And InStr(1, header, "Артикул", vbTextCompare) > 0 Then
codeCol = c
End If
If InStr(1, header, "Штрихкод", vbTextCompare) > 0 Then
barcodeCol = c
End If
Next c
If codeCol = 0 Or barcodeCol = 0 Then Exit Function
' Читаем данные
Dim data As Variant
data = ws.Range(ws.Cells(2, codeCol), ws.Cells(lastRow, barcodeCol)).Value
' Агрегируем через словарь
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = TextCompare
Dim i As Long
For i = 1 To UBound(data, 1)
Dim key As String: key = Trim(CStr(data(i, 1)))
Dim barcode As String: barcode = Trim(CStr(data(i, 2)))
If Len(key) > 0 Then
If Not dict.Exists(key) Then
dict.Add key, barcode
ElseIf Len(barcode) > 0 Then
dict(key) = dict(key) & ", " & barcode
End If
End If
Next i
' Конвертируем в массив
ReDim result(1 To dict.Count, 1 To 2)
i = 1
Dim k As Variant
For Each k In dict.Keys
result(i, 1) = k
result(i, 2) = dict(k)
i = i + 1
Next k
ProcessBarcodeFast = result
End Function
Private Function TransformOstatkiFast(ByVal ws As Worksheet, ByVal start_col As Long) As Variant
Const findHeader As String = "Номенклатура.Код"
CleanSheetFast ws
Dim lastRow As Long, lastCol As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
' Находим заголовок
Dim headerRow As Long
Dim r As Long, c As Long
For r = 1 To lastRow
For c = 1 To lastCol
If ws.Cells(r, c).Value = findHeader Then
headerRow = r
Exit For
End If
Next c
If headerRow > 0 Then Exit For
Next r
If headerRow = 0 Then Exit Function
' Удаляем строки выше заголовка
If headerRow > 1 Then
ws.Rows("1:" & (headerRow - 1)).Delete
lastRow = lastRow - (headerRow - 1)
End If
' Читаем данные
Dim data As Variant
data = ws.Range(ws.Cells(2, start_col + 1), ws.Cells(lastRow, lastCol)).Value
TransformOstatkiFast = data
End Function
Private Sub CleanSheetFast(ByVal sh As Worksheet)
Dim lastRow As Long, lastCol As Long
lastRow = sh.Cells(sh.Rows.Count, 1).End(xlUp).Row
lastCol = sh.Cells(1, sh.Columns.Count).End(xlToLeft).Column
' Быстрое удаление полностью пустых строк
Dim r As Long
For r = lastRow To 1 Step -1
If Application.CountA(sh.Rows(r)) = 0 Then
sh.Rows(r).Delete
End If
Next r
' Быстрое удаление полностью пустых столбцов
Dim c As Long
For c = lastCol To 1 Step -1
If Application.CountA(sh.Columns(c)) = 0 Then
sh.Columns(c).Delete
End If
Next c
End Sub
7. clsTimerFast - оптимизированный таймер
' clsTimerFast
Option Explicit
Private Declare PtrSafe Function QueryPerformanceCounter Lib "kernel32" (lpPerformanceCount As Currency) As Long
Private Declare PtrSafe Function QueryPerformanceFrequency Lib "kernel32" (lpFrequency As Currency) As Long
Private startTime As Currency
Private freq As Currency
Private timePoints As Object
Private pointNames As Object
Private Sub Class_Initialize()
QueryPerformanceFrequency freq
Set timePoints = CreateObject("Scripting.Dictionary")
Set pointNames = CreateObject("Scripting.Dictionary")
End Sub
Public Sub Start()
QueryPerformanceCounter startTime
timePoints.RemoveAll
pointNames.RemoveAll
AddPoint "start"
End Sub
Public Sub AddPoint(ByVal pointName As String)
Dim currentTime As Currency
QueryPerformanceCounter currentTime
timePoints.Add pointName, currentTime
pointNames.Add pointNames.Count, pointName
End Sub
Public Function GetTime(ByVal pointName As String) As Double
If timePoints.Exists(pointName) Then
GetTime = (timePoints(pointName) - startTime) / freq
End If
End Function
Public Function GetReport() As String
Dim report As String
Dim i As Long
Dim prevPoint As String, currPoint As String
For i = 1 To pointNames.Count - 1
prevPoint = pointNames(i - 1)
currPoint = pointNames(i)
report = report & currPoint & ": " & _
Format((timePoints(currPoint) - timePoints(prevPoint)) / freq, "0.00") & " сек." & vbCrLf
Next i
report = report & "Всего: " & _
Format((timePoints(pointNames(pointNames.Count - 1)) - startTime) / freq, "0.00") & " сек."
GetReport = report
End Function
8. modMainEntry - точка входа
' modMainEntry
Option Explicit
#If Win64 Then
Private Declare PtrSafe Function SetProcessAffinityMask Lib "kernel32" (ByVal hProcess As LongPtr, ByVal dwProcessAffinityMask As LongPtr) As LongPtr
Private Declare PtrSafe Function GetCurrentProcess Lib "kernel32" () As LongPtr
#Else
Private Declare Function SetProcessAffinityMask Lib "kernel32" (ByVal hProcess As Long, ByVal dwProcessAffinityMask As Long) As Long
Private Declare Function GetCurrentProcess Lib "kernel32" () As Long
#End If
Public Sub RunOptimized()
' Привязываем процесс к производительным ядрам (для ноутбуков с гибридной архитектурой)
#If Win64 Then
Dim hProcess As LongPtr
hProcess = GetCurrentProcess()
' Маска для первых 6 ядер (предполагаем, что это P-ядра)
SetProcessAffinityMask hProcess, &H3F ' 0011 1111 - ядра 0-5
#Else
Dim hProcess As Long
hProcess = GetCurrentProcess()
SetProcessAffinityMask hProcess, &HF ' 1111 - ядра 0-3
#End If
' Запускаем основной процесс
main_sub
' Сбрасываем маску привязки
#If Win64 Then
SetProcessAffinityMask hProcess, &HFFFFFFFF
#Else
SetProcessAffinityMask hProcess, &HFFFFFFFF
#End If
End Sub
|