Russian version
English version
ОБ АЛЬЯНСЕ | НАШИ УСЛУГИ | КАТАЛОГ РЕШЕНИЙ | ИНФОРМАЦИОННЫЙ ЦЕНТР | СТАНЬТЕ СПОНСОРАМИ SILICON TAIGA | ISDEF | КНИГИ И CD | ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ | УПРАВЛЕНИЕ КАЧЕСТВОМ | РОССИЙСКИЕ ТЕХНОЛОГИИ | НАНОТЕХНОЛОГИИ | ЮРИДИЧЕСКАЯ ПОДДЕРЖКА | АНАЛИТИКА | КАРТА САЙТА | КОНТАКТЫ
 
Программное обеспечение
 
Для зарегистрированных пользователей
 
РАССЫЛКИ НОВОСТЕЙ
IT-Новости
Новости компаний
Российские технологии
Новости ВПК
Нанотехнологии
 
Поиск по статьям
 
RSS-лента
Подписаться
Средства разработки

Оптимизация программы на VB6

Существует мнение, что VB - редкостный тормоз. Я хочу опровергнуть это мнение. Произведем сравнение VB6 и Delphi на вычисление факториала.

Тест 1. Целочисленная арифметика

В тесте производится 10 000 000-кратное вычисление факториала 12-ти с помощью программы на Delphi и на VB. Причем для последнего произведено несколько тестов: для неоткомпилированной программы (F5), и для компилированной с различными оптимизациями.

Для начала приведу тексты программ.

VB:

Проект: форма Form1 с кнопкой Command1, и модуль Module1

Form1:

Option Explicit

Private Sub Command1_Click()

Dim i As Long

Dim t As Long

Dim tmr As Long

tmr = GetTickCount

For i = 0 To 10000000

    t = Factorial(12)

Next i

MsgBox GetTickCount - tmr

End Sub

Module1:

Public Declare Function GetTickCount Lib "kernel32" () As Long

Option Explicit

Public Function Factorial(ByVal x As Long) As Long

Dim i As Long

Dim t As Long

t = 1

For i = 2 To x

    t = t * i

Next i

Factorial = t

End Function

Delphi:

Проект: Форма, на ней Button1: TButton.

Код формы (начало и конец опущены):

function Factorial(x:integer):integer;

var

  i:integer;

  t:integer;

begin

  t:=1;

  for i:=2 to x do t:=t*i;

  factorial:=t;

end;

procedure TForm1.Button1Click(Sender: TObject);

var

  tmr:integer;

  i:integer;

  t:integer;

begin

  tmr:=gettickcount;

  for i:=0 to 10000000 do begin

    t:=Factorial(12);

  end;

  messagedlg(inttostr(gettickcount-tmr),mtwarning,[mbok],0);

end;

И вот что вышло в результате тестов:

Обозначения:

Все, что не Delphi относится к VB.

Debug - неоткомпилированная. Т.е. просто F5.

P-Code: программа, откомпилированная в псевдо-код. О том, что это такое, будет сказано дальше.

Все остальное - Native Code.

Применено следующее сокращение:

      O P abifud

O: optimization

   F=Optimize for fast code

   S=Optimize for small code

   N=No optimization

P: Favor Pentium Pro

   +=on

   -=off

a=Assume No Aliasing (+-)

b=Remove Array Bounds Checks

i=Remove Integer Overflow Checks

f=Remove Floating Point Error Checks

u=Allow Unrounded Floating Point Operation

d=Remove Safe Pentium(tm) FDIV Checks

Несколько слов о результатах. Как видно, показатели Delphi совсем не плохи, но VB все же может работать быстрее. И видно, что основной вклад дает флаг отключения проверки переполнения.

Примечания: 1. все времена измерялись функцией GetTickCount, которая имеет дискретность в 15-16 миллисекунд (она изменяет свое значение иногда с шагом 15, а иногда 16). Возможно, это специфика конкретно моей конфигурации, но я не встречал отклонений от этого свойства. Если захочется измерять реально с точностью до 1-й миллисекунды, используйте мультимедиа-таймеры (TimeBeginPeriod, TimeGetTime и TimeEndPeriod из библиотеки WinMM).

