Типы данных

Вопросы программирования на Free Pascal, использования компилятора и утилит.

Модератор: Модераторы

Ответить
Аватара пользователя
vitaly_l
долгожитель
Сообщения: 3333
Зарегистрирован: 31.01.2012 16:41:41
Контактная информация:

Сообщение vitaly_l »

MylnikovDm писал(а):Поэтому во втором случае у вас ассемблерный код по определению должен быть больше.

Нет они по командам - одинаковые, разница в одной команде. Inc и Move, а всё остальное одинаково (за исключением создания и удаления). Создание и удаление - я отбрасываю, т.к. это не серьёзное и мало-весомое отличие.
ElectroGuard писал(а):Никто же не мешает сделать прямой указатель на динамический массив.

Пример покажите пожалуйста, чтобы исключить недопонимание художников в программировании.
MylnikovDm
постоялец
Сообщения: 103
Зарегистрирован: 15.02.2007 20:26:10
Откуда: Челябинск

Сообщение MylnikovDm »

Нет они по командам - одинаковые, разница в одной команде. Inc и Move, а всё остальное одинаково (за исключением создания и удаления).


Давайте посчитаем.

arr^ := b; - переводится в три команды процессора:
mov 0x42c000, %edx - записываем значение указателя arr с адреса 0x42c000 в регистр edx - чтение в регистр из памяти
mov 0x42c010, %al - записываем значение переменной b с адреса 0x42c010 в регистр al - чтение в регистр из памяти
mov %al, (%edx) - записываем значение регистра al (переменная b) в ячейку памяти по адресу в регистре edx (arr^) - запись из регистра в память

inc(arr); - смещаем указатель
addl 0x01, 0x42c000 - прямое прибавление 1 к значению в ячейке памяти 0x42c000, то есть, тут на самом деле происходит чтение и запись в одной команде.

Итого получаем три чтения из памяти в регистр и две записи из регистра в память

Теперь рассмотрим второй вариант.

arr[b] := b;
mov 0x42c000, %eax - записываем значение указателя arr с адреса 0x42c000 в регистр eax - чтение в регистр из памяти
movzbl 0x42c010, %edx - записываем значение переменной b с адреса 0x42c010 в регистр edx c дополнением нулями до 64 бит - чтение в регистр из памяти
mov 0x42c010, %al - записываем значение переменной b с адреса 0x42c010 в регистр al - чтение в регистр из памяти
mov %сl, (%eax, %edx, 1) - записываем значение регистра cl (переменная b) в ячейку памяти по адресу в регистрaх eax+edx (arr[b]) - запись из регистра в память

Получаем три чтения из памяти в регистр и одну запись из регистра в память.

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

Но в целом ассемблерный код как-то не особо порадовал. Оптимизация как таковая отсутствует. Регистровые переменные не используются, идёт постоянная загрузка данных, которые уже и так находятся в регистрах процессора. Зачем?
Аватара пользователя
serbod
постоялец
Сообщения: 449
Зарегистрирован: 16.09.2016 10:03:02
Откуда: Минск
Контактная информация:

Сообщение serbod »

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

Код: Выделить всё

var p: Pointer; // или PByte, для удобства
var arr: array of Byte;
var str: string;

p := @arr[0];

p := s[1];  // будет больно, если s = ''
p := PChar(s); // а так будет норм


Добавлено спустя 3 минуты 8 секунд:
MylnikovDm писал(а):Оптимизация как таковая отсутствует.

Возможно, для удобства отладки.
Аватара пользователя
vitaly_l
долгожитель
Сообщения: 3333
Зарегистрирован: 31.01.2012 16:41:41
Контактная информация:

Сообщение vitaly_l »

MylnikovDm писал(а):arr^ := b; - переводится в три команды процессора:
mov 0x42c000, %edx - записываем значение указателя arr с адреса 0x42c000 в регистр edx - чтение в регистр из памяти
mov 0x42c010, %al - записываем значение переменной b с адреса 0x42c010 в регистр al - чтение в регистр из памяти
mov %al, (%edx) - записываем значение регистра al (переменная b) в ячейку памяти по адресу в регистре edx (arr^) - запись из регистра в память

