VBA Вставить формулы в таблицу очень медленно - PullRequest
0 голосов
/ 08 января 2020

У меня есть рабочая книга, которая при открытии превращает использованные ячейки в таблицу, а затем вводит различные формулы в каждый столбец. Я внедряю формулы, чтобы сохранить размер файла от взрыва, предварительно перетаскивая формулы дальше, чем нужно. VBA, которую я должен сделать, работает отлично, но невероятно медленно. Я уже выполнил проверки производительности и могу подтвердить, что медлительность вызвана внедрением формулы (в секунду может быть введено только 141 строка). Я уже выполнил базовые c расчеты / обновления экрана, связанные с оптимизацией. Что еще можно сделать, чтобы ускорить следующий код? (Обратите внимание, что я сократил до соответствующей части кода):

Sub OptimizeVBA(isOn As Boolean)
  Application.Calculation = IIf(isOn, xlCalculationManual, xlCalculationAutomatic)
  Application.EnableEvents = Not(isOn)
  Application.ScreenUpdating = Not(isOn)
  ' ActiveSheet.DisplayPageBreaks = Not(isOn)
End Sub

Private Sub Workbook_Open()
    OptimizeVBA True

    Dim ws As Worksheet
    Set ws = Worksheets("Book1")
    Dim tbl As ListObject
    Set tbl = ws.ListObjects("Table1")

    tbl.ListColumns("Dollar Share ").DataBodyRange.Formula = "=IFERROR(([@[Dollar Share  ]] - MEDIAN([[Dollar Share  ]])) / STDEV.P([[Dollar Share  ]]), """")"
    tbl.ListColumns("Unit Share ").DataBodyRange.Formula = "=IFERROR(([@[Unit Share  ]] - MEDIAN([[Unit Share  ]])) / STDEV.P([[Unit Share  ]]), """")"
    tbl.ListColumns("Units PSPW ").DataBodyRange.Formula = "=IFERROR(([@[Units PSPW  ]] - MEDIAN([[Units PSPW  ]])) / STDEV.P([[Units PSPW  ]]), """")"
    tbl.ListColumns("Dollar Growth ").DataBodyRange.Formula = "=IFERROR(IF(OR([@[Dollar Growth  ]] = """", [@[Dollars, Yago]] < New_Item_Floor), """", ([@[Dollar Growth  ]] - MEDIAN([[Dollar Growth  ]])) / STDEV.P([[Dollar Growth  ]])), """")"
    tbl.ListColumns("Unit Growth ").DataBodyRange.Formula = "=IFERROR(IF(OR([@[Unit Growth  ]] = """", [@[Dollars, Yago]] < New_Item_Floor), """", ([@[Unit Growth  ]] - MEDIAN([[Unit Growth  ]])) / STDEV.P([[Unit Growth  ]])), """")"
    tbl.ListColumns("Comp Avg % ACV ").DataBodyRange.Formula = "=IFERROR(([@[Comp Avg % ACV  ]] - MEDIAN([[Comp Avg % ACV  ]])) / STDEV.P([[Comp Avg % ACV  ]]), """")"

    OptimizeVBA False
End Sub

Мне известно, что взаимодействия между объектами Excel и другими объектами Excel являются дорогостоящими, но, учитывая, что приведенные выше формулы отличаются, я не смог бы • найти простой способ уменьшить количество отдельных взаимодействий для каждого столбца.

Примечания:

  • На самом деле инъекций больше, чем показано выше, с различными формулами примерно одинаковой длины строки.
  • В среднем tbl будет иметь 16 тыс. Строк.

Заранее спасибо за помощь!

Ответы [ 3 ]

2 голосов
/ 09 января 2020

Одним из источников замедления может быть использование функции MEDIAN - это более дорого / медленно для вычисления, чем AVERAGE.

В быстром и грязном тестировании моего оборудования с использованием строк по 16 КБ фиктивной даты и с Расчеты, События и Обновление экрана все включено (без ускорений), ваша формула

=IFERROR(([@[Dollar Share  ]] - MEDIAN([[Dollar Share  ]])) / STDEV.P([[Dollar Share  ]]), "")

потребовалось 5.4 seconds для выполнения, но когда AVERAGE используется вместо MEDIAN