2. В Delphi были установлены следующие флаги:

Optimization +

Stack Frames -

Pentium-safe FDIV -

Range Checking -

I/O Checking +

Overflow Checking -

Тест 2. Вещественная арифметика

Сейчас начинается самое интересное. Я очень удивился, увидев результаты, и сразу стал искать, что же я не так сделал. Но не нашел. Привожу программы. Единственное что там поменялось - тип данных. Отличия от целочисленных версий подчеркнуты.

VB:

Form1:

Option Explicit

Private Sub Command1_Click()

Dim i As Long

Dim t As Double

Dim tmr As Long

tmr = GetTickCount

For i = 0 To 10000000

    t = Factorial(12)

Next i

MsgBox GetTickCount - tmr

End Sub

Module1:

Public Declare Function GetTickCount Lib "kernel32" () As Long

Option Explicit

Public Function Factorial(ByVal x As Long) As Double

Dim i As Long

Dim t As Double

t = 1

For i = 2 To x

    t = t * i

Next i

Factorial = t

End Function

Delphi:

Код формы (начало и конец опущены):

function Factorial(x:integer): extended ;

var

  i:integer;

  t: extended ;

begin

  t:=1;

  for i:=2 to x do t:=t*i;

  factorial:=t;

end;

procedure TForm1.Button1Click(Sender: TObject);

var

  tmr:integer;

  i:integer;

  t: extended ;

begin

  tmr:=gettickcount;

  for i:=0 to 10000000 do begin

    t:=Factorial(12);

  end;

  messagedlg(inttostr(gettickcount-tmr),mtwarning,[mbok],0);

end;

Вот результаты:

Обозначения:

Delphi (extended) - результаты для приведенной программы.

Delphi (double) - результаты работы программы с типом Double вместо Extended.

Все, что не Delphi относится к VB.

Debug - неоткомпилированная. Т.е. просто F5.

P-Code: программа, откомпилированная в псевдо-код. О том, что это такое, будет сказано дальше.

Все остальное - Native Code.

Применено следующее сокращение:

      O P abifud

O: optimization

   F=Optimize for fast code

   S=Optimize for small code

   N=No optimization

P: Favor Pentium Pro

   +=on

   -=off

a=Assume No Aliasing (+-)

b=Remove Array Bounds Checks

i=Remove Integer Overflow Checks

f=Remove Floating Point Error Checks

u=Allow Unrounded Floating Point Operation

d=Remove Safe Pentium(tm) FDIV Checks

Как и ожидалось, время выполнения на Delphi увеличилось. Все-таки, вещественные числа обязаны работать медленнее. А вот VB выдает поразительные результаты. Мало того, что все режимы Native-кода кроме неоптимизированного на VB работают быстрее, так еще и подсчет с полной оптимизацией вообще даже быстрее чем целочисленный метод почти в 2 раза! Я сам не могу понять, в чем здесь дело. Единственная догадка - компилятор VB догадался заменить вещественный тип на целый. Но как-то не верится в это. К тому же, это не дало бы ускорения по сравнению с целочисленным методом…

Надеюсь, у вас больше не будет мнения о VB как о тормозе. Но я приведу еще аргументы.

Тест 3. Запуск среды разработки

Здесь все лаконично. Измеряется время второго подряд запуска среды разработки. (Второй по тому, что при первом Windows’у требуется считать все в оперативную память, время чего непредсказуемо. А при втором запуске Windows уже все имеет в оперативке, и время запуска стабилизируется.) Запуск производился открытием проектов, использованных в тесте 1.

Delphi 7:              13.5sec

VB6:                   1.8sec

Разница налицо.

Тест 4. Запуск программы на исполнение

Опять измеряется время второго запуска. Но на этот раз программы.

Delphi 7:           1sec

VB6:                меньше чем 0.19sec

