Страницы: 1
RSS
Очистка памяти при выполнении макроса
 
Всем привет.

Столкнулся с проблемой нехватки памяти и решил, что я умнее всех и пытался очистить ее следующим образом: Set oResponse = Nothing. Но это не работает, оперативная память не освобождается, пока не произойдет выход из цикла. Пример кода:
Код
Dim oRequestList As Collection
Dim oRequest As ServerXMLHTTP60
Dim oResponse as Object

'создание асинхронных http-запросов и добавление их в oRequestList

For Each oRequest In oRequestList
    oRequest.waitForResponse
    Set oResponse = JsonConverter.ParseJson(oRequest.responseText)

    'обработка json ответа

    Set oResponse = Nothing
Next oRequest
Иногда json-ответ, преобразованный в структуру для VBA может занимать 400 и более Мб памяти, также количество запросов варьируется. Были случаи, когда суммарно Excel начинал "поедать" 8 Гб памяти, что может быть страшным для некоторых ПК. Строкой Set oResponse = Nothing я думал память, выделенная под json-ответ, высвобождается, но это не так. Она высвобождается автоматически, когда происходит выход из цикла, а хотелось бы поддерживать эти ~400 Мб занятой памяти на протяжении работы всего цикла. Это как-то возможно сделать?

Ну и на самом деле я все равно не верил, что это поможет, ведь каждую итерацию в oResponse присваивается ссылка на другой объект, что, по идее, должно уничтожать объект по предыдущей ссылке.
 
Какой версией класса JsonConverter Вы пользуетесь?
Лучше всего выложите файл с этим классом.
Владимир
 
Файл конвертера.
Не думаю, что проблема в нем, в ответе действительно приходит немалый объем данных, в json-формате может занимать ~40 Мб.
Но если Вы подскажете конвертер с какими-либо преимуществами перед этим, буду благодарен.
Изменено: Дмитрий Г - 09.12.2020 12:39:34 (Добавлен файл во вложение)
 
Цитата
Дмитрий Г написал:
Вы подскажете конвертер с какими-либо преимуществами
С этим на открытом (бесплатном) рынке дефицит. Tim Hall написал JsonConverter, в котором при парсинге для каждого объекта json ({...}) создается объект VBA (коллекция или динамический массив). Поскольку объектов Json обычно очень много, то возникают проблемы с быстродействием и с памятью.
Чтобы давать конкретные советы, нужно знать версию упомянутой программы (в разных версиях алгоритмы могут отличаться).
Владимир
 
Проверил. В этой (последней) версии JsonConverter также есть проблема с утечкой памяти, отмеченная в #1. Ваш код не виноват. :)
Память освобождается после завершения работы макросов VBA. Можно посоветовать разбить все задание на обработку данных на какие-то логические шаги и следующий шаг запускать с помощью метода Application.OnTime.
Владимир
 
Off
Цитата
Дмитрий Г написал:
Очистка памяти при выполнении макроса

Читаю тему и боюсь
По вопросам из тем форума, личку не читаю.
 
Вместо Set oResponse = Nothing попробуйте oResponse.RemoveAll: DoEvents.
На простом тесте утечки не обнаружил, приведите, пожалуйста, пример с утечкой.
Код
Sub Test_Json()
  Const N = 10000
  Dim i As Long, s As String
  Dim oResponse As Object ' Dictionary
  s = "{""a"":123,""b"":[1,2,3,4],""c"":{""d"":456}}"
  For i = 1 To N
    Set oResponse = JsonConverter.ParseJson(s)
    'oResponse.RemoveAll: DoEvents
    If i = N Then Stop
  Next
End Sub
Изменено: ZVI - 10.12.2020 02:37:31
 
ZVI, Владимир, приветствую! Как-то пару раз замечал, что при позднем связывании словарь иногда не очищается через .RemoveAll, зато стабильно очищается через Nothing. Очистка через .RemoveAll при раннем связывании проблем не вызывала  :idea:
Примера не будет, т.к. надо "ловить" — информация просто для осторожности  ;)
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Цитата
ZVI написал:
приведите, пожалуйста, пример
Здравствуйте, Владимир!

Я проверял на следующем (типичном, на мой взгляд) примере:
Код
Option Explicit

Sub TestJson2()
  Dim s As String, json As Object, i As Long, nMax As Long, t As Double
  ' Генерация строки json: массив с 2 объектами
  nMax = 100000
  ReDim arr(1 To nMax)
  For i = 1 To nMax
    arr(i) = "{""Key1"":1, ""Key2"":""Value2""}"
  Next i
  s = "[" & Join(arr, "," & vbLf) & "]"
  Erase arr

  Stop ' для замера памяти
  t = Timer
  For i = 1 To 10
    Set json = ParseJson(s)
  Next i
  Debug.Print Format(Timer - t, "0.00"), nMax
  Stop ' для замера памяти
End Sub

