Доброго дня. К сути. Берем чистый лист, простую функцию, допустим
Код
Function tst(a)
tst = a
End Function
Заполняем ей 15000 ячеек в 1 столбце(=tst(A1+1)). При вводе в первую ячейку происходит пересчет всех остальных. При ручном измненеии происходит значительная пауза, если менять программно, пересчет проихсодит очень быстро. Собственно говоря, и дело видимо не в пересчете, как сути, вопро в чем? Если кто-то решит проверить, не советую делать более 20 тыс. ячеек, можно словить мертвецкий зависон, даже Ctrl+Shift+Del не помогает
у меня быстро, 20.000 строк с функцией, которую вы написали - секунд 5. См. файл. Файл с 20.000 строк 400кб, это больше, чем разрешено выкладывать, поэтому оставил 10 строк - продлите до 20.000 сами
20 тыс. указал, из гуманных целей, сам я словил зависон на 100 к. 5 сек. это долго, если поменять значение из vba - 0,296875 для 30000, у вас может быть меньше.
testuser, вызов UDF всегда более затратен по ресурсам, и чем больше тем растет проблема, как снежный ком. Могу предположить что зависимость для пересчета строится тоже с определенными ограничениями по объему, что может также влиять на пересчет после определенного количества зависимых ячеек на листе и где-то может быть резкое снижение производительности.
но все равно напрашивается вопрос, о той тайной связи файла с примером и тем что в тексте сообщения!
написал: где-то может быть резкое снижение производительности.
Вот это "где-то" оно как-то связано с механизмом заполнения ячейки " в ручную". Пишу значение (или ctrl + V) нажимаю Enter и,, происходит что-то страшное.. Ели сделать так Range("A2") = 2 мгновенный пересчет (200 тыс. за 3 сек.) с отображением результатов!
Цитата
написал: той тайной связи файла с примером и тем что в тексте сообщения!
Это со всеми Excel файлами у вас так или с каким-то одним? P.s. в вашем файле сплошной мусор. В тексте сообщения у вас одна функция, в файле куча непонятных и ещё в разных модулях. Скачайте свой файл из этой темы и посмотрите код и код в модулях листов и книги. В следующий раз поменьше мусора добавляйте в свой файл-пример
В файле 1 лист с закомментированными событиями, 1 модуль с несколькими процедурками и 2 функции. Вторая функция, которая используется также простейшая, разницы в скорости между ними особой нет, поэтому оставил ее в примере. Критику не засчитываю)
Код
Public oldvals As New Dictionary
Function tst(a, id)
If a <> oldvals(id) Then
oldvals(id) = a
End If
tst = a
End Function
На что бы еще хоте обратить внимание, этот адский пересчет происходит не только при изменении связанной с функциями ячейки, а любой. Говоря о практической части сего разговора можно сказать так, если у вас (гипотетических читателей сего топика) случайно окажется несколько десятков тысяч связанных vba-шных udf-ов (возможно и со стандартными также справедливо) в одной книге, то любое изменение вновь открытого файла неизежно будет приводить к ужасному и мерзкому зависанию. В то же время если вносить изменение макросом, такого эффекта не происходит, хотя пересчет меж тем работает. Какое решение могло бы помочь в данной ситуации - перехват любых изменений на листе макросом. Допустим, выделяем ячейку и в ней образуется поле для ввода (кокой-нибудь текст-бокс или элемент списка допустим) вводим значение в него а дальше макрос переправляет его в ячейку.
Критика заключается в том что с трудом понятен кейс. У Вас что-то крутится в голове, но другие об этом не знают.
И так действительно при множестве вызовов UDF зависимых от результата их выполнения при смене родительского значения, при этом тоже происходит и при "протягивании", при этом именно руками
происходит мгновенно, . Пересчет выполняется тоже время, чего не происходит если типа вставить формулу. Причины скорее всего в механизмах пересчета, которые скрыты от пользователя.
Интересная тема, спасибо автору и коллегам! У меня для 100 000 строк при ручном внесении значения ячейки тоже пересчет занимает существенное время. Если убрать птичку с параметра Excel Дополнительно / Формулы / Включить многопоточные вычисления (или
), то пересчет производится быстро. Возможно, дело в этом. Когда мы меняем ячейку макросом, то следует учитывать, что макросы работают в однопоточном режиме.
Данный пример является "экстремальным", поскольку имеет длинную цепочку (граф) вычисления ячеек (см. \xl\calcChain.xml в структуре файла .xlsm).
sokol92, Владимир, я всегда говорил, что чем больше потоков, тем больще ресурса тратится на то, чтоб распределить по ним, компенсируя этим ускорения от многопоточности, но чтоб замедляло ......
sokol92, Приветствую. то есть разработчик сознательно признал что они конкретно что-то упустили и упустили давно и надолго? Мдаааа. Слова заканчиваются.
Многопоточность вычислений - одна из самых сложных тем в программировании. В данном конкретном случае сразу "видно", что дерево зависимости формул состоит из одной длинной цепи и "лишние" процессоры будут только вредить (это не Ваше "фирменное" произведение матриц, в котором можно независимо умножать каждую строку на каждый столбец).
sokol92, Таким образом мы выходим на то что многопоточность отключается на время выполнения макроса, так как те же изменения сделанные кодом, а не руками не приводят к таким тормозам. Ну к несчастью могу только взгрустнуть, если это так. Типа одна не успевает за другим. Узнаю даже свою работу. Там та же фигня.
Если мы в вышеуказанном примере заменим UDF-функцию на, к примеру, ABS, то многопоточность не мешает (и не помогает). Нашел у разработчика соответствующие рекомендации:
Избегайте однопоточных функций. Большинство "родных" функций Excel хорошо работают с многопоточными вычислениями. Однако по возможности избегайте использования следующих однопоточных функций: Пользовательские функции VBA и автоматизации (UDF), но UDF на основе XLL могут быть многопоточными. ...
Иными словами, если Вы используете UDF-функции вместе с многопоточными вычислениями, то, как говорится, вас предупреждали...
sokol92, Владимир, думаю что эти рекомендации относятся к тому что многопоточные по скорости выигрывают, но это не объясняет, почему при многопоточности приложение просто "блуждает в трех соснах".
В моем случае не помогает, т.е. нет заметной разницы. Там еть еще "многопотоковая обработка", тоже не влияет.
Цитата
Если мы в вышеуказанном примере заменим UDF-функцию на, к примеру, ABS, то многопоточность не мешает (и не помогает).Нашел у разработчика соответствующие рекомендации :Избегайте однопоточных функций.Большинство "родных" функций Excel хорошо работают с многопоточными вычислениями. Однако по возможности избегайте использования следующих однопоточных функций: Пользовательские функции VBA и автоматизации (UDF), но UDF на основе XLL могут быть многопоточными.
Спасибо, познавательно. Аналогично, =СУММ(A1; 1) работатет работает "штатно"
Цитата
написал: фирменное" произведение матриц, в котором можно независимо умножать каждую строку на каждый столбец
ZVI, Добрый день, Владимир. Потестировал у себя. Разницы нет, всё быстро. Но где-то пару недель назад у меня была ситуация, что код при открытом редакторе VBE выполнялся очень долго, а с закрытым быстро. Я удивился. Но так и не понял, почему так происходит
Спасибо (Павел?), у меня разница приличная. Можно ещё сохранить файл, закрыть Excel, в затем открыть в Excel и проверить, не открывая VBE. Но, похоже, что эффект зависит от железа.
ZVI, Потестировал в новом открытом файле с закрытым VBE и открытым, результат в среднем 0,2900391. Потом сохранил книгу, закрыл, открыл, так же запускал с закрытом VBE и открытым. Плюс/минус те же 0,2900391 (иногда 0,28) P.S. отличается только самый первый запуск с закрытым или открытым VBE не играет роли - 0,22. Второй и последующие запуски снова 0,29. У меня ноутбук, Excel 2021 (64bit)
Добрый день, Владимир, Павел, коллеги! В теме речь идет о двух разных экспериментах.
А. Занесение формулы, содержащей ссылку на пользовательскую функцию, в ячейки A2:A100000 (сообщение #19).
Б. Стартуем Excel, открываем книгу, ранее созданную и сохраненную в эксперименте A. Меняем ячейку A1 вручную и макросом и сравниваем время перерасчета листа (сообщение #10).
Перепроверил результаты эксперимента Б. Владимир прав, результат зависит от того, открыто ли окно VBE (многопоточность реабилитирована). Приношу извинения на невольную дезинформацию. Разница по времени, если заносить значение A1 через интерфейс пользователя: при открытом окне VBE перерасчет занимает 65 секунд, при закрытом - менее 2 секунд. Макрос по изменению значения ячейки A1 выполняется за доли секунды, независимо от того, открыто ли окно VBE. Этот эффект, похоже, касается только формул с использованием UDF-функций.
При ручной замене, пересчет идет в 15 раз дольше, чем программной, но при открытом окне VBA-редактора добавляется затуп, между пересчетом и событием Calculate, который еще удваивает время выполнения.
Номер ф-ции
Программная замена значения
Ручная замена 1
Ручная замена 2
2/1
3/1
3/2
2000
0,015625
0,234375
0,28125
15
18
1,2
4000
0,03125
0,453125
0,515625
14,5
16,5
1,137931
6000
0,046875
0,6875
0,75
14,6666667
16
1,090909
8000
0,0625
0,90625
1
14,5
16
1,103448
10000
0,078125
1,125
1,234375
14,4
15,8
1,097222
12000
0,09375
1,34375
1,46875
14,3333333
15,666667
1,093023
14000
0,109375
1,578125
1,703125
14,4285714
15,571429
1,079208
16000
0,125
1,796875
1,953125
14,375
15,625
1,086957
18000
0,140625
2,015625
2,1875
14,3333333
15,555556
1,085271
20000
0,15625
2,25
2,421875
14,4
15,5
1,076389
WS Calculate
0,15625
2,34375
6,28125
15
40,2
2,68
Скрытый текст
Код
Public oldvals As New Dictionary
Public t!
Function tst(a, id)
If t = 0 Then t = Timer
If a <> oldvals(id) Then
oldvals(id) = a
End If
tst = a
If (id Mod 2000) = 0 Then Debug.Print id; Timer - t!
End Function
Sub ПрогЗамена()
Range("A1") = 1
End Sub
Private Sub Worksheet_Calculate()
Debug.Print "Worksheet_Calculate"
If t Then Debug.Print Timer - t: t = 0
Debug.Print
End Sub
Кстати, первый вариант (с программной заменой) даже сопоставим по времени с простым перебором того же диапазона в цилке с последовательным вызовом udf.
Sub ПрограммныйПересчет()
Dim rng As Range, cl As Range, i&, ar(1 To 20000)
Set rng = Range("A1:A20000")
' For Each cl In rng
' i = i + 1
' ar(i) = tst(cl + 1, i)
' Next
With rng
For i = 1 To 20000
ar(i) = tst(.Cells(i) + 1, i)
Next
End With
Debug.Print "ПрограммныйПересчет"
Debug.Print Timer - t
Debug.Print
t = 0
End Sub