Здесь время пришлось замерять секундомером. По сему погрешность измерений велика. А для VB вообще не удалось замерить время запуска - не успевал нажать на кнопку. Но все равно, разница налицо.

Оптимизация программ для VB

Все до этого было просто лирическим отступлением. А теперь к собственно оптимизации программ.

Дизайн проекта

Здесь все естественно. Использовать поменьше дополнительных компонентов. Они могут долго грузиться. Также использовать как можно меньше рисунков на формах, чтоб они грузились быстрее.

Могу сказать, что многие OLE-объекты работают медленно. Старайтесь обойтись без них. Если OLE-объект является лишь элементом дизайна, то его лучше заменить картинкой.

Старайтесь сделать в вашем проекте как можно меньше форм. Если их достаточно мало, то не потребуется выгружать форму после закрытия, что еще позволит при следующем показе ее не загружать. Если диалоговое окно долго грузится, это откладывает довольно сильное впечатление о медленности программы.

Чем легче ваша программа в смысле размера, тем быстрее она будет работать. Это связано с тем, что если Windows’у потребуется освободить память, и он скинет вашу программу на винчестер, ему потом будет легче ее оттуда выгружать обратно, когда юзер снова обратится к ней.

Кодирование

Самое важное для скорости программы - правильно продумать архитектуру. Это и упростит программу, и ускорит ее. Однако обсуждать вопрос архитектуры в общем случае трудно. Могу лишь дать один совет: старайтесь придерживаться стандартных форматов. Я к тому, что я сам один раз напоролся на несоответствие моего формата с форматом для вывода данных. Это было в графическом редакторе. Поначалу я писал его, не зная, что такое WinAPI. И выводил рисунок на экран методами Line и PSet. И формат цвета пикселя соответствовал тому, что используется в VB. А в Bitmap-ах, как я позже выяснил, используется формат, где синий и красный поменяны местами по сравнению с тем, что я использовал. Кроме того, порядок индексов у меня был неверен: у меня получалось, что данные записываются по столбикам, а все стандартные WinAPI хотят развертку по строкам. Последнее я исправил без труда. А вот с первым пришлось возиться очень долго, и я до сих пор ощущаю последствия этого перехода (кое-где до сих пор синий вместо красного вылезает). Вообще, я не понимаю, почему MS так сделали: в Bitmap-ах цвета хранятся в формате BGR0, а всем функциям рисования (SetPixel к примеру) надо подавать цвет в формате RGB0.

Ну и конечно, надо составить эффективные алгоритмы для наиболее длительных операций. Тут уже думайте сами.

Когда дело доходит до кодирования, здесь важно помнить следующее:

А) обязательно объявлять все переменные. И каждой переменной указывать тип. Это поможет не только компилятору создать более эффективный код, но и вам не запутаться в вашей программе. Поясню подробнее.

Рассмотрим пример. На функции вычисления факториала. Предположим я написал ее так:

Function Factorial(x)

accum = 1

For i = 1 To x

    accum = acuum * i

Next i

Factorial = accum

End Function

Если эту функцию протестировать, то она будет все время давать ноль. В чем дело?

Дело в том, что я сделал опечатку. Внутри цикла я написал acuum вместо accum. Здесь, конечно опечатка довольно искусственная, но поверьте мне, такого рода опечатки случаются довольно часто, и иногда их очень сложно найти.

Чтобы избежать таких сложных поисков ошибок, поместите Option Explicit в самое начало модуля. Эта фраза скажет Visual Basic’у, что вы обязуетесь объявлять все переменные. Тогда вам придется переписать функцию следующим образом.

Function Factorial(x)

Dim i, accum

accum = 1

For i = 1 To x

    accum = acuum * i

Next i

Factorial = accum

End Function

Теперь если вы попробуете выполнить эту функцию, VB укажет вам на вышеупомянутую опечатку, и вы исправите.

Function Factorial(x)

Dim i, accum

accum = 1

For i = 1 To x

    accum = acсum * i

Next i

Factorial = accum