Время выполнения 40 сек, разница в памяти, отведенной Excel, между измерениями 100-150 МБ. Время и "утечка" растут пропорционально nMax.

Excel 2016 (32-) Win 10.
Изменено: sokol92 - 10.12.2020 13:58:10
Владимир
 
Добрый день, Владимир! Спасибо за пример!
В Вашем примере - коллекции. У них есть особеность - быстро набирать вес, а его сгон отдавать операционной системе, когда/если у нее найдется для этого время.
Для форсирования очистки коллекций можно использовать - Set json = New Collection
Код
Sub TestJson3()
  Dim s As String, i As Long, nMax As Long, t As Double
  Dim json As Object ' As Collection
  ' Генерация строки json: массив с 2 объектами
  nMax = 100000
  ReDim arr(1 To nMax)
  For i = 1 To nMax
    arr(i) = "{""Key1"":1, ""Key2"":""Value2""}"
  Next i
  s = "[" & Join(arr, "," & vbLf) & "]"
  Erase arr
  Stop ' для замера памяти
  t = Timer
  For i = 1 To 10
    Set json = ParseJson(s)
    If TypeName(json) = "Collection" Then Set json = New Collection
    Set json = Nothing
  Next i
  Debug.Print "Time="; Format(Timer - t, "0.00") ', nMax
  Stop ' для замера памяти
End Sub
Изменено: ZVI - 11.12.2020 14:11:47
 
Здравствуйте, Владимир! Спасибо за очень интересную информацию!

Действительно, в такой редакции макроса занятая память практически не растет в ходе итераций цикла. Весьма полезный "трюк". :)  
Изменено: sokol92 - 11.12.2020 14:17:02
Владимир
 
Здравствуйте все.

Я разгадал причину с помощью Ваших простых примеров. Спасибо Вам огромное!!!
Проблема крылась не в работе Excel, а в обработке объекта json.
Во вложении приложил файл с json строкой, которая приходит от API, но думаю это особо и не к чему.

Вот как примерно выглядит обработка ответа:
Код
Dim oRequestList As Collection
Dim oRequest As ServerXMLHTTP60
Dim oResponse as Object
Dim oItem As Object
Dim oRow As Object
 
'создание асинхронных http-запросов и добавление их в oRequestList
 
For Each oRequest In oRequestList
    oRequest.waitForResponse
    Set oResponse = JsonConverter.ParseJson(oRequest.responseText)
 
    'обработка json ответа
    For Each oItem In oResponse("items")
        For Each oRow In oItem("rows")
         ' обработка строки
      Next oRow
   Next oItem
    Set oResponse = Nothing
Next oRequest

Как писал sokol92 "Память освобождается после завершения работы макросов VBA.", действительно, память не освобождается после выхода из цикла, но, позволю себе поправить sokol92'а, память освобождается после завершения работы не макроса, а процедуры/функции, уничтожая все данные, связанные с этой процедурой/функцией и на которые нет ссылок в других местах кода. Моя же проблема заключалась в том, что в коде я не создавал ссылку на массив объектов в цикле, а отдавал это каким-то внутренним силам VBA, в результате чего своими силами я не мог очистить память Nothing'ом. Память не очищалась при помощи Set oResponse = Nothing, потому что была ссылка на коллекцию oResponse("items").

Если создать переменную для такой коллекции и передавать ее в качестве коллекции для перебора, а после цикла по oResponse("items") присвоить ей Nothing, то память очищается при каждой итерации.
Пример:
Код
Dim oRequestList As Collection
Dim oRequest As ServerXMLHTTP60
Dim oResponse as Object
Dim oItem As Object
Dim oRow As Object
Dim oItems As Object
Dim oRows As Object

'создание асинхронных http-запросов и добавление их в oRequestList

For Each oRequest In oRequestList
    oRequest.waitForResponse
    Set oResponse = JsonConverter.ParseJson(oRequest.responseText)
 
    'обработка json ответа
    Set oItems = oResponse("items")
    For Each oItem In oItems
        Set oRows = oItem("rows")
        For Each oRow In oRows
            ' обработка строки
        Next oRow
    Next oItem
    Set oRows = Nothing
    Set oItems = Nothing
    Set oResponse = Nothing
Next oRequest

Многие, наверное, скажут, что как такое можно было не знать и не понимать, но я не знал :)
 
Цитата
Дмитрий Г написал:
отдавал это каким-то внутренним силам VBA
Не думаю, что есть какие-то таинственные силы гемоглобина VBA. :)
Вы самостоятельно пришли к правильному выводу. Цитата из сообщения Владимира на форуме:

"Объекты, объявленные As Object, как правило, требуют принудительного  уничтожения в конце кода, чтобы освободить память:
Код
  Set o = Nothing   
Объекты же природные для Excel уничтожаются корректно им самим после завершения кода."

Добавлю, что освобождать память нужно в порядке, обратном для выделения (что и делается в #12).
Владимир
Страницы: 1
Наверх