Асинхронная загрузка. Тест
Соорудил небольшой тест, сравнивающий производительность асинхронной и синхронной загрузки. Для разнообразия, в цикле выборки данных реализована модификация результирующего множества.
Конфигурация:
— Серверная машина: Q6600, 8GB, RAID0 из 4HDD, FB 2.5.4 SuperClassic
— Клиентская машина: Core i7, 16GB, SSD.
— Гигабитная сеть.
Тестировался 64-битный IBProvider.
Сразу озвучу результат — наблюдались устойчивые тридцать процентов ускорения работы.
Тест написан на VBScript и работает с провайдером через ADODB.
Выполняется загрузка ~2млн записей из таблицы (LOG_TABLE) с протоколом действий пользователей. В таблице есть текстовый блоб (MSG_LARGE_TEXT), который мы и будет модифицировать.
- Множество открывается с параметрами adOpenKeyset, adLockPessimistic.
- Запрещается отложенная загрузка блоб-данных — deferred_data=0
- Размер памяти под кэширование определен равным 10MB
- Указываем, что модификации не надо передавать обратно на сервер — disconnected_rowset=true
Асинхронная загрузка включается/выключается через свойство "asynch_fetch". Нулевое значение выключает асинхронную загрузку, единица — включает.
Исходный код теста:
call proc("AsyncFetch", 1) call proc("SyncFetch", 0) MsgBox "Stop" call wscript.quit(0) '------------------------------------------------------------------------- sub proc(testSign, _ rs_prop__async_fetch) MsgBox "Press For Start" call wscript.echo("------------------- "&testSign) dim cn set cn=createobject("ADODB.Connection") cn.Provider="LCPI.IBProvider.3" call cn.Open("location=home2:d:\database\re48120.gdb","gamer","vermut") call cn.BeginTrans() dim rs set rs=createobject("ADODB.Recordset") rs.ActiveConnection=cn rs.Properties("Memory Usage")=10*1024 '10MB rs.Properties("deferred_data").Value=0 rs.Properties("disconnected_rowset").Value=true rs.Properties("asynch_fetch").Value=rs_prop__async_fetch ' adOpenKeyset, adLockPessimistic call rs.Open("select * from LOG_TABLE where ID<2000000",,1,2) dim v,v2 dim nRecs,nUpdates nRecs=0 nUpdates=0 dim startTime startTime=Now() wscript.Echo("Start time: "&startTime) while(not rs.eof) nRecs=nRecs+1 v=rs("MSG_LARGE_TEXT").Value if(not IsNull(v))then nUpdates=nUpdates+1 call rs.Update("MSG_LARGE_TEXT",ucase(v)) end if call rs.MoveNext() if(Not IsNull(v))then call rs.MovePrevious() v2=rs("MSG_LARGE_TEXT").Value if(v2<>ucase(v))then call err.raise(-1,,"ERROR!") end if call rs.MoveNext() end if wend dim stopTime stopTime=Now() wscript.Echo("Stop time : "&stopTime) wscript.Echo("Duration : "&DateDiff("s",startTime,stopTime)&" sec(s)") wscript.Echo("Records : "&nRecs) wscript.Echo("Updates : "&nUpdates) end sub
Вывод:
------------------- AsyncFetch Start time: 15.05.2015 11:50:29 Stop time : 15.05.2015 11:51:47 Duration : 78 sec(s) Records : 1999927 Updates : 12326 ------------------- SyncFetch Start time: 15.05.2015 11:51:57 Stop time : 15.05.2015 11:53:42 Duration : 105 sec(s) Records : 1999927 Updates : 12326
На следующей картинке отображена загрузка ресурсов клиентского компьютера в процессе работы теста. Первый «горб» относится к асинхронной загрузка, второй — к синхронной.
Асинхронная загрузка данных представляет собой выделенный поток, который выбирает записи из результирующего курсора и сохраняет их в локальное хранилище (временный файл). Клиент, когда запрашивает следующую запись, получает данные из локального хранилища.
Модификация колонки MSG_LARGE_TEXT является дополнительным «шумом», провоцирующим запуск фоновых операций уборки мусора в локальном хранилище.
Все операции с локальным хранилищем пропускаются через кэш (мы указывали его максимальный размер равным 10MB), поддерживающий многопоточный доступ.
Список потоков, в процессе работы асинхронной загрузки выглядел так:
Поток с идентификатором 25536 как раз и выполняет асинхронную загрузку. 30200 — это основной поток процесса, который выполняет VB-код. Остальные активные потоки — это служебные потоки, поток фоновой выгрузки данных во временный файл, потоки сборщика мусора.
В случае синхронной загрузки данных, картинка с потоками будет практически идентичной. Отсутствует только фоновая загрузка записей — её осуществляет основной поток процесса:
В данном тесте мы грузили данные в локальное хранилище с поддержкой произвольного доступа. Можно еще (асинхронно) загружать данные в режиме «Forward-Only». В этом случае, если клиент будет оперативно выбирать данные из локального кэша, то можно избежать создание временного файла.
Параметры локального хранилища можно узнать, прочитав значения свойств результирующего множества — IBP_RS_INFO: Result Storage Size и IBP_RS_INFO: Using File Storage.
Как-то так. Вот.
Напоследок замечу, что IBProvider (судя по всему) является единственным OLEDB провайдером в которым реализована полноценная поддержка асинхронной загрузки данных. Да и вообще, в целом — единственный и неповторимый 🙂