End Function

Но этот алгоритм не оптимален. Можно заметить, что в первом проходе цикла производится умножение на единицу. А зачем нам умножать на единицу? Поправим.

Function Factorial(x)

Dim i, accum

accum = 1

For i = 2 To x

    accum = acсum * i

Next i

Factorial = accum

End Function

Правда, повышение производительности от этого вы вряд ли заметите. По тому, что удалена всего одна операция, и она ничто по сравнению с 15-ю к примеру.

Но есть случаи, когда такого рода поправки очень критичны. Напимер, вам надо нарисовать холмик параболической формы. И можно написать:

Sub Holmik(h)

Dim x, y

For x = 0 To 100

    For y = 0 To 200

        If y <= (1 - ((x / 100) * 2 - 1) ^ 2) * h Then

            Form1.PSet (x, 200 - y)

        End If

    Next y

Next x

End Sub

Но зачем перебирать точки, которые не нужно изменять? Давайте не будем их перебирать. Для этого установим правильные границы для цикла по y.

Sub Holmik(h)

Dim x, y

For x = 0 To 100

    For y = 0 To (1 - ((x / 100) * 2 - 1) ^ 2) * h

        Form1.PSet (x, 200 - y)

    Next y

Next x

End Sub

Это работает заметно быстрее, даже не смотря на то, что метод PSet жутко медленный. А теперь давайте ускорим наш холмик еще сильнее.

Работа с типом данных Variant очень тяжела. А в данном случае я нигде не указал тип данных. И он стал Variant, как по умолчанию. Давайте объявим типы:

Sub Holmik(h As Long)

Dim x, y As Long

For x = 0 To 100

    For y = 0 To (1 - ((x / 100) * 2 - 1) ^ 2) * h

        Form1.PSet (x, 200 - y)

    Next y

Next x

End Sub

ВНИМАНИЕ. Здесь я напоролся на подводный камень, на который налетают многие, особенно те, кто знает, к примеру, паскаль. Написав это:

Dim x, y As Long

Я объявил переменную x как Variant (не указал тип переменной). По этому, такое объявление надо писать так:

Dim x As Long, y As Long

Или так:

Dim x As Long

Dim y As Long

Последняя запись предпочтительнее, так как она не порождает желания опустить тип для x.

Так, перепишу Holmik правильно:

Sub Holmik(h As Long)

Dim x As Long

Dim y As Long

For x = 0 To 100

    For y = 0 To (1 - ((x / 100) * 2 - 1) ^ 2) * h

        Form1.PSet (x, 200 - y)

    Next y

Next x

End Sub

Вот этот алгоритм достаточно быстр. Конечно, его можно еще немного оптимизировать. Возведение в степень - операция медленная. По этому можно расписать его как ((x / 100) * 2 - 1) * ((x / 100) * 2 - 1). Не знаю, будет ли это быстрее. Но однозначно будет, если записать выражение ((x / 100) * 2 - 1) в отдельную переменную t, и потом вместо ((x / 100) * 2 - 1) * ((x / 100) * 2 - 1) написать t * t. Но все это мелочи, так как происходит вне цикла и по времени выполнения несравнимо быстро по отношению к циклу. По этому об этом не стоит сильно беспокоиться. Как правило, достаточно обработать самый глубокий цикл (но в то же время иногда бывают случаи, когда это не совсем так).

А код факториала стоит написать так:

Function Factorial(x As Long) As Long

Dim i As Long

Dim accum As Long

accum = 1

For i = 2 To x

    accum = accum * i

Next i

Factorial = accum

End Function

Как ни странно, этот код еще можно оптимизировать. А именно - передавать значение x не по ссылке, а по значению. Так как ссылка есть 32-битное число (адрес в памяти), и Long тоже 32-битное число. И по ссылке, и по значению данных передается столько же, но при передаче по ссылке придется делать дополнительную операцию - читать значение x из памяти. Так что лучше использовать передачу по значению. Это немного более важно в функциях, которые вы будете вызывать в цикле и если они состоят из одной-трех строчек. Например:

Function Min(x As Long, y As Long) As Long

If x > y Then Min = y Else Min = x

End Function

Вообще для повышения производительности рекомендуется использовать в выражении только один тип данных. Если, к примеру, вы делите два числа нацело, пишите w = w \ 2 вместо w = Int(w / 2). Вообще последнее должно рассматриваться так: w = CInt(Int(CDbl(w) / 2)). А целочисленное деление есть команда процессора, которая должна выполняться как одна операция, то есть быстро. Но однако оператор •\• (делить нацело) работает не совсем так как Int(•/•). А именно, он работает как Fix(•/•).Напомню, разница между Int и Fix в том, что Int возвращает наибольшее целое, меньшее аргумента, а Fix возвращает ближайшее меньшее по модулю целое к аргументу. Пример:

Immediate window

-

? int(0.5)

 0

? fix(0.5)

 0

? int(-0.5)

-1

? fix(-0.5)

 0

? int(-1/2)=-1\2

False

? fix(-1/2)=-1\2

True

Типы с плавающей точкой

На самом деле оказывается, что VB абсолютно до лампочки (в смысле производительности), Single или Double - время исполнения не зависит то выбора типов. Но однако настоятельно рекомендую использовать только один из них (Double), чтобы у VB не было трудностей со сравнением. И крайне не рекомендуется использовать тип Variant, так как он жутко медленный. Например, подсчет факториала в предыдущем тесте, произведенный с помощью Variant, занял 12.2 сек, тогда как Double в той же программе обсчитывался 0.55 сек. Разница очевидна.

Работа с графикой

Графические методы VB неэффективны. По этому, рекомендую осваивать GDI. Например для рисования сразу большого количества линий можно воспользоваться функцией PolyPolyLine, которая нарисует их все разом. С этим, пожалуй, все.

Работа со строками

Я часто сталкиваюсь с надобностью перебора и преобразования строк. Здесь важно следующее.

Для поиска фрагмента в строке не пишите свою функцию - используйте встроенные функции VB InStr и InStrRev.

Для поиска и замены есть функция Replace.

Если все же пришлось делать перебор, помните следующее.

Вам наверняка придется строить новую строчку из символов старой. И вы наверняка это сделаете вот так:

St = St + Char

Не делайте так, Если строчка St может стать длинной. Получается, что при каждом добавлении VB копирует строчку St саму в себя, параллельно приписывая Char. Когда строчка становится большой, эта штука начинает жутко тормозить.

Предлагается следующее решение проблемы. Место для строчки выделяется изначально - как правило, перед перебором известно, какую длину не может превзойти строчка, которую надо склеивать. Тогда можно сделать так:

Sub …

Dim i As Long

Dim cp As Long

Dim St As String

Dim Char As String

cp = 1

St = Space$(Len(InSt))

For i = 1 To Len(InSt)

    …

    GoSub AddChar

    …

Next i

St = Left$(St, cp-1)

Exit Sub

AddChar:

Mid$(St, cp, Len(Char)) = Char

cp = cp + Len(Char)

Return

End Sub

Т.е. надо заполнить Char тем, что надо приклеить, и написать GoSub AddChar. Это будет работать сильно быстрее, особенно с большими строчками.

Компиляция проекта

Правильно скомпилировать проект тоже надо уметь. Расскажу о том, какие настройки имеет компилятор VB6.

Заходите в "File: Make .exe". Появляется окно сохранения. Жмете Options. Появляется диалог, где вы можете написать информацию о своей программе. Но нам это сейчас не интересно, по этому щелкайте вкладку Make. Там вы увидите следующее:

Поясню все настройки.

Compile to P-Code - компилировать в П-код. Это значит, ваша программа будет переведена на некоторый промежуточный язык и записана в exe. Этот exe будет интерпретироваться Runtime-машиной (MSVBVM60.dll). Откомпилированная так программа будет работать наиболее медленно, почти как в отладке VB. Но зато она будет очень маленькой. Заметно меньше, чем в Native-коде. Теоретически программа может быть перенесена на другую платформу (если вы, конечно, не использовали WinAPI), если найдется виртуальная машина для новой платформы. Лично я не рекомендую компилировать в П-код.