inc(arr); - смещаем указатель
addl 0x01, 0x42c000 - прямое прибавление 1 к значению в ячейке памяти 0x42c000, то есть, тут на самом деле происходит чтение и запись в одной команде.

Итого получаем три чтения из памяти в регистр и две записи из регистра в память

Теперь рассмотрим второй вариант.

arr[b] := b;
mov 0x42c000, %eax - записываем значение указателя arr с адреса 0x42c000 в регистр eax - чтение в регистр из памяти
movzbl 0x42c010, %edx - записываем значение переменной b с адреса 0x42c010 в регистр edx c дополнением нулями до 64 бит - чтение в регистр из памяти
mov 0x42c010, %al - записываем значение переменной b с адреса 0x42c010 в регистр al - чтение в регистр из памяти
mov %сl, (%eax, %edx, 1) - записываем значение регистра cl (переменная b) в ячейку памяти по адресу в регистрaх eax+edx (arr[b]) - запись из регистра в память

Получаем три чтения из памяти в регистр и одну запись из регистра в память.

Вы хотите сказать что, SetLength - работает быстрее чем GetMem?
Точнее из ваших слов, получается что, перебор цикла: SetLength - работает быстрее чем перебор цикла GetMem?
Что-то мне в это с трудом верится.

.
MylnikovDm
постоялец
Сообщения: 103
Зарегистрирован: 15.02.2007 20:26:10
Откуда: Челябинск

Сообщение MylnikovDm »

Вы хотите сказать что, SetLength - работает быстрее чем GetMem?
Точнее из ваших слов, получается что, перебор цикла: SetLength - работает быстрее чем перебор цикла GetMem?
Что-то мне в это с трудом верится.

А тут не надо верить мне или не верить. Просто возьмите и сами проанализируйте ассемблерный листинг, который вы же тут и опубликовали, посчитайте количество обращений к памяти и сами убедитесь, что в приведённом вами варианте компиляции обращение через указатель и его инкремент работает медленнее, чем через массив и индекс. При этом экономия возникает за счёт того, что индекс смещается отдельными командами вне команд обращения к элементу, поэтому запись его значения не происходит, а в случае с инкрементом указателя происходит дополнительная запись значения обратно в память.
На самом деле, если эти варианты оптимизировать на ассемблере, то оба варианта можно значительно ускорить за счёт использования регистровых переменных. Но компилятор FreePascal этого не делает. Возможно потому, что отключена оптимизация для режима отладки.

Да, насколько я сейчас понимаю логику работы компилятора, статический массив с индексом будет работать примерно также, как динамический массив с SetLength. То есть, тут дело не столько в SetLength, сколько в в способе адресации элемента массива, поскольку для случая элемент массива по индексу в процессоре есть специальный вариант команды с адресацией через два регистра, в одном из которых базовый адрес, а во втором смещение на нужный элемент.
Mirage
энтузиаст
Сообщения: 881
Зарегистрирован: 06.05.2005 20:29:07
Откуда: Russia
Контактная информация:

Сообщение Mirage »

Как относится SetLength vs GetMem к адресации? Тем более, что аналогом SetLength является не GetMem, а ReallocMem.
И для динамического массива ничто не мешает взять адрес первого элемента: @a[0] и работать с ним как с указателем.
Другое дело, что неплохо было бы чтобы компилятор научился переводить обращения по индексу работу с указателями...
MylnikovDm
постоялец
Сообщения: 103
Зарегистрирован: 15.02.2007 20:26:10
Откуда: Челябинск

Сообщение MylnikovDm »

И для динамического массива ничто не мешает взять адрес первого элемента: @a[0] и работать с ним как с указателем.

Из того ассемблерного когда, листинг которого был опубликован, следует, что именно работа через индекс в массиве оказывается быстрее, чем работа через указатель с инкрементом. А так-то да, можно выдернуть адрес любого элемента массива и работать с ним напрямую. Но если писать такой код, то тогда непонятно, а на кой вам вообще FreePascal? Писали бы уже сразу на ассемблере и работали непосредственно с указателями. :D
Аватара пользователя
vitaly_l
долгожитель
Сообщения: 3333
Зарегистрирован: 31.01.2012 16:41:41
Контактная информация:

Сообщение vitaly_l »

Mirage писал(а):аналогом SetLength является не GetMem, а ReallocMem


Если сделать:
ReallocMem(somePointer, 0);
somePointer:=nil;

так можно освобождать память?

Это будет равнозначно освобождению памяти как в SetLength(arr,0); ?
Mirage
энтузиаст
Сообщения: 881
Зарегистрирован: 06.05.2005 20:29:07
Откуда: Russia
Контактная информация:

Сообщение Mirage »

MylnikovDm писал(а):Из того ассемблерного когда, листинг которого был опубликован, следует, что именно работа через индекс в массиве оказывается быстрее, чем работа через указатель с инкрементом.


Чет сомнительно. Да и из листинга это не может следовать - только их тестов.

MylnikovDm писал(а):Но если писать такой код, то тогда непонятно, а на кой вам вообще FreePascal? Писали бы уже сразу на ассемблере и работали непосредственно с указателями.


Никто не предлагает так писать весь код. Только в тех немногих местах, где критична производительность.

vitaly_l писал(а):Это будет равнозначно освобождению памяти как в SetLength(arr,0); ?


Да, это освободит память.
MylnikovDm
постоялец
Сообщения: 103
Зарегистрирован: 15.02.2007 20:26:10
Откуда: Челябинск

Сообщение MylnikovDm »

Чет сомнительно. Да и из листинга это не может следовать - только их тестов.

??? То есть, вы действительно считаете, что если у вас физически происходит на одну запись в память из регистра процессора больше, то при натурных тестах вы можете получить противоположный результат?
Mirage
энтузиаст
Сообщения: 881
Зарегистрирован: 06.05.2005 20:29:07
Откуда: Russia
Контактная информация:

Сообщение Mirage »

Это код без оптимизации видимо, раз указатель в памяти держится.
В нормальном коде указатель будет в регистре и доступ к памяти будет только при записи собственно по указателю.
Как и в случае с обращением по индексу. Просто в этом случае надо еще вычислить указатель по базовому смещению и индексу.
Так что обращение по указателю должно быть быстрее, чем по индексу. Но будет ли это заметно быстрее покажут только тесты.
MylnikovDm
постоялец
Сообщения: 103
Зарегистрирован: 15.02.2007 20:26:10
Откуда: Челябинск

Сообщение MylnikovDm »

В нормальном коде указатель будет в регистре и доступ к памяти будет только при записи собственно по указателю.

В нормальном коде на ассемблере можно много чего написать по другому, чтобы оно работало быстрее. Но мы тут вроде обсуждали какие конструкции FreePascal лучше использовать при написании программ именно на FreePascal, а не на ассемблере. И пока вы конкретными фактами (листингом дизассемблера) не подтвердите, что компилятор FreePascal умеет генерировать другой, более эффективный код, это всё словоблудие и обсуждать тут нечего.

При этом вы правы в том, что на практике сравнивать быстродействие того или иного реального варианта кода следует именно с помощью тестов. Но в реальном коде весьма редко встречаются такие примитивные задачи, которая была использована в обсуждаемом примере. Да и настройки оптимизатора у компилятора тоже могут быть разные. Можно стараться повысить быстродействие программы, а можно пытаться сократить размер получаемого кода. И вовсе не факт, что вариант, который оказался быстрее в первом случае, окажется быстрее и во втором случае тоже.
Аватара пользователя
bormant
постоялец
Сообщения: 408
Зарегистрирован: 21.03.2012 11:26:01

Сообщение bormant »

Ежели что, у компилятора есть ключи оптимизации, влияющие на вид конечного кода, одна и та же последовательность паскалевых операторов способна давать разный ассемблерный код. Поэтому есть смысл проверить кодогенерацию при разных -On.
Mirage
энтузиаст
Сообщения: 881
Зарегистрирован: 06.05.2005 20:29:07
Откуда: Russia
Контактная информация:

Сообщение Mirage »

Не думаю, что надо проверять способность компилятора размещать интенсивно используемые в цикле переменные в регистрах. Если бы он этого не умел, то интерес разве что академический представлял. :D
Ответить