Асинхронная загрузка. Тест
Соорудил небольшой тест, сравнивающий производительность асинхронной и синхронной загрузки. Для разнообразия, в цикле выборки данных реализована модификация результирующего множества.
Конфигурация:
— Серверная машина: 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 провайдером в которым реализована полноценная поддержка асинхронной загрузки данных. Да и вообще, в целом — единственный и неповторимый 🙂