XGM Forum
Сайт - Статьи - Проекты - Ресурсы - Блоги

Форуме в режиме ТОЛЬКО ЧТЕНИЕ. Вы можете задать вопросы в Q/A на сайте, либо создать свой проект или ресурс.
Вернуться   XGM Forum > Warcraft> Академия: форум для вопросов> Jass
Ник
Пароль
Войти через VK в один клик
Сайт использует только имя.

Ответ
 
Алексей
Где кошачья мята?!
offline
Опыт: 26,303
Активность:
Ещё раз повторю, что создание эффективного кода, по быстродействию не уступающего ассемблерному, возможно и на ЯВУ (том же Си). Просто нужно учитывать архитектурные особенности процессоров и памяти. Вот ещё один пример:
Код:
type xrec=record
a:char;
b:integer;
c:byte;
d:single;
end;
Var ar:array[1..1000] of xrec;

Если переменные типа char занимают в памяти 1 байт, byte – тоже байт, а integer и single – по 4 байта, то сколько памяти займёт весь массив? Считаем: (1+4+1+4)*1000=10000 байт. Так? А вот и нет! На самом деле такой массив займёт 16000 байт! А вот если объявить так:
Код:
type xrec=record
a:char;
c:byte;
b:integer;
d:single;
end;
то массив действительно займёт 10000 байт. ПОЧЕМУ?! Тот, кто не знает asm’а, будет долго ломать голову над этой проблемой. А вот ассемблерщик быстро представит, во ЧТО может транслировать этот код компилятор – и сразу поймёт, почему первый вариант требует больше памяти. Соответственно, сумеет он и сгенерировать эффективный код. На «закуску» - дополнительный пример. Объяснить, почему код:
Код:
for (a=0;a<g;a++)
{
 a1+=bar[4096];
 a2+=bar[4096*2];
 a3+=bar[4096*3];
 a4+=bar[4096*4];
 a5+=bar[4096*5];
}
исполняется в 6 раз медленнее кода:
Код:
for (a=0;a<g;a++) {
 a1+=bar[4096];
 a2+=bar[4096*2];
 a3+=bar[4096*3];
}
for (a=0;a<g;a++)
{
 a4+=bar[4096*4];
 a5+=bar[4096*5];
}
Старый 10.04.2006, 15:21
NETRAT

offline
Опыт: 83,712
Активность:
Может быть это связяно с выходом числа 4096*4 за границы некоторого сегмента?
Вообще код, конечно извратный, не думаю что я стал бы так что-нить писать, находясь в здравом уме - ибо его читать неприятно

Как оно будет выглядеть в асме...
Цикл
a в регистр
a-g
Проверка флагов

a1 в регистр
4096 в индексный регистр
a1+индексный регистр
a1 в память

a2 в регистр
8192 в индексный регистр
a2+индексный регистр
a2 в память

a3 в регистр
12288 в индексный регистр
a3+индексный регистр
a3 в память

a4 в регистр
16384 в индексный регистр
a4+индексный регистр
a4 в память

a5 в регистр
20480 в индексный регистр
a5+индексный регистр
a5 в память

inc a
джамп

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

Я вот что подумал - ты же тут сам подсказку дал, вероятно, память выделяется сегментами кратными степени двойки(а может 4 или больше), то есть теоретически понятно почему по записи выделяется больше места чем им положено - ибо меньше просто нельзя - на размещение этих записей не хватит места. Так вот, под целостные сегменты, коими у нас и являются массивы, выделяется непрерывный сегмент памяти(ибо адресация прямая). То есть очевидно что массив записей не может занимать меньше места чем набор массивов для каждого из полей записи.

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

NETRAT добавил:
Интересно что чем круче компьютеры, тем меньше внимания уделяется скорости и оптимальности работы - то есть и так "проглотит", иногда лучше написать хоть как-то, а потом оптимизировать. То есть на первый план выходит high-level, ибо мощности позволяют наворотить удобные среды, которые могут сами себя контроллировать за счет потери быстродействия. Тенденция, однако...

NETRAT добавил:
Да, то что память выделяется сегментами с размером, кратным степени двойки - это очевидно, по другому просто и быть не может - ибо адресация памяти была бы невозможна(или затруднена или неоптимальна), думаю, что это все обьясняет

