14 мая 2014 г.

CURRENT OF vs поиск по ключу

Какое же утро без хорошего эксперимента. Берем встроенный сервер Firebird 2.5.3 и чистую БД:
Page size               8192 
ODS version             11.2 
Page buffers            75 
Database dialect        3 
Attributes              force write 
Создаем две идентичных по структуре таблицы:
create table t1 (i integer not null)
create table t2 (i integer not null)
Одинаково заполняем каждую из них миллионом случайных значений:
execute block
as
  declare variable i integer = 0;
  declare variable v integer = 0;
begin
  while (i < 1000000) do
  begin
    v = round(rand() * 2000000000);
    insert into t1 values (:v);
    insert into t2 values (:v);
    i = :i + 1;
  end
end
Для таблицы t1 создаем индекс:
create index t1x on t1 (i)
Делаем коммит транзакции и переподключение к базе данных. Организуем скан таблицы t1 и случайным образом удаляем четверть записей через поиск по индексированному полю:
execute block
as
  declare variable i integer;
begin
  for select i from t1 into :i
  do
  begin
    if (rand() < 0.25) then
      delete from t1 where i = :i;
  end
end
Статистика выполнения:
Execute       : 10 717.00 ms
Read          : 239 855
Writes        : 6 250
Fetches       : 4 264 681
Marks         : 250 286
IR            : 250 286
NIR           : 999 934
Deletes       : 250 286
Не совсем понятно почему NIR не миллион. Все остальное вполне логично. Коммит транзакции и еще раз удаляем четверть, теперь от оставшихся записей:
Execute       : 582 243.00 ms
Read          : 414 519
Writes        : 246 390
Fetches       : 5 712 020
Marks         : 944 206
IR            : 187 214
NIR           : 749 693 
Deletes       : 187 214
Expunges      : 250 286
Почти 10 минут! Обратите внимание на сумасшедшее количество Writes и Marks. Сборка мусора и перестроение индекса?

Далее, берем таблицу t2 и удаляем из нее четверть записей, но с помощью курсора и конструкции CURRENT OF:

execute block
as
  declare variable c cursor for
    (select i from t2);
  declare variable i integer;
begin
  open c;
  while (1=1) do
  begin
    fetch c into :i;
    if (row_count = 0) then
      leave;
    if (rand() < 0.25) then
      delete from t2 where current of c;
  end
  close c;
end
Статистика:
Execute       : 8 362.00 ms
Read          : 6 138
Writes        : 6 064
Fetches       : 2 762 582
Marks         : 250 100
NIR           : 1 000 000
Deletes       : 250 100
Обратите внимание, насколько меньше чтений по сравнению с удалением по индексированному полю. Выполнение на 20% быстрее за счет того, что не надо дергать индекс.

Коммит транзакции и еще раз удаляем четверть записей:

Execute       : 8 252.00 ms
Read          : 6 137
Writes        : 6 067
Fetches       : 3 587 315
Marks         : 693 789
NIR           : 749 900
Deletes       : 187 455
Expunges      : 250 100
Выполнился даже быстрее чем первый запрос, несмотря на сборку мусора, и в 70 (!!!) раз быстрее чем запрос с удалением по индексированному полю. Непонятно почему так велико значение Marks, ведь удаляется всего 187 455 записей.

Подсчитаем количество записей в первой таблице:

select count(*) from t1
Статистика:
Execute       : 493 697.00 ms
Read          : 181 339
Writes        : 181 084
Fetches       : 3 009 620
Marks         : 561 642
NIR           : 562 500
Expunges      : 187 214
Все ясно, сборка мусора напоролась на индекс. 8 минут на кофе. Теперь считаем во второй:
select count(*) from t2
Статистика:
Execute       : 6 443.00 ms
Read          : 6 137
Writes        : 6 064
Fetches       : 2 261 901
Marks         : 374 910
NIR           : 562 445
Expunges      : 187 455
Упс! Мы ожидали увидеть сравнимые цифры по времени выполнения для первой и второй таблицы, но разница составила 76 (!!!) раз. Все из-за наличия в первой таблице индекса, который при подсчете количества никак не используется, но катастрофически тормозит сборку мусора.

Попытаемся выяснить как влияет наличие индекса на выполнение операции CURRENT OF. Возвращаемся к исходной базе данных. Будем удалять записи из таблицы t1 с помощью конструкции CURRENT OF:

Execute       : 8 050.00 ms
Read          : 6 138
Writes        : 6 064
Fetches       : 2 765 717
Marks         : 251 145
NIR           : 1 000 000
Deletes       : 251 145
В одно время с удалением из таблицы, по которой нет индекса.

Второй цикл удаления:

Execute       : 688 480.00 ms
Read          : 240 628
Writes        : 240 341
Fetches       : 4 592 695
Marks         : 945 817
NIR           : 748 855
Deletes       : 186 248
Expunges      : 251 145
Самое большое время, которое нам пришлось наблюдать сегодня. Сборка мусора и индекс явно не дружат друг с другом.

Для оценки влияния сборки мусора повторим все операции с самого начала на исходной базе данных с флагом подключения no_garbage_collect. Первое удаление из таблицы t1 (поиск по индексированному полю):

Execute       : 12 418.00 ms
Read          : 239 735
Writes        : 6 231
Fetches       : 4 260 434
Marks         : 249 809
NIR           : 999 939
IR            : 249 809
Deletes       : 249 809
Странно, но NIR снова не миллион. Куда деваются около шестидесяти чтений непонятно.

Второе удаление:

Execute       : 9 563.00 ms
Read          : 181 842
Writes        : 6 189
Fetches       : 3 702 914
Marks         : 187 849
NIR           : 750 162
IR            : 187 849
Deletes       : 187 849
Записей стало меньше и время сканирования и удаления уменьшилось соответственно.