Compile to Native code - компилировать в машинные инструкции. Код программы переводится в машинные инструкции, и они записываются в exe-шник. Такая программа будет привязана к архитектуре машин Intel и к платформе Windows, что не позволит переносить ее на другие платформы. Но так как я никогда не слышал о попытках перенести VB-программу на другую платформу, а по сему лучше компилировать в Native-код, так как он дает преимущество в скорости вплоть до двадцатикратного (см. тест производительности, плавающая точка - Native код может быть быстрее в 20.5 раз).

Программы, откомпилированные в Native Code, тяжелее программ на П-коде. Но ради скорости все же я всегда компилирую в Native код, чего и вам советую.

Об оптимизациях:

Optimize for Fast Code - оптимизировать программу для скорости. Как написано в документации, если компилятор найдет два способа перевода в машинных код, он переведет в тот, что будет быстрее. Но это не совсем так. Смотрите результаты тестов с плавающей точкой - там по неизвестным мне причинам выиграла оптимизация на маленькую программу, а не на скорость.

Короче, рекомендую для большинства случаев.

Optimize for Small Code - оптимизировать размер. Опять, если компилятор найдет несколько вариантов реализации, он выберет ту из них, что меньше по размеру. Но реально эти две оптимизации мало чем отличаются, и размер, и производительность меняются не сильно.

No Optimization - отключить оптимизацию. Не рекомендую. Включайте эту опцию, если неоткомпилированная программа работает, а откомпилированная - нет. Но у меня еще не было случаев, что это помогало. Как правило, причины другие.

Favor Pentium Pro(tm) - оптимизация вашей программы для работы на процессоре Pentium Pro. Был когда-то такой процессор. Но я пробовал и с ней, и без нее - почти никакой разницы.

Create Symbolic Debug Info - не делает ничего с собственно программой, а лишь создает дополнительный файл, содержащий информацию для отладки вашей программы во внешнем отладчике (файл .pdb).

Теперь нажмите Advanced Optimizations - посмотрим, что у нас там.

В первой строке написано, что если вы включите эти оптимизации, ваша программа может перестать работать. Эта чистая правда. Чтобы иметь возможность включить эти оптимизаторы, вам необходимо соблюдать некоторые требования при кодировании.

Assume No Aliasing - дает вашей программе возможность передавать аргументы функций через регистры процессора. Однако если вы включаете этот оптимизатор, вам необходимо убедиться, что у вас нет конструкций такого рода:

Sub Foo(x As Integer, y As Integer)
   x = 5   'доступ к одной и той же переменной
   y = 6   'через разные имена
End Sub
Sub Main
   Dim z As Integer
   Foo z,z
End Sub 
 

Как правило, включение этого оптимизатора к неправильным результатам не приводит, но следует иметь это в виду. А выражение Aliasing в данном случае означает доступ к одной области памяти через разные имена. Никакой связи с графикой.

Remove Array Bounds Checks - не включать в код программы логику автоматической проверки границ массивов. Если этот флаг не отмечен, VB перед каждой операцией с данными в массиве проверяет, не выходят ли индексы за границы массива. Если ваша программа использует ошибку Subscript Out Of Range, не включайте этот оптимизатор. Если вы это сделаете, у вас вместо нормальной ошибки получится Access Violation, и программа будет снята со счета. Тут на помогут никакие On Error… Но зато этот оптимизатор жутко ускоряет работу с массивами. Подробнее о том, что можно и чего нельзя, будет сказано дальше, в Работе с массивами.

Remove Integer Overflow Checks - не проверять переполнения при целочисленных операциях. Здесь все просто - если вы выйдите за границы допустимых значений, вы получите неправильный результат. Никакого Overflow-а. Естественно, это относится только к целочисленным операциям, плавающая точка будет работать так же, как раньше.