NETRAT добавил:
Я о том, что как ни крути, однако код на асме можно лучше оптимизировать.
Кстати, помницца, я еще на втором курсе научился использовать ассемблерные вставки в Дельфе и в С...
Старый 11.04.2006, 03:35
Алексей
Где кошачья мята?!
offline
Опыт: 26,303
Активность:
Да, память действительно выделяется блоками, размер которых кратен степени 2. Хотя на asm можно выделять блок произвольного размера. Тут причина ещё в выравнивании: доступ к данным, выровненным на величину, кратную их размеру, осуществляется быстрее.
Поэтому все современные компиляторы выравнивают данные. Переменные, занимающие 1 байт, могут располагаться по любому адресу. 2-байтовые переменные располагаются только по чётным адресам. 4-байтовые - по адресам, делящимся на 4. За счёт перерасхода памяти достигается значительное увеличение быстродействия. Поэтому объявление вида:
a:byte;
b:integer;
b:byte;
займёт в памяти не (4+2), а (4+4+1), т.к. между a и b компилятор оставит 3 пустых байта - для выравнивания. А вот если объявить
b:integer;
a,b:byte;
то всё "уляжется" вплотную.
Впрочем, всё сказанное относится только к глобальным переменным. Локальные переменные - разговор особый (там всё куда сложнее).
.
Вообще, чтение данных из оперативной памяти осуществляется ОЧЕНЬ медленно. Если тактовые частоты процессоров уже давно перевалили за 3000МГц, то памяти - всего 200МГц.
Вот как осуществляется чтение данных из памяти (растактовка из документации):
  1. Получив запрос на чтение ячейки, процессор выполняет арбитраж и передаёт чипсету адрес и длину запрашиваемого блока памяти. При условии, что шина свободна, эта операция укладывается в 4 такта.
  2. Контроллер шины, получив запрос, ставит его в очередь и, если контроллер памяти свободен, передаёт ему запрос с началом следующего такта.
  3. В течение следующего такта контроллер памяти декодирует адрес и ставит его в свою внутреннюю очередь запросов на чтение памяти.
  4. В следующем такте запрос извлекается из очереди, и контроллер, при необходимости дождавшись прихода фронта тактового импульса микросхемы памяти, передаёт ей адрес ячейки:
    1. Если соответствующая страница открыта и банк памяти не находится на регенерации, то чипсет выставляет сигнал CAS и передаёт сокращённый адрес ячейки. Спустя 2-3 такта частоты памяти на шине появляется первая порция считанных данных.
    2. Контроллер памяти считывает её за 1 такт. Дальнейшее поведение контроллера зависит от его типа. Синхронный контроллер с началом следующего такта передаёт считанные данные контроллеру шины и в дальнейшем пересылка осуществляется параллельно с чтением, но с задержкой в 1 такт. Асинхронный контроллер памяти "благодаря" расхождению частот не может передавать данные одновременно с чтением, и вынужден накапливать их во временном буфере. После завершения пакетного цикла чтения контроллер памяти по приходу фронта следующего синхроимпульса начинает передавать содержимое временного буфера контроллеру шины на требуемой частоте.
      <...опускаю пару примечаний, касающихся дешёвых моделей контроллеров и чипсетов...>
    3. На чтение "хвоста" (tail) пакета в зависимости от его длины уходит ещё 3 или 7 тактов частоты оперативной памяти.
    4. Если длина запроса превышает длину пакета, вернуться к пункту I.
    5. Контроллер шины, получив считанные данные, формирует запрос на передачу данных от чипсета к процессору и ставит его в очередь, на что расходуется 1 такт.
    6. Если в очереди не находится ничего другого и шина ничем не занята, контроллер шины извлекает запрос из очереди и выставляет его на шину, передавая за один такт одну, две или четыре порции данных (на K6/P-II/PIII, Athlon и P-4 соответственно).
    7. Как только запрошенная ячейка попадает в процессор, она становится немедленно доступной, даже если пакетный цикл ещё не завершён
      <...>
  5. Если требуемая DRAM-страница закрыта, но банк не находится на регенерации, контроллер памяти передаёт адрес строки, вырабатывает сигнал RAS, ждёт 2-3 такта, пока микросхема его обработает и переходит к п. I.
  6. Если же банк находится на регенерации, контроллер ждёт 1-3 такта, пока она не завершится.
Впечатляет? Да, оперативка - весьма тормозное устройство. К счастью, реально процессор обращается к ней нечасто - почти все данные оседают в кэше, который реализован на триггерах, интегрирован в процессор и работает очень быстро.
Современные процессоры пользуются четырёхассоциативным кэшем. И вышеуказанный цикл построен так, что "заваливает" кэш мусором и процессору всё время приходится ждать, пока данные считаются из оперативки. Отсюда - 6-кратное падение производительности (кэш не может хранить более 4 ячеек с одинаковыми "установочными" адресами – т.е. адресами, отличающимися на n*4096).
Код на ASM действительно великолепно поддаётся оптимизации, но это уже требует глубокого знания архитектуры процессора.
Старый 11.04.2006, 10:48
NETRAT

offline
Опыт: 83,712
Активность:
Да, вот как раз триггерами мы сейчас и занимаемся... Булева временная логика...
Такие сложности в основном делаются для повышения надежности "механизма", ибо если отдельно рассматривать элементы такой схемы, то становится понятно почему без дополнительных проверок не обойтись - ибо есть вероятность сбоя, потери данных, ошибки, а в ядре это должно быть исключено - чревато

NETRAT добавил:
Именно поэтому хай-левел работает без глюков...
Старый 11.04.2006, 14:10
Ответ

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск

Ваши права в разделе
Вы не можете создавать темы
Вы не можете отвечать на сообщения
Вы не можете прикреплять файлы
Вы можете скачивать файлы

BB-коды Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход



Часовой пояс GMT +3, время: 21:07.