Для второй таблицы удаление через курсор. Первый проход:

Execute       : 8 112.00 ms
Read          : 6 138
Writes        : 6 064
Fetches       : 2 764 184
Marks         : 250 634
NIR           : 1 000 000
Deletes       : 250 634
Второй:
Execute       : 8 034.00 ms
Read          : 6 137
Writes        : 6 064
Fetches       : 2 574 131
Marks         : 187 283
NIR           : 749 366
Deletes       : 187 283
Обратите внимание, что цифры идентичны тем, которые мы имели при включенной сборке мусора. Т.е. нет индекса и сборка мусора не проблема.

Получаем количество записей для первой таблицы:

Execute       : 546.00 ms
Read          : 6 137
Writes        : 2
Fetches       : 2 012 281
Marks         : 0
NIR           : 562 342
Для второй:
Execute       : 531.00 ms
Read          : 6 137
Writes        : 2
Fetches       : 2 012 281
Marks         : 0
NIR           : 562 083
Выводы:

  • Нижесказанное применимо к частным случаям массовой обработки данных.
  • Если по таблице нет индексов, то включение/выключение сборки мусора практически не влияет на производительность.
  • Если по таблице созданы индексы, то сборка мусора способна радикально, в десятки и сотни раз, замедлить ход процесса.
  • В нашем примере CURRENT OF был на 20-25% быстрее чем поиск по индексированному полю.
  • CURRENT OF может применяться на таблицах, где индексов нет вообще. Совершенно очевидно, что удаление с поиском по неиндесированному полю выполнялось бы в нашем случае часами, если не сутками.
Так и осталось загадкой, почему количество неиндексированных чтений при скане таблицы с индексом дважды получилось меньше правильного по теории миллиона.

13 мая 2014 г.

Запуск RAD StudioXE6 в Минске

27 мая 2014 г. Минск. Отель Crowne Plaza, ул. Кирова, 13.
Приглашаем вас принять участие в мероприятиях, посвящённых запуску – RAD Studio XE6.

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

Вы узнаете, как с помощью новых возможностей RAD Studio XE6:

  • Улучшить свои Windows приложения с помощью новых стилей VCL, меню, компонентов для поддержки работы с датчиками. Расширить существующие Windows приложения для сопряжения с новыми мобильными приложениями;
  • Перевести ваши мобильные приложения на новый уровень с помощью интеграции с облачными BaaS сервисами, включая аутентификацию, Push – уведомлениямя и монетизацию приложений с использованием встроенных покупок и рекламы;
  • Разрабатывать Delphi и C++. приложения для Android и переносных гаджетов
Кроме этого вы сможете задать все ваши вопросы экспертам, поделиться впечатлениями о продуктах и узнать о планах по дальнейшему развитию портфеля решений компании. Ни один вопрос не останется без внимания!

Количество мест ограничено, регистрируйтесь заранее.

Регистрация и дополнительная информация http://delphitour.ru

10 мая 2014 г.

Книга "Автоматизированная обработка информации в бухгалтерском учете"

Неутомимые преподаватели БГЭУ Татьяна Владимировна Прохорова и Татьяна Геннадьевна Ускевич написали очередную книгу по автоматизации бухучета. Учебное пособие выпущено значительным тиражом в 1000 экземпляров и имеет стандартную для такого рода публикаций структуру. Методология организации и автоматизации учета рассматривается в разрезе участков: расчетно-финансовые операции, учет запасов, учет труда и т.д. Каждая глава начинается с общей, регламентной информации по соответствующему разделу бухгалтерского учета, после чего следует описание практического применения программных продуктов Гедымин, и БЭСТ-5. Завершается глава списком вопросов для самоконтроля и небольшим тестом.
Книга допущена Министерством образования в качестве учебного пособия для высших учебных заведений Беларуси. Объем 407 страниц. Продается в книжном киоске главного корпуса БГЭУ и в интернет-магазине Издательского центра.

8 мая 2014 г.

Обсуждения по SWI-Prolog из почтовых рассылок переведено на Google Groups.

5 мая 2014 г.

Устаревшие функции g_m_round и g_m_roundnn

Функции g_m_round, g_m_roundnn были добавлены в GUDF.DLL когда в FB отсутствовали встроенные аналоги и с их использованием могут возникнуть проблемы. В стандартных ПИ мы заменили эти функции на вызов встроенной ROUND. В частных решениях это несомненно стоит проделать.

G_M_ROUND

Как отмечено здесь при аргументе больше 2 млрд меняется знак. Причина в типе результата -- INTEGER. Исправить проблематично, так как надо менять и GUDF.DLL, и объявление функции в БД. Из своего опыта мы знаем насколько живучи старые версии DLL, которые способны возникать буквально из ниоткуда, пробираться на самые критичные сервера и проявляться в самые неожиданные моменты.

Замена тривиальна: G_M_ROUND(X) -> ROUND(X)

G_M_ROUNDNN

При использовании возможна потеря точности, так как результат имеет тип DOUBLE PRECISION.

Замена:

g_m_roundnn(X, Y)
->
ROUND(X, IIF((Y = 0) OR (Y = 1), 0, - LOG(10, ABS(Y))))

Такая сложная замена нужна только в существующем коде, чтобы не трогать определение и значение параметра разрядности. У нас задается 0.01, если необходимо округление до сотых, 0.1 -- до десятых, 10 -- до десятков и т.п. Во встроенной функции ROUND второй параметр это целое число. Положительные значения определяют количество десятичных знаков для округления, а отрицательные -- кратность оругленной суммы соответствующей степени 10. Напрмер, ROUND(8153, -3) вернет 8000.