Remove Floating Point Error Checks - аналогичная опция, но для плавающей точки. Если не отмечена, VB проверяет, не вышел ли результат за границы и не пытается ли ваша программа делить на ноль. Так он выдаст ошибку (переполнение или деление на ноль). Но если опция включена, никакой ошибки не будет, а результат окажется неверным.

Allow Unrounded Floating Point Operation - разрешить сравнение двух чисел разных типов, не округлив их предварительно. Это может привести к тому, что условие, которое должно бы выполняться, не выполнится.

Вообще говоря, сравнивать числа с плавающей точкой вот так: If a = b Then … не следует, так как все операции с плавающей точкой производятся приближенно и вероятность того, что два результата совпадут точно, не велика. По этому, для проверки равенства рекомендуется сравнивать их, введя некоторый допуск. Вот так: If Abs(a - b) < Delta Then … Здесь Delta - какая-либо константа, определяемая той точностью, которая вам необходима в вашей задаче. Таким образом, если a отличается от b меньше чем на Delta, они принимаются равными.

Этот оптимизатор влияет только на сравнение, где сравниваются типы разной точности (Single и Double). Если флаг не отмечен, оба значения сначала округляются до Single, а потом сравниваются. Если этот флаг отмечен, то сравнение производится без округления.

Remove Safe Pentium FDIV Checks - убирает специальную реализацию вещественного деления из программы. У некоторых процессоров Pentium есть ошибка в инструкции FDIV, в результате чего при вещественном делении может получиться немного неправильный результат. Если эта опция включена, компилятор использует для вашей программы инструкцию FDIV, в результате чего производительность программы повысится, но на процессорах с ошибкой FDIV результаты могут быть немного другими.

Работа с массивами

Здесь я опишу, чего следует остерегаться, когда включен флаг Remove Array Bounds Checks (далее RABC).

Остерегаться стоит обращение к неправильному индексу в массиве. Опишу некоторые случаи.

  1. Обращение к любому элементу пустого массива.
  2. Обращение к индексам за границами.
  3. Использована неправильная размерность.

При этом к немедленному Access Violation привели только последние два.

Попытка считывания из пустого массива в режиме отладки вызывает VB ошибку Subscript out of range. А откомпилированная версия (я под откомпилированной дальше буду иметь в виду откомпилированную в Native-код с отмеченным Remove Array Bounds Checks) дала ошибку Object variable or With block variable not set. Причем в связи с тем, что эта ошибка возникала нерегулярно, можно прийти к выводу, что полагаться на эту ошибку нельзя.

И вот, кстати, возникает вопрос, как определить, пуст ли массив. Можно, в принципе использовать функцию UBound, но она тоже возвращает Object variable or With block variable not set в скомпилированной программе при действии на пустой массив. И хотя я не наткнулся на отсутствие возникновения ошибки, судя по ее поведению, она тоже может быть нерегулярной. По этому, я не рекомендую полагаться на эту ошибку. Но как же все-таки определить, пуст ли массив?

Я так и не нашел способа это сделать стандартными способами VB. И предлагаю два решения этой проблемы.

Первое приходит сразу. Вместе с массивом таскать за ним его длину. Но этот метод не надежен, так как вы спокойно можете забыть изменить этот номер.

Второе - решение, основанное на знании того, как VB хранит массивы. Предлагаю процедурку для определения размерности массива.

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
        (Destination As Any, _
         Source As Any, _
         ByVal Length As Long)
Private Declare Function AryPtr Lib "msvbvm60.dll" Alias "VarPtr" (Ary() As Any) As Long
 
 
Private Type SafeArrayBound '8 bytes
    cElems As Long '4
    lBound As Long '4
End Type
 
Private Type SafeArray
    '                length    offset
    cDims As Integer '2        0
    fFeatures As Integer '2    2
    ElemSize As Long '4        4
    cLocks As Long '4          8
    PtrData As Long '4         12
    Bounds(0 To 1) As SafeArrayBound '16 bytes         16
End Type
 
Private Function AryDims(ByVal ptrAry As Long) As Long
Dim PtrStruc As Long
Dim SA As SafeArray
If ptrAry = 0 Then
    Err.Raise 1111, "AryDims", "Некорректный указатель на массив. Используйте AryDims(AryPtr(ary))"
End If
CopyMemory PtrStruc, ByVal ptrAry, 4
If PtrStruc = 0 Then
    AryDims = 0
Else
    CopyMemory SA, ByVal PtrStruc, 2
    AryDims = SA.cDims
End If
End Function
 

Поясню, что делает эта процедурка и как организованы массивы в VB.

Функция AryPtr возвращает указатель на то место, где хранится указатель на заголовок массива. То есть, если понимать идентификатор массива как переменную, то в ней записан адрес, по которому надо искать заголовок массива. А вызвав функцию VarPtr(массив), я получил указатель на указатель на заголовок массива. В заголовке массива хранятся размеры массива, указатель на данные и еще кое-какая информация. Для того, чтобы узнать немного о том, что есть в заголовке массива, поищите в MSDN в индексе слово «SAFEARRAY».

Таким образом, определение, пуст ли массив, выглядит так:

If AryDims(AryPtr(MyAry())) = 0 Then
    ‘Массив пуст
End If
 

У этой функции есть один большой недостаток - она не работает с массивами строк. А опция RABC влияет в том числе и на массивы строк. Что в данном случае делать с массивами строк, я не знаю, по этому мне приходится таскать размер массива.

Итак, обобщу все, что так сложно написал:

Если вы включили флаг Remove Array Bounds Checks, вы должны следить:

А) при работе с элементами:

  1. за тем, что массив не пуст.
  2. за размерностью.
  3. за границами.

Б) при использовании LBound/UBound:

  1. за тем, что массив не пуст.
  2. за размерностью.

За всем остальным следить не надо - все случаи ReDim работают хорошо. Копирование массивов также не вызывает никаких проблем.

А чтобы определить, пуст ли массив, нужно либо таскать за каждым массивом его длину, либо использовать функцию AryDims, код которой приведен выше.

Примечание. Флаг RABC не влияет на работу массивов элементов управления. Т.е. на то, что называется Control Array. Отмечу еще, что массив объектов не есть Control Array, а просто обычный массив, и на него тоже действует флаг RABC.

Некоторые полезные советы.

Если вам надо откопировать массив, не пишите цикл, а просто напишите так:

Ary1=Ary2

Это хоть и будет копировать данные из массива в массив, но все же быстрее, чем в обычном цикле.

Заключение

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

Еще очень сильное впечатление пользователя может отложиться по тому, как программа докладывает о своем состоянии. То есть, если программа задумалась, она должна нарисовать прогресс бар, или сделать указатель часиками. Еще не плохо сопроводить эти действие информацией о том, в какой стадии оно находится и что сейчас происходит. Если уж действие длится совсем долго, обязательно сделать возможность отмены. Можно еще показывать полезные советы, чтобы пользователь не скучал.


  Рекомендовать страницу   Обсудить материал  [1] Написать редактору  
  Распечатать страницу
 
  Дата публикации: 10.03.2006  

ОБ АЛЬЯНСЕ | НАШИ УСЛУГИ | КАТАЛОГ РЕШЕНИЙ | ИНФОРМАЦИОННЫЙ ЦЕНТР | СТАНЬТЕ СПОНСОРАМИ SILICON TAIGA | ISDEF | КНИГИ И CD | ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ | УПРАВЛЕНИЕ КАЧЕСТВОМ | РОССИЙСКИЕ ТЕХНОЛОГИИ | НАНОТЕХНОЛОГИИ | ЮРИДИЧЕСКАЯ ПОДДЕРЖКА | АНАЛИТИКА | КАРТА САЙТА | КОНТАКТЫ

Дизайн и поддержка: Silicon Taiga   Обратиться по техническим вопросам  
Rambler's Top100 Rambler's Top100