=IFERROR(([@[Dollar Share  ]] - AVERAGE([[Dollar Share  ]])) / STDEV.P([[Dollar Share  ]]), "")

он работал в 0.8 seconds, ускорение ~ 6,6X.

Проблема усугубляется тем, что одни и те же MEDIAN и STDEV.P пересчитываются (расточительно) 16K раз (по одному разу для каждой строки таблицы). Похоже, что механизм расчета Excel оптимизирует для этого. Вы должны быть в состоянии ускорить свои таблицы, вычисляя эти значения ТОЛЬКО ОДИН РАЗ и затем повторно используя.

Один из способов сделать это - добавить строку Totals в таблицу и использовать MEDIAN в качестве общей функции. Тогда, если ваш код:

Sheet1.ListObjects("Table1").ListColumns("Dollar Share ").DataBodyRange.Formula = "=IFERROR(([@[Dollar Share  ]] - Table1[[#Totals],[Dollar Share  ]]) / STDEV.P([[Dollar Share  ]]), """")"

, время выполнения сокращается до 0.52 seconds, что в 10 раз лучше. Вы также можете иметь вспомогательные ячейки на рабочем листе для хранения всех значений MEDIAN и STDEV.P; это должно дать еще большее ускорение.

Надеюсь, это поможет.

2 голосов
/ 09 января 2020

Я предоставлю вам свои тесты здесь, так как это слишком много кода для комментариев.

Я изменил ваш код для прямой вставки формул. Я проверил как строки (из-за отсутствия настройки вашей таблицы), и это в основном сразу. Таким образом, должно быть какое-то связанное с таблицей махинации. Пожалуйста, попробуйте в вашей таблице. Вам просто нужно настроить диапазоны / рабочий лист в соответствии с вашими потребностями:

Private Sub Workbook_Open()
    OptimizeVBA True

    Dim ws As Worksheet
    Set ws = ActiveSheet


    ws.Range("A2:A200000").Formula = "=IFERROR(([@[Dollar Share  ]] - MEDIAN([[Dollar Share  ]])) / STDEV.P([[Dollar Share  ]]), """")"
    ws.Range("B2:B200000").Formula = "=IFERROR(([@[Unit Share  ]] - MEDIAN([[Unit Share  ]])) / STDEV.P([[Unit Share  ]]), """")"
    ws.Range("C2:C200000").Formula = "=IFERROR(([@[Units PSPW  ]] - MEDIAN([[Units PSPW  ]])) / STDEV.P([[Units PSPW  ]]), """")"
    ws.Range("D2:D200000").Formula = "=IFERROR(IF(OR([@[Dollar Growth  ]] = """", [@[Dollars, Yago]] < New_Item_Floor), """", ([@[Dollar Growth  ]] - MEDIAN([[Dollar Growth  ]])) / STDEV.P([[Dollar Growth  ]])), """")"
    ws.Range("E2:E200000").Formula = "=IFERROR(IF(OR([@[Unit Growth  ]] = """", [@[Dollars, Yago]] < New_Item_Floor), """", ([@[Unit Growth  ]] - MEDIAN([[Unit Growth  ]])) / STDEV.P([[Unit Growth  ]])), """")"
    ws.Range("F2:F200000").Formula = "=IFERROR(([@[Comp Avg % ACV  ]] - MEDIAN([[Comp Avg % ACV  ]])) / STDEV.P([[Comp Avg % ACV  ]]), """")"

    OptimizeVBA False
End Sub

Ваш вопрос из комментариев: как вы можете использовать автозаполнение для такой задачи:

Sub autof()
    Cells(1, 1).Value = 1
    Cells(1, 2).Value = 2
    Range(Cells(1, 1), Cells(1, 2)).AutoFill Range(Cells(1, 1), Cells(10, 2)), xlFillCopy
End Sub
0 голосов
/ 09 января 2020

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

  Sub ExtendFormula()
  .
  .
  Dim Limit As Long 'Instead of Integer : Thanks to BigBen for it's recommendation
  Application.ScreenUpdating = False 
  Limit = Range("A" & Rows.Count).End(xlUp).Row 
  Range("B5").Formula = "=A5+2*B5" 
  Range("B5").AutoFill Destination:=Range("B5:B" & Limit)
  .
  .
  End Sub

Надеюсь, что может помочь вам

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