Автор: mabu (Корпорация «Пакетные файлы»)

Опубликовано:

Исправлено:

Версия документа: 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

Используйте объявления переменных в новом стиле, это работает быстрее: таким образом мы исключаем лишние инструкции обнуления переменных.

Прекращение инициализации переменных по умолчанию

При объявлении новой переменной ей сразу же присваивается значение по умолчанию, если мы не сделали это сами:

Если нам не требуется инициализация переменных, для этого переменной нужно присвоить специальное значение 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