| « Поставить закладку » « Сделать стартовой » | |||
|
|||
|
Статьи:: Базы данных :: Microsoft FoxPro :: Начало работы с Visual FoxPro :: Быстродействие программ на VFP
Быстродействие программ на VFP
Основная часть этой статьи была написана, когда автор пытался ускорить расчеты, выполнявшиеся около получаса. В результате время работы процедуры сократилось до нескольких минут :-)
Здесь вы можете скачать тексты демонстрационных программ. Важное замечаниеНаши пользователи хотят, чтобы программы выполнялись максимально быстро. Но часто бывает, что быстродействие может быть достигнуто в ущерб другим требованиям, например универсальности, модифицируемости или надежности кода. Далеко не всегда стоит добиваться максимальной производительности. Бывает, что код настолько усложняется, что вы рискуете потерять контроль над своей "супер-быстрой" программой. Так же не стоит отказываться от проверок входящих параметров. Сэкономленные секунды могут обернуться долгими часами, потраченными на ликвидацию последствий незначительной ошибки. Скорость загрузки программыЕсли пользователи жалуются на слишком длительную загрузку форм, вы можете предпринять следующие действия:
RushmoreВы уже знаете про оптимизацию Rushmore. Вот наиболее частые ошибки, при которых оптимизация перестает работать:
Навигационный и реляционный подходыВ FoxPro сосуществуют два подхода к обработке данных - навигационный и реляционный.Навигационные команды - это Locate, Seek, Set Relation, Set Order, GoTo. Основная реляционная команда - Select SQL. Скорость выполнения запросов SQL весьма высока, однако во многих случаях навигационный подход работает еще быстрее. Пример поиска максимального значения поля различными способами: *** поиск с помощью Select SQL Select MAX(Field1) as MaxField From Table1 Into Cursor _Temp ? _Temp.MaxField *** а так выполнится значительно быстрее (необходимо наличие индекса по полю Field1) Select Table1 Set Order To Field1 Go Bottom ? Field1 Оптимизация запросовЗапросы на FoxPro могут выполняться долго, если в них участвует больше трех-четырех таблиц. Речь идет именно о запросах к родным БД на dbf, а не о запросах, отправляемых на сервер. Мне не удавалось увеличить скорость путем разбиения большого запроса на несколько последовательно выполняемых подзапросов. С другой стороны, эффект от разбиения может зависеть от количества и распределения информации в связанных таблицах. Поэтому результаты могут отличаться. Можно выйти из положения, исключая из запроса справочные таблицы (например, ФИО сотрудников), и привязывая их к результату через Set Relation, смешивая таким образом "реляционный" и "навигационный" подходы. Использование Outer join снижает скорость выполнения запроса. Если нет прямой необходимости в этой опции, лучше переписать запрос без нее. Как вариант - если у вас есть строки, не имеющие соответствия в связанной таблице, и потому содержащие .NULL., добавьте в связанную таблицу строку с нулевым кодом, а в основной - замените пустые значения на этот нулевой код. Подзапросы вида Where Field1 in (Select ... ) или Where Exists (Select ... ) могут замедлить скорость выполнения запроса (хотя это зависит от многих параметров). Если такой запрос выполняется слишком медленно - попробуйте переписать его с использованием простого объединения. Для большего быстродействия располагайте первыми те условия, которые максимально сужают область дальнейшего поиска. Для анализа оптимальности SQL-запроса воспользуйтесь функцией Sys(3054). Имейте в виду, что если у таблиц нет индексов по Deleted(), эта команда будет показывать частичную оптимизацию, даже если фактически они вносят отрицательный вклад в скорость выполнения запроса. Этот эффект подробно описан Владимиром Максимовым. Нормализация БДПринципы нормализации требуют, чтобы все повторяющиеся значения были вынесены в отдельные таблицы. Однако это может замедлить выполнение программы. В некоторых случаях нормализацией можно пренебречь. Например, в справочнике клиентов есть поле "Город". Исходя из принципов нормализации, необходимо завести таблицу - справочник городов. Так как названия городов меняются нечасто, в таблице клиентов вместо кода города можно записывать его название. Это позволит уменьшить количество таблиц в запросах, но потребует аккуратности, если название города все-таки изменилось, или в нем была допущена ошибка. Другой вариант - предварительные расчеты. Например, с помощью триггеров можно заранее пересчитывать суммарные данные о продажах за день в момент добавления или корректировки нового счета. К примеру, пусть счета хранятся в таблице Invoices, а данные о продажах за день - в таблице DailySales. Тогда в триггеры таблицы Invoices нужно добавить примерно такой код: *** для триггера Insert Update DailySales Set TolalSum = TolalSum + Invoices.InvoiceSum Where DailySales.SalesDate = Invoices.InvoiceDate *** для триггера Update Update DailySales Set TolalSum = TolalSum + Invoices.InvoicesSum - OldVal(Invoices.InvoiceSum) Where DailySales.SalesDate = Invoices.InvoiceDate *** для триггера Delete Update DailySales Set TolalSum = TolalSum - Invoices.InvoiceSum Where DailySales.SalesDate = Invoices.InvoiceDate Естественно, сам код триггеров будет намного сложнее. Кроме приведенных строк там необходимы проверки, обработка ошибок и т.д. Если вы решите идти по такому пути, не забудьте написать процедуру синхронизации, если из-за сбоев предварительные расчеты станут отличаться от основных. АлгоритмКак правило, стройный и понятный алгоритм выполняется быстрее всего, хотя бывают исключения. С другой стороны, применяя "обходные маневры" вы можете потерять возможность исправить что либо в коде, когда условия изменятся. Вот несколько общих советов:
Скорость выполнения отдельных командСкорость выполнения отдельных команд приобретает критическое значение, когда они выполняются в цикле, и доли секунды, за которые выполняется команда, умножаются в тысячи, если не в сотни тысяч раз. Так же бывает важным максимально сократить время работы триггеров, чтобы не тормозить операции с таблицей. Вот некоторые приемы:
Вы можете самостоятельно сравнить скорость работы различных команд с помощью тестовых примеров. ПримерПредположим, вам необходимо всем клиентам, у которых есть заказ в предыдущем месяце, предоставить скидку 10% на будущие заказы. Данные о заказах хранятся в таблице Invoices, а информация о скидке - в таблице Clients. New_Discount = 0.10 && скидка, которую мы хотим предоставить
PrevDate = GoMonth(Date(), -1)
PrevMonth = Month(PrevDate) && предыдущий месяц
PrevYear = Year(PrevDate) && год, в котором был предыдущий месяц
Go top in Clients
Do while not EOF("Clients") && перебор записей в таблице клиентов
Go top in Invoices
Do while not EOF("Invoices") && перебор записей в таблице счетов
If Invoices.ClientID = Clients.ClientID AND ;
Year(Invoices.InvoiceDate) = PrevYear AND ;
Month(Invoices.InvoiceDate) = PrevMonth
Replace Clients.Discount with New_Discount
Exit
EndIf
Skip in Invoices
EndDo
Skip in Clients
EndDo
Приведенный код логически верен, но абсолютно не оптимизируем. Попробуем переписать этот код: Month_End = Date() - Day(Date()) && последний день предыдущего месяца
Month_Start = Month_End - Day(Month_End) + 1 && первый день предыдущего месяца
Select Clients
Scan
Select Invoices
Locate for ClientID = Clients.ClientID AND Between(InvoiceDate, Month_Start, Month_End)
If Found("Invoices")
Replace Clients.Discount with New_Discount
Endif
EndScan
Перебор записей таблицы Clients осуществляется командой Scan. Поиск в таблице Invoices может быть оптимизирован с помощью индексов по полям ClientID и InvoiceDate. Этот вариант будет выполняться существенно быстрее. Для реализации следующего варианта необходимо, чтобы у таблицы Invoices существовал особый индекс: Index on STR(ClientID, 10,0) + DTOS(InvoiceDate) tag MIXED ... ... ... Set Exact OFF LastMonth = LEFT(DTOS( GOMONTH(DATE(), -1) ), 6) && год и месяц в формате "YYYYMM" Select Clients Replace Clients.Discount with New_Discount For IndexSeek( STR(ClientID, 10,0) + m.LastMonth, .F., "Invoices", "MIXED" ) Поиск в таблице Invoices осуществляется по индексу, поэтому этот пример отработает еще быстрее. Но индекс весьма специфический, и не может быть применен для других вычислений. Кроме того, даже автору это кода этого потребуется немало времени, чтобы спустя полгода разобраться, что именно он делает :-) Решение с использованием команд SQL: Update Clients Set Discount = New_Discount Where Exists ; (Select * From Invoices Where ; Invoices.ClientID = Clients.ClientID AND ; Invoices.InvoiceDate BETWEEN Month_Start AND Month_End) Возможно, данный вариант не выиграет с точки зрения скорости по сравнению с предыдущим, но он гораздо понятнее. Если время выполнения запроса находится в разумных пределах, я бы остановился именно на нем. К сожалению, такой синтаксис команды SQL-Update возможен только в VFP9. Здесь вы можете скачать исходные тексты примеров. Как успокоить пользователя, пока выполняется программаЕсли программа в течение минуты не будет подавать признаков жизни, у пользователей возникает жгучее желание нажать Ctrl+Alt+Del, и прервать процесс. Вам необходимо позаботиться об информировании пользователя о состоянии дел. Вот варианты. Если расчеты многоступенчатые, перед каждым этапом выводите либо в статус-бар, либо в окно информацию Wait window "Этап 1: Проверка данных" nowait ... Wait window "Этап 5: Суммирование данных за год" nowait ... Wait window "Этап 10: Окончательное форматирование" nowait ... Wait clear Очень полезным может оказаться следующий прием. Запоминайте, сколько времени в секундах длился расчет в прошлый раз, и используйте эти данные для текущего расчета. В самом простом случае можно вывести сообщение Wait window "Расчет продлится примерно до " + Transform(DateTime() + Duration) nowaitСохранять эту информацию нужно локально, так как скорость вычислений зависит от возможностей данной рабочей станции. Если длительность расчета зависит от каких-то параметров, вы можете экстраполировать данные пропорционально им. Например, если пользователь указывает диапазон дат, разделите длительность процесса на количество дней в диапазоне, и получите длительность в расчете на один день. В следующий раз, когда пользователь введет другой диапазон, умножьте сохраненный результат на новое количество дней, и получите экстраполяцию, сколько будут длиться вычисления. Время, естественно, будет очень приблизительным, но это в любом случае поможет пользователю оценить, сколько чашечек кофе он успеет выпить :-) Для полноты картины можете вывести прогресс-бар. Однако имейте в виду, что любые украшательства требуют системных ресурсов. Так что не переусердствуйте. У многих программистов возникает желание написать свой прогресс-бар для запросов. Увы, единственный известный работающий пример - это стандартный "градусник", который выводится при выполнении долгого запроса, если перед этим была дана команда Set Talk on. Все другие решения приводили к многократной задержке в обработке данных. ТестированиеСоветы уважаемых гуру, и даже выдержки из HELP не дают гарантий, что именно ваш случай не будет редким, но исключением из общих правил. Скорость работы может зависеть от сервера и сети, от активности пользователей, от объема и частотного распределения данных. Самый хороший способ определить, насколько быстро будет выполняться программа - провести тесты, максимально приближенные к реальности. Использование тестов только кажется сложным. Нет ничего сверхъестественного в том, чтобы сгенерировать табличку, близкую по размерам к максимальному ожидаемому размеру реальной БД, и попробовать поработать. В генерации тестовых данных вам помогут функции RAND(), SYS(3) и SYS(2015), а так же любой осмысленный текст, например kladr - классификатор адресов России. Вы легко можете найти его в Интернете. Для большей достоверности постарайтесь, чтобы данные не были физически отсортированы по какому-либо полю. *** Генерация тестовой таблицы ***
=RAND(-1) && HELP рекомендует перед началом работы вызвать RAND() с отрицательным параметром
For nCount = 1 to BigNumber && число добавляемых записей
*** пример генерации случайного числа от 1 до 1000
nRandomValue = CEILING(1000 * RAND())
*** а так можно заполнять почтовые индексы и номера телефонов
cZipCode = PADR(CEILING(999999 * RAND()), 6, "0")
*** поиск случайной записи в связанной таблице
Goto CEILING(RECCOUNT("ChildTable") * RAND() ) in ChildTable
RelatedTableValue = ChildTable.Field1
*** пример генерации случайного адреса
Goto CEILING(RECCOUNT("Towns") * RAND() ) in Towns
Goto CEILING(RECCOUNT("Streets") * RAND() ) in Streets
cAddress = ALLTRIM(Towns.Name) + ", " + ALLTRIM(Streets.Name) +
", дом " + TRANSFORM( CEILING(200 * RAND()) )
*** Вставляем полученные данные в таблицу
Insert into BigTable (...) values (nRandomValue, cZipCode, cAddress, RelatedTableValue)
EndFor
Регулярное проведение тестов имеет еще одну приятную особенность. Пока выполняется тест, вы имеете полное право откинуться на спинку стула, и несколько минут наслаждаться полным бездельем ;-) БлагодарностиВ работе над статьей принимали участие члены
Фокс-Клуба Автор: Игорь Ильин Рубрика: Начало работы с Visual FoxPro
HTML 5: пять вещей вызывающих особый интер....
HTML 5 — это грядущее обновление гипертекстового языка разметки, основного способа создания контента для размещения его во всемирной паутине. Разработка HTML остановилась в 1999 году, на версии HTML 4.01 и с тех пор web-содержимое изменилось так, что текущие спецификации HTML перестали соответствовать сегодняшним требованиям. HTML 5 нацелен на то, чтобы увеличить функциональную совместимость HTML и соответствовать растущим требованиям разнообразного и смешанного web-контента. HTML 5 так же нацелен на устранение недостатков четвертой версии. В этой статье мы взглянем на 5 новых интересных вещей в HTML 5.
Подробнее... |
Рубрика: Html
| Добавлено: 22.12.2008
asp.net: ListView с разных сторон.
Элемент управления ListView был представлен в .Net Framework 3.5 как замена устаревшему GridView. Новый элемент имеет более расширенный функционал, чем его предшественник, но в тоже время лишен некоторых внутренних механизмов, что впрочем целиком следствие из расширенной универсальности ListView. Среди отличий ListView и GridView можно назвать и гибкую настройку разметки, что позволяет выводить данные не только в табличном виде, но и вообще в любом каком пожелает программист. Благодаря шаблонам ItemTemplate, EditItemTemplate, InsertItemTeplate можно настроить внешний вид при любом из состояний ListView: редактировании или выборе элемента.
Подробнее... |
Рубрика: .NET компоненты
| Добавлено: 22.12.2008
Создание кросс-таб отчета в Stimulsoft Rep....
Компания Стимулсофт предоставляет для разработчиков мощный набор инструментов для создания отчетов для Microsoft Visual Studio .Net 2005 и 2008; эти инструменты доступны как для Windows Forms, так и для Web Forms. Это генератор отчетов Stimulsoft Reports.Net. Генератор отчетов Stimulsoft Reports.Net имеет ряд особенностей: простая работа с дизайнером отчетов, полная поддержка экспорта в PDF, Word, Excel и многие другие форматы. Crystal Report и Microsoft Reporting Service – очень хорошие программные продукты для повседневной работы, но, если Вам необходимо создать отчеты с поддержкой кросс-табов, drill down, Ajax, штрих-кодов и возможностью подключения одновременно более одного источника данных, то Stimulsoft Reports.Net поможет Вам сэкономить массу времени. Также, данный генератор отчетов позволяет пользователям создавать свои собственные отчеты любой сложности. И все эти особенности делают Stimulsoft Reports.Net хорошим выбором в сфере программных продуктов для Business Intelligence.
Подробнее... |
Рубрика: .NET компоненты
| Добавлено: 22.12.2008
Остальные статьи: |
Цитата дня (все,добавить):
|
Realcoding.NET
© 2003-2008 |
Контакты |
Реклама на сайте
|