Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
дз №1, группа ФН2-101, Голубева.doc
Скачиваний:
13
Добавлен:
09.02.2015
Размер:
457.73 Кб
Скачать

2.5. Представить программу, добавление в которой отладочного оператора печати влияет на результаты вычислений.

В качестве примера для демонстрации возникновения ситуации, в которой добавление в программу отладочного оператора печати влияет на результат вычислений, выберем неустойчивый алгоритм нахождения корня квадратного уравнения

(1)

где (2)

Корни уравнения (1) будем определять по формулам:

В качестве начальных данных положим т.е. решим уравнение

удовлетворяющее условиям (2), наложенным на уравнение (1).

Ниже приведен листинг программы, реализованной на языке С++ в кроссплатформенной свободной IDE "QT Creator", осуществляющей поиск корня :

Результатом работы программы является окно с данными, скриншот которого приведен далее:

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

Задание 3.

1.5. Множество вещественных чисел ЭВМ можно представить в виде множества Форсайта где - основание системы счисления, - точность (разрядность мантиссы) и - интервал значений показателя. Выписать формулу, определяющую значения максимального элемента

Любое число с плавающей запятой можно представить в виде

(3)

где и

Тогда значения максимального элемента множества вещественных чисел, представленные через множество Форсайта, будут определяться формулой:

(4)

которая следует из формулы (3) при и

Формулу (4) можно переписать в виде:

тогда будем искать в виде суммы первого члена геометрической прогрессии, для которой знаменатель прогрессии

Таким образом, значения максимального элемента будут определяться по формуле:

Задание 4.

Перечислить алгоритмы оптимизации в трансляторах с алгоритмических языков, приводящие, иногда, к неоднозначным результатам счета объектных программ.

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

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

Среди факторов, влияющих на оптимизацию, отмечаются как вычислительные характеристики целевой машины (например, количество и тактовая частота процессорных ядер, размер процессорного кэша, пропускная способность системной шины, объём оперативной памяти), так и архитектура целевого процессора (в частности, в разных архитектурах доступно различное количество регистров общего назначения, по-разному реализован вычислительный конвейер). Другой класс факторов, влияющих на оптимизацию — сценарии использования целевого программного кода, например, целевые характеристики оптимизации могут значительно отличаться для кода, предназначенного отладки и тестирования, для запуска время от времени, для постоянного использования, для применения во встраиваемых или автономных системах.

Среди принципов оптимизации, применяемых в различных методах оптимизации в компиляторах (некоторые из них могут противоречить друг другу или быть неприменимыми при разных целях оптимизации) различают:

  • уменьшение избыточности: повторное использование результатов вычислений, сокращение числа перевычислений;

  • компактификация кода: удаление ненужных вычислений и промежуточных значений;

  • сокращение числа переходов в коде: например, использование встраивания функций (англ. Inline expansion) или размотки цикла позволяет во многих случаях ускорить выполнение программы ценой увеличения размера кода;

  • локальность: код и данные, доступ к которым необходим в ближайшее время, должны быть размещены рядом друг с другом в памяти, чтобы следовать принципу локальности ссылок(англ. Locality of reference);

  • использование иерархии памяти: размещать наиболее часто используемые данные в регистрах общего назначения, менее используемые — в кэш, ещё менее используемые — в оперативную память, наименее используемые — размещать на диске.

  • распараллеливание: изменение порядка операций может позволить выполнить несколько вычислений параллельно, что ускоряет исполнение программы.

Подробнее рассмотрим Visual C++ compiler и примеры алгоритмов оптимизации в трансляторе, приводящие к ошибочным результатам при компиляции.

Параметры компилятора обеспечивают управление различными способами оптимизации, предназначенными для создания кода максимальной производительности или минимального размера.

В следующей таблице описываются параметры /O1 и /O2.

Параметр

Эквивалентен

Комментарий

/O1(минимизировать размер)

/Og /Os /Oy/Ob2 /Gs /GF/Gy

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

/O2(максимизировать скорость)

/Og /Oi /Ot/Oy /Ob2 /Gs/GF /Gy

В большинстве случаев создает более быстрый код (используется по умолчанию).

Параметры /O1 и /O2 также включают оптимизацию возврата именованных значений, благодаря чему уменьшается количество вызовов конструкторов копирования и деструкторов временных объектов, хранящихся в стеке.

Пример 1.

Проблема:

Предполагается компилировать файл с исходным кодом C в 64-разрядный двоичный файл с помощью компилятора Visual C/C++ (Cl.exe) в Microsoft Visual Studio 2010. Если включен параметр оптимизации /o1/O1 (минимизировать размер) или параметра оптимизации /o2/O2 (максимизировать скорость), может появиться сообщение об ошибке, подобное приведенному ниже, при попытке компиляции файла:

Неустранимая ошибка C1001: Внутренняя ошибка в компиляторе. ("путь и имя файла[0x0000000054BA3E3C:0x0000000000000044]", созданного компилятором файла строки номер строки)

Примечания:

Эта проблема возникает только при компиляции файла из исходного кода на 64-разрядный двоичный файл с / O1 или/O2 оптимизации параметр включен. Эта проблема не возникает при отключении обоих параметров оптимизации или при компиляции файла исходного кода для 32-разрядных двоичных файлов.

Пример 2.

Проблема:

Операторы побитового сдвига возвращают неверные результаты при запуске приложения, скомпилированного с помощью Visual C++ 2010 компилятор x86 с параметром  /o2/O2 (максимизирование скорости) или оптимизацией /Ox (полная оптимизация).

Эту проблему демонстрирует следующий код:

#include <stdio.h>

#include <memory.h>

int main(int argc, char ** argv)

{

short lActualOutput;

const char * mBytes;

char x[5];

char lBuffer[2];

for (int i = 0; i < 5; i++)

{

x[i] = 97+i;

}

mBytes=&x[3];

size_t aSize = sizeof(lBuffer);

memcpy(lBuffer, mBytes, aSize);

mBytes += aSize;

lActualOutput = (lBuffer[0] << 8) + lBuffer[1];

fprintf(stdout, "lActualOutput= 0x%04X \n", lActualOutput);

}

Получается неправильный следующий результат:

lActualOutput = 0x6464

Ожидаемый результат выглядит следующим образом:

lActualOutput = 0x6465

Пример 3.

Проблема:

Неправильный код компьютера создается для оператора «switch» в x64 компилятора Visual C++ 2010 при включении компилятора  /Ob1 (Only_inline), /o1/O1 (минимизировать размер), /o2/O2 (максимизировать скорость), /Ox (полная оптимизация) или параметра оптимизации компилятора /Og (виды глобальной оптимизации).

Эту проблему демонстрирует следующий код:

#include <stdio.h>

int test(int bps, int sflags)

{

if (sflags & (1 << bps))

{

switch (bps)

{

case 1: return 1;

case 2: return 3;

default: return 0;

}

}

else

{

switch (bps)

{

case 1: return 2;

default: return 0;

}

}

}

void main()

{

int res = test(1, -1);

printf("%d\n", res);

}

Ожидаемый результат: 1. Фактический результат равен 0. 

10