Урок 7
Добро пожаловать на урок номер 7. Темой сегодняшнего урока является плавающая запятая в BASM. Это уже было темой в более раннем уроке, но этот урок даст дополнительную информацию. Мы посмотрим, как кодировать скаляры на SSE2 и как инструкции обслуживаются в конвейерах FP. Сегодняшний пример это расчет полинома третьего порядка.
Code: |
function ArcSinApprox1a(X, A, B, C, D : Double) : Double; begin Result := A*X*X*X + B*X*X + C*X + D; end; |
Вместо анализа и оптимизации этой функции мы посмотрим, как реально мы можем ее использовать. Полином третьего порядка может аппроксимировать функцию в ее интервале, [-1;1], с максимальной абсолютной ошибкой 0.086. Это не очень высокая точность, но то что мы разработаем в данном уроке можно будет расширить до более высоких порядков, в той же манере для получения большей точности.
Параметры A, B, C и D определяют форму кривой для функции и значения для аппроксимации в ArcSin с минимальной ошибкой. Для этой цели мы разработаем оптимизатор, который будет использоваться для измерения производительности. Поскольку ARCSIN(0) = 0 мы непосредственно видим, что D=0 и D можно вывести из оптимизации. Мы также знаем, что ArcSin это нечетная функция и поэтому выражение второго порядка B*X*X не используется в аппроксимации. Это поскольку выражение второго порядка четное и симметрично относительно оси Y. Функции нечетных порядков имеют анти симметрию вокруг оси Y с F(X) = -F(-X). Все это означает, что наша функция может быть уменьшена до
Result := A*X*X*X + C*X;
Тем не менее, мы не поступим так, поскольку это будет более наглядно с полной функцией. ArcSin это особый случай, и мы хотим сделать его обычным, насколько это возможно.
В функции номер 1a имеется 6 умножений и три сложения. Напишем ее в виде формы Хорнера (Horner form).
Result := ((A*X + B)*X + C)*X + D;
Уменьшив этим до трех умножений и сложений.
Другая форма такая
Result := (A*X + B)*(X*X)+(C*X + D);
Здесь четыре умножения и три сложения.
На современных процессорах очень важно распараллеливания можно извлечь из формулы и как много умножений и сложений она имеет. Современные процессоры, такие как AMD Athlon, Intel P4 и P3 имеют конвейеры. Конвейеры необходимы на процессорах, работающих на высокой частоте, поскольку основные операции сложения, вычитания, умножения или деления не могут быть выполнены за один такт частоты. На P4 есть конвейер называемый FP_ADD, который предназначен для операций сложения и вычитания. Этот конвейер имеет 5 состояний, это означает, что процесс сложения или вычитания может быть разбит на 5 подзадач. Следовательно, сложение и вычитание выполняются за 5 тактов. Преимущество конвейера состоит в том, что хотя операция требует 5 тактов, но зато каждая новая операция может начинаться в каждом такте. Это потому что первое сложение покидает первую подзадачу при втором такте и эта подзадача может начинать сложение для второго числа. Если мы имеем серию сложений, то первое сложение покидает конвейер на такте 5, второе на такте 6 и так далее. Производительность Throughput получается всего в один такт. Параллельность составляет до 5 сложений или вычитаний в конвейере одновременно. Проблема в том, что если второе или следующие сложения связаны с первым сложением, то придется ожидать, когда закончится первое сложение. Мы можем сказать, что здесь есть зависимость данных между двумя инструкциями, и мы видим, что полная латентность для сложения составляет 2 раза по 5 тактов.
Посмотрим на основе нашей функции работу конвейера.
Result := A*X*X*X + B*X*X + C*X + D;
Также видно, что четвертое выражение может выполняться параллельно, и затем сложено в конце действия. A*X это первая инструкция, готовая для обработки в конвейере F_MUL. Латентность для FMUL на P4 составляет 7 тактов и выражение A*X будет готово через 7 тактов. FMUL имеет максимальную пропускную способность (throughput) в 2 такта. Отсюда ясно, что FMUL не полностью конвейеризирован. Конвейер принимает новую инструкцию на такте три, а не на втором. B*X это вторая инструкция, готовая к выполнению и процессор начнет ее выполнение на такте 3. В такте 5 конвейер снова готов к принятию новой инструкции и это будет инструкция C*X. В такте 7 выполнение инструкции A*X будет закончено и выражение (A*X)*X можно будет начать вычислять в такте 8. В такте 10 вычисление выражения B*X будет закончено и процессор начнет выполнению выражения (B*X)*X. В такте 12 также будет закончено выполнение C*X и конвейер F_ADD прибавит значение D. В такте 15 будет закончено вычисление (A*X)*X и можно будет начинать выражение (A*X*X)*X. В такте 17 выражения (B*X)*X и (C*X) + D будут закончены и можно начать работу с конвейером F_ADD. Данное сложение будет закончено на такте 21, где выражение (A*X*X)*X также будет готово. Последнее сложение можно будет начать на такте 22. Осталась только одна операция в действии, и мы должны подождать до полной латентности FADD, которая составляет 5 тактов. На такте 27 последнее сложение будет закончено и работа будет выполнена.
Данные таблицы покажут это в деталях. Левая колонка символизирует конвейер F_MUL , с 7 состояниями 7, а правая конвейер F_ADD на 5 состояний.
F_MUL |
F_ADD |
A*X |
|
|
|
|
|
|
|
|
|
|
|
|
Просьба писать ваши замечания, наблюдения и все остальное,
что поможет улучшить предоставляемую информацию на этом сайте.
ВСЕ КОММЕНТАРИИ МОДЕРИРУЮТСЯ ВРУЧНУЮ, ТАК ЧТО СПАМИТЬ БЕСПОЛЕЗНО!