Опубликовано:
Исправлено:
Версия документа: 2
Секреты и хитрости
В этой статье собраны советы по написанию быстрого и компактного кода. Некоторые советы специфичны для фрибейсика, некоторые для всех языков программирования, некоторые для операционной системы Windows.
Деление чисел с плавающей запятой
Если у вас числа с плавающей запятой (Single
или Double
), то для повышения точности вычислений лучше не делить на N
, а умножать на десятичную дробь из (1 / N)
.
Например, вместо деления на 2,0 следует умножать на 0,5; вместо деления на 3,0 — умножать 0,3(3), и так далее:
' У этой операции относительная погрешность будет больше
Dim Result As Double = 1.5 / 2.0
' Так будет точнее
Dim Result As Double = 1.5 * 0.5
Используйте знаковые переменные счётчиков цикла
Целочисленные переменные могут быть знаковыми и беззнаковыми:
' Знаковая переменная
Dim SignedVariable As Integer
' Беззнаковая переменная
Dim UnsignedVariable As UInteger
Беззнаковые переменные могут хранить в себе только положительные числа и 0, знаковые — положительные, отрицательные и 0.
«Ёмкость» переменной ограничена типом данных, мы не можем хранить в переменной бесконечно большое число. Например, максимально возможное число, которое «влезет» в переменную типа Byte
ограничена константой 127
. Если мы к такой переменной прибавим единицу, то результат «не влезет» и произойдёт переполнение.
Переполнение знаковой переменной — это неопределённое поведение. Результат зависит от настроек компилятора, архитектуры процессора и много чего ещё.
Задача программиста — писать программу так, чтобы в коде не встречалось неопределённое поведение. Со своей стороны компилятор полагает, что в коде не встретится неопределённого поведения. Поэтому для знаковой переменной компилятор предполагает, что она не будет переполнена.
Целый класс оптимизаций компилятора опирается на отсутствие переполнения знаковых переменных.
С другой стороны, переполнение беззнаковых переменных не является неопределённым. Это значит, что прибавив единицу к максимальному значению 255
переменной типа UByte
— мы всегда получим 0.
Когда мы используем беззнаковые переменные в счётчиках цикла, то компилятор не может применить такие оптимизации.
Это же относится и к индуктивным переменным. Индуктивные переменные — такие переменные, которые зависят от счётчика цикла или вычисляются с использованием счётчика цикла. Индуктивные переменные по возможности тоже следует знаковыми.
' Циклы со знаковыми переменными оптимизируются лучше:
For i As Integer = 0 To Length - 1
' Знаковая индуктивная переменная
' оптимизируется лучше
Dim x As Integer = i * 2
Next
Объявление переменных вместе с инициализирующим значением
Старые компиляторы бейсиков принуждали сначала объявлять переменную, а потом присваивать ей значение:
Dim Width As Integer
Width = 640
Dim Height As Integer
Height = 480
В новом стиле переменные можно объявлять вместе с инициализирующим значением:
Dim Width As Integer = 640
Dim Height As Integer = 480
Используйте объявления переменных в новом стиле, это работает быстрее: таким образом мы исключаем лишние инструкции обнуления переменных.
Прекращение инициализации переменных по умолчанию
При объявлении новой переменной ей сразу же присваивается значение по умолчанию, если мы не сделали это сами:
- целочисленным переменным присваивается значение 0;
- логические переменные получают значение
False
; - строки фиксированной длины типа
ZString
иWString
заполняются нулевыми символами на всю их длину; - все элементы массива обнуляются.
Если нам не требуется инициализация переменных, для этого переменной нужно присвоить специальное значение Any
:
' Этой переменной значение по умолчанию не будет присвоено
' Сейчас в ней находится так называемый «мусор»
Dim Count As Integer = Any
В случае со строками необходимо поставить нулевой символ самостоятельно:
' Память под строку выделена
Dim Value As WString * (MAX_BUFFER_LENGTH + 1) = Any
' Не забываем установить нулевой символ:
Value[0] = 0
Используйте модификатор Private
для функций
Функции, объявленные с модификатором Private
могут быть видны только в текущей модуле (единице трансляции):
' Функция видна только в текущем модуле
Private Function Foo()As Integer
Return 0
End Function
Это даёт подсказку оптимизатору на встраивание функций или удаление таких функций, если на них нет ссылки.
Объявление переменных ближе к месту где они используются
Старые компиляторы бейсика заставляли пользователей объявлять все переменные в верхней части программы:
Dim A, B, C, Width, Height
Сейчас это уже неактуально. FreeBASIC не требует, чтобы переменные были объявлены в начале функции. Стремитесь объявлять переменные как можно ближе к месту использования:
Dim Age As Integer = 80
SetAge(Value)
Dim FirstName As String = "John Doe"
SetName(FirstName)
Объявление переменных в том блоке, где они используются
Если переменная используется только внутри блока, то её следует объявлять внутри блока:
If Condition Then
Dim Age As Integer = 80
SetAge(Age)
End If
Это же относится к циклам For
, где счётчик цикла можно сразу указать в заголовке:
For i As Integer = 0 To Length - 1
Foo(i)
Next
Компилятор подсчитывает общее количество переменных в функции и выделяет под них память на стеке. Если какая‐то переменная существует в локальном блоке, то оптимизирующий компилятор может переиспользовать это место для других переменных, снижая нагрузку на память:
Scope
Dim Age As Integer = 80
Print Hex(@Age)
' Выведет 65FE00
End Scope
Scope
' Переменная Length может занимать ту же ячейку памяти
' что и переменная Age объявленная в предыдущем блоке
' тем самым снижается нагрузка на память
Dim Length As Integer = 512
' При компиляции с оптимизацией выведет тот же адрес
' что у переменной Age — 65FE00
Print Hex(@Length)
End Scope
Массивы без инициализации дескриптора массива
Массив во фрибейсике — это встроенный объект (дескриптор массива), в котором хранятся указатель на данные, количество измерений и размер каждого измерения. При объявлении массива фрибейсик автоматически создаёт дескриптор массива и инициализирует его поля. Это необходимо для контроля за границами и передачи массива в подпрограммы.
Однако если массив объявлен внутри структуры или как Shared‐переменная, то фрибейсик будет контролировать границы массива на этапе компиляции без создания дескриптора массива.
Воспользуемся этой особенностью и объявим структуру, содержащую в себе массив фиксированной длины:
Const CAPACITY = 512
Type IntegerVector
Buffer(CAPACITY - 1) As Integer
End Type
Теперь компилятор знает размер массива, и мы можем использовать экземпляр структуры с массивом внутри:
Dim Vector As IntegerVector = Any
For i As Integer = 0 To CAPACITY - 1
Vector.Buffer(i) = i
Next
Проверка строк на пустоту
Если нужно проверить не пуста ли строка, то следует использовать функцию Len
:
If Len(s) Then
' строка не пуста
End If
Сравнение строки с литеральной константой ""
работает медленнее, потому что требует сравнения символьных данных:
' Этот код работает медленнее
If s = "" Then
' строка пуста
End If
Быстрая очистка (обнуление) строки
Для этого достаточно присвоить 0 первому символу в строке, то есть символу под индексом 0:
Dim Value As WString * (MAX_BUFFER_LENGTH + 1) = Any
Value[0] = 0
Получение символа в строке
Новички нередко используют функцию Mid
для получения единственного символа в строке, однако есть способ проще.
Строка — это массив символов, следовательно, к ней можно применять индексирование по указателю:
' Строка длиной в 99 символов (+1 на нулевой)
Dim s As WString * (99 + 1) = Any
' Получение пятого символа в строке
Dim charCode As Integer = s[4]
Посимвольный обход строки
Предыдущий подход можно применять для посимвольного обхода строки в цикле. Проверяя текущий символ, цикл остановится как только встретится нулевой, обозначающий конец строки. Таким образом вычислять длину строки заранее не нужно:
Dim Value As WString * (MAX_BUFFER_LENGTH + 1) = Any
Dim i As Integer
Do While Value[i] <> 0
' Что‐то сделать с символом
' Например, вывести на консоль
Print Value[i]
' Сдвигаем индекс на следующий символ
i += 1
Loop
Если заранее известно, что строки будут ненулевой длины, то условие выхода из цикла While s[i] <> 0
можно поставить после Loop
.
Выравнивание структур
В объявлении структур старайся располагать поля по убыванию их размера. Особенно нужно уделить внимание группировке вместе переменных, чей размер меньше текущей архитектуры: Byte, Short, Long, Boolean, их массивы и структуры с ними.
Например, обращение к переменной с типом Integer по выровненному адресу занимает всего один такт процессора, а если переменная не выровнена, то два такта на архитектуре i386. На некоторых архитектурах читать по невыровненному адресу вообще нельзя. Так происходит потому, что невыровненная переменная располагается в нескольких ячейках памяти: часть в одной и часть в следующей.
Такой код работает медленно:
Type Foo
Field1 As Boolean
Field2 As Integer
End Type
Такой код работает быстро:
Type Bar
Field2 As Integer
Field1 As Boolean
End Type