Целью работы является освоение способов оптимизации программ на этапе компиляции для RISC-V.
Для достижения поставленной цели требуется решить следующие задачи:
Презентация к блоку "Оптимизации в компиляторах и отладка"
Работа программиста, использующего языки высокого уровня, часто сопряжена с определенным уровнем противоречий. С одной стороны, разработчик должен писать легко читаемые и легко поддерживаемые программы, что выражается в использовании высокоуровневых синтаксических конструкций. С другой стороны, программа должна эффективно работать с учетом конкретного аппаратного обеспечения пользовательского компьютера, что требует учета низкоуровневых особенностей исполнения. Данное противоречие частично разрешается компиляторами - они не просто выполняют переход от языков высокого уровня к машинным инструкциям, но и стараются выбрать наиболее эффективный способ данного перехода. Однако, в ряде случаев, им требуется дополнительная настройка для достижения максимального эффекта. В таком случае применяются методы оптимизации на уровне компилятора и техники инструментирования. Данные инструменты регулируются флагами компилятора. Для определенности, далее мы будем рассматривать тему применительно к компилятору GCC, однако подобные инструменты также есть и в компиляторе LLVM.
Оптимизации на уровне компилятора представляют собой дополнительные процедуры по выбору оптимального преобразования исходного кода программы в машинные инструкции так, чтобы достигнуть максимального значения параметра производительности (время работы и/или объем исполняемого файла). При этом, в зависимости от выбранной интенсивности, компилятор может применять достаточно агрессивные эвристики для оптимизации, что может привести к ошибкам и сбоям в работе программы (например, многопоточные ошибки и ошибки обращения с памятью). Наиболее важные флаги оптимизации для GCC [10]:
-O0 - тривиальный случай, когда оптимизация компилятора не выполняется. Тем не менее оптимизация для конкретного языка в соответствии с требованиями стандарта по-прежнему выполняется. Например, вычисления во время компиляции, требуемые стандартом C++, по-прежнему выполняются.-O1 - на этом уровне включается множество оптимизаций, повышающих производительность программы. Например, развертывание циклов (loop unrolling), встраивание функций (inline functions), планирование инструкций (instruction planning) и т. д.-O2 позволяет использовать все оптимизации -O1, а также более агрессивные оптимизации в распределении регистров, планировании инструкций, частичном устранении избыточности и т. д. Этот уровень используется при построении кода с преобладанием ветвлений, например, операционных систем.-O3. Этот уровень включает в себя все возможности -O2, а также некоторые современные оптимизации, такие как векторизация. -O3 является уровнем оптимизации для максимизации производительности большинства приложений.-Ofast или -O3 с -ffast-math. Флаг -ffast-math указывает компилятору ослабить некоторые требования арифметики с плавающей запятой, такие как ассоциативность и коммутативность. Во многих приложениях ошибки, возникающие после ослабления этих требований, вполне допустимы за счёт более высокой производительности. Без -ffast-math многие циклы с операциями с плавающей точкой не могут быть векторизованы. -Os оптимизирует размер кода. Таким образом, большинство оптимизаций, увеличивающих размер кода, будут менее агрессивными на этом уровне. Это популярная оптимизация среди встраиваемых систем и мобильных приложений, поскольку размер кода там является большой проблемой. -Wl,--strip-all (или не передавать флаг -g): этот флаг указывает компоновщику удалить раздел отладки. -fno-unroll-loops: отключает развертывание цикла, которое является одной из популярных оптимизаций производительности компилятора, увеличивающей размер кода. -fno-exceptions: удаляет код обработки исключений из двоичного файла. Обратите внимание, что это не всегда возможно, если есть код, который их "бросает". -lto (-flto): включение оптимизации времени компоновки с параметром -flto приводит к агрессивной оптимизации компилятора. Оптимизируются многие функции и глобальные переменные, девиртуализируются многие вызовы. Полученный двоичный файл быстрее и меньше одновременно. Могут быть значительные накладные расходы во время компиляции. Описанные выше флаги для оптимизации не являются панацеей - они действуют на всю программу целиком и не учитывают особенностей ее использования, продиктованных действиями пользователя и структурой взаимодействия с ним. Более эффективные оптимизации могут быть достигнуты применением инструментирования - методик, внедряющих дополнительный код в бинарный файл для сбора технической информации о производительности (профиля работы) программы в ходе ее работы. Данная информация может быть передана компилятору чтобы повысить эффективность оптимизаций. Для инструментирования применяются следующие флаги:
-g, чтобы иметь возможность отлаживать приложение с аннотациями исходного кода, компилятор должен предоставить дополнительную информацию в двоичном файле. Флаг -g указывает компилятору сделать это. Без этого флага отладчик будет показывать только имена глобальных символов и дизассемблер, поскольку он не может связать строку исходного кода со сборкой.-finstrument-functions. Этот флаг используется для инструментирования входа и выхода функций. Инструментирование позволяет получить представление о поведении программ. При использовании этого флага также необходимо определить две функции __cyg_profile_func_enter и __cyg_profile_func_exit, которые вызываются соответственно при входе и выходе из каждой вызываемой функции. Если есть функции, которые не должны быть инструментированы, к ним можно добавить __attribute__ ((no_instrument_function)).-fprofile-generate, -fprofile-arcs, -pg .Эти флаги используются для инструментирования программ с целью сбора профилей времени выполнения различных точек программы. Это позволяет компилятору проводить оптимизацию с учетом профиля в последующих компиляциях. В зависимости от того, какие флаги вы используете, могут быть достигнуты различные типы инструментирования. Подробный обзор различных флагов приведен на странице руководства.-fstack-protector, -fstack-protector-all, -fstack-protector-strong. Эти опции инструментируют уязвимые функции путем вставки защитных переменных в кадр стека. Перед возвратом функции проверяется, что защитная переменная не была перезаписана, что позволяет убедиться в том, что стек не был поврежден. Это тривиальный способ улучшить защиту от атаки на переполнение буфера. Однако это может увеличить размер кода приложения. В случае, если это создает накладные расходы, с этим флагом можно компилировать только критически важные для безопасности части приложения.Рассмотрим пример использования инструментирования для сбора профиля работы программы. Пример приведен для RISC-V ОС, однако его можно повторить и в гостевой ОС, используя команды для кросс-компиляции. Первое действие это компиляция с флагом -fprofile-generate:
$ gcc -O2 -fprofile-generate=/path/to/outputfile test.c -o a.out
Далее необходимо запустить программу (a.out) несколько раз для сбора профиля:
$ ./a.out
Профиль работы программы в виде файла с расширением .gcda будет сохранен в каталог /path/to/outputfile. После этого можно запустить компиляцию исходного кода с флагом -fprofile-use для дополнительной оптимизации:
$ gcc -O2 -fprofile-use=/path/to/outputfile test.c -o a.out
Важно понимать, что с одной стороны, инструментирование позволяет собрать больше информации о работе ПО, но с другой - за счет внедрения инструментального кода сам процесс измерения может повлиять на измеряемые величины или даже привести к сбоям в работе ПО. Для получения более детальной картины возможно применение внешних инструментов измерения производительности - профилировщиков, например perf, valgrind, callgrind, cachegrind. Такие профилировщики позволяют собрать информацию о самых низкоуровневых особенностях работы программы - эффективность работы кэш-памяти, качество предсказаний переходов, статистика работы с динамической памятью.
Для того, чтобы убедится в эффективности проводимых оптимизаций, необходимо использовать инструменты для измерения скорости работы программы, а также объема бинарных файлов. Выбор конкретных утилит, а также технологий измерения зависит от множества различных факторов. Например, для измерения времени работы программы:
Данные вопросы подробно изучаются в дисциплине "Метрология ПО". В данной работе мы предлагаем грубый и быстрый способ оценки эффекта ускорения работы ПО от оптимизаций.
Для неответственных измерений времени работы программы в ОС на базе Linux можно использовать утилиту time. Она измеряет время работы программы с момента запуска и до завершения, а также выводит детализацию. Для измерения времени работы скомпилированной ранее программы используйте команду:
$ time ./a.out
Вывод имеет вид:
# Вывод программы a.out … real 0m0,020s user 0m0,005s sys 0m0,015s
где real - общее время работы программы, user - время работы программы в режиме пользователя, sys - время работы программы в режиме ядра (использование системных вызовов и низкоуровневых операций ОС).
Важно отметить, что единичное измерение не обладает ценностью - для оценки производительности важны статистические (множественные наблюдения), проводимые в воспроизводимых условиях. Поэтому, необходимо сделать множество измерений в одинаковых условиях. Также, для оценки эффекта необходимо проводить измерения базового уровня (baseline) - показателей программы до оптимизации. Таким образом, примерный алгоритм измерений будет иметь вид:
Для автоматизации процесса измерений вы можете использовать уже готовую утилиту multitime - она дублирует функции time, а также умеет подводить статистику нескольких последовательных запусков. Например, команда для проведения десяти запусков и вычисления статистики:
$ multitime -n 10 ./a.out
Результат выполнения:
# Вывод программы a.out
…
===> multitime results
1: ./a.out
Mean Std.Dev. Min Median Max
real 0.016 0.002 0.012 0.016 0.021
user 0.007 0.005 0.000 0.007 0.013
sys 0.008 0.004 0.004 0.007 0.015
По умолчанию, multitime может быть не установлена в популярных Linux дистрибутивах (в том числе и в ОС Syntacore Kit), вы можете установить ее командами
$ apt update $ apt install multitime
Измерение эффекта от соответствующих оптимизаций аналогично описанному ранее алгоритму, за исключением того, что достаточно провести одно измерение объема файла. Для измерения самого объема бинарного файла можно использовать различные утилиты. Утилита командной строки ls позволяет понять общий размер файла в байтах без детализации о распределении объема:
$ ls -l a.out -rw-r--r-- 1 root root 13423 сен 26 2023 a.out
Значение в пятой колонке соответствует размеру файла в байтах. О значениях в прочих колонках можно подробнее узнать в справке.
Для более точной оценки можно использовать утилиту size. Она отображает размер каждой секции в рамках бинарного файла:
$ size a.out text data bss dec hex filename 1524 600 8 2132 854 a.out
Подготовьте исходный код программы на языке С, которая будет выполнять заданное вариантом действие со случайной целочисленной матрицей А размера 20 на 20. Необходимо самостоятельно скомпилировать программу с различными опциями для архитектуры RISC-V, а также убедится в работоспособности. Опции для компиляции:
С помощью флагов компилятора, постарайтесь добится наилучших характеристик для программ пунктов №1 и №2. Для полученных бинарных файлов с наилучшими результатами измерьте объем файла, а также время работы программы (количество измерений - 10).
Варианты заданий (действия для матрицы):
a[i][j] = a[i][j] * a[i][j]a[i][j] = a[i][j] % 9a[i][j] = a[i][j+1] + a[i][j-1]a[i][j] = a[i][j] * a[j][i]Примечание для преподавателей: данную лабораторную работу можно проводить не только в стандартном режиме, описанном выше, но и на соревновательной основе по аналогии с олимпиадами. Для этого предлагается следующий упрощенный алгоритм:
В качестве первого шага, необходимо реализовать программу согласно выбранному варианту. При реализации программы не забудьте реализовать вывод исходных данных и ответа в консоль. Выполним сборку программы без флагов оптимизации с помощью. компилятора gcc (или путем кросс компиляции, или запуская компилятор непосредственно в рамках RISC-V ОС). Программу необходимо отладить перед оптимизацией - с помощью данных меньшего объема и многократных запусков убедитесь, что она корректно справляется с поставленной задачей и успешно выполняется без сбоев и ошибок.
Далее, измерим исходные значения времени работы программы и объем бинарного файла с помощью утилит time / multitime и size. При измерении времени работы программы, убедитесь, что вы измеряете время работы программы, а не время ее запуска в рамках процесса qemu-riscv64:
$ qemu-riscv64 -L "$RISCV/sysroot" time ./hello # Корректное измерение $ time qemu-riscv64 -L "$RISCV/sysroot" ./hello # Некорректное измерение
Далее необходимо выполнить оптимизацию с помощью флагов компиляции. Мы рекомендуем увеличивать степень оптимизации постепенно, начиная с флага -O1, контролируя корректность работы самой программы по вводимым значениям и измеряя время работы. Аналогичные действия необходимо предпринять и для оптимизации объема бинарного файла. После окончания оптимизации необходимо повторить измерения и внести полученные данные в отчет.
Оптимизируйте время работы бинарного файла для программы суммирования остатков от деления на 3 для целочисленного случайного массива из 10 элементов. Предположим, что исходный код программы уже написан. Выполним сборку и измерим время работы на 10 запусках:
$ gcc main.c -o a.out
$ multitime -n 10 ./a.out
…
===> multitime results
1: ./a.out
Mean Std.Dev. Min Median Max
real 0.016 0.002 0.012 0.016 0.021
user 0.007 0.005 0.000 0.007 0.013
sys 0.008 0.004 0.004 0.007 0.015
Для начала оптимизации по времени рекомендуется выполнить компиляцию c флагом -O1 и оценить достигнутый эффект.
$ gcc -O1 main.c -o a.out
$ multitime -n 10 ./a.out
…
===> multitime results
1: ./a.out
Mean Std.Dev. Min Median Max
real 0.012 0.002 0.010 0.016 0.020
user 0.005 0.005 0.000 0.007 0.013
sys 0.008 0.004 0.004 0.007 0.015
Целью работы является освоение способов оптимизации программ на этапе компиляции для RISC-V.
Для достижения поставленной цели требуется решить следующие задачи:
Презентация к блоку "Оптимизации в компиляторах и отладка"
Работа программиста, использующего языки высокого уровня, часто сопряжена с определенным уровнем противоречий. С одной стороны, разработчик должен писать легко читаемые и легко поддерживаемые программы, что выражается в использовании высокоуровневых синтаксических конструкций. С другой стороны, программа должна эффективно работать с учетом конкретного аппаратного обеспечения пользовательского компьютера, что требует учета низкоуровневых особенностей исполнения. Данное противоречие частично разрешается компиляторами - они не просто выполняют переход от языков высокого уровня к машинным инструкциям, но и стараются выбрать наиболее эффективный способ данного перехода. Однако, в ряде случаев, им требуется дополнительная настройка для достижения максимального эффекта. В таком случае применяются методы оптимизации на уровне компилятора и техники инструментирования. Данные инструменты регулируются флагами компилятора. Для определенности, далее мы будем рассматривать тему применительно к компилятору GCC, однако подобные инструменты также есть и в компиляторе LLVM.
Оптимизации на уровне компилятора представляют собой дополнительные процедуры по выбору оптимального преобразования исходного кода программы в машинные инструкции так, чтобы достигнуть максимального значения параметра производительности (время работы и/или объем исполняемого файла). При этом, в зависимости от выбранной интенсивности, компилятор может применять достаточно агрессивные эвристики для оптимизации, что может привести к ошибкам и сбоям в работе программы (например, многопоточные ошибки и ошибки обращения с памятью). Наиболее важные флаги оптимизации для GCC [10]:
-O0 - тривиальный случай, когда оптимизация компилятора не выполняется. Тем не менее оптимизация для конкретного языка в соответствии с требованиями стандарта по-прежнему выполняется. Например, вычисления во время компиляции, требуемые стандартом C++, по-прежнему выполняются.-O1 - на этом уровне включается множество оптимизаций, повышающих производительность программы. Например, развертывание циклов (loop unrolling), встраивание функций (inline functions), планирование инструкций (instruction planning) и т. д.-O2 позволяет использовать все оптимизации -O1, а также более агрессивные оптимизации в распределении регистров, планировании инструкций, частичном устранении избыточности и т. д. Этот уровень используется при построении кода с преобладанием ветвлений, например, операционных систем.-O3. Этот уровень включает в себя все возможности -O2, а также некоторые современные оптимизации, такие как векторизация. -O3 является уровнем оптимизации для максимизации производительности большинства приложений.-Ofast или -O3 с -ffast-math. Флаг -ffast-math указывает компилятору ослабить некоторые требования арифметики с плавающей запятой, такие как ассоциативность и коммутативность. Во многих приложениях ошибки, возникающие после ослабления этих требований, вполне допустимы за счёт более высокой производительности. Без -ffast-math многие циклы с операциями с плавающей точкой не могут быть векторизованы. -Os оптимизирует размер кода. Таким образом, большинство оптимизаций, увеличивающих размер кода, будут менее агрессивными на этом уровне. Это популярная оптимизация среди встраиваемых систем и мобильных приложений, поскольку размер кода там является большой проблемой. -Wl,--strip-all (или не передавать флаг -g): этот флаг указывает компоновщику удалить раздел отладки. -fno-unroll-loops: отключает развертывание цикла, которое является одной из популярных оптимизаций производительности компилятора, увеличивающей размер кода. -fno-exceptions: удаляет код обработки исключений из двоичного файла. Обратите внимание, что это не всегда возможно, если есть код, который их "бросает". -lto (-flto): включение оптимизации времени компоновки с параметром -flto приводит к агрессивной оптимизации компилятора. Оптимизируются многие функции и глобальные переменные, девиртуализируются многие вызовы. Полученный двоичный файл быстрее и меньше одновременно. Могут быть значительные накладные расходы во время компиляции. Описанные выше флаги для оптимизации не являются панацеей - они действуют на всю программу целиком и не учитывают особенностей ее использования, продиктованных действиями пользователя и структурой взаимодействия с ним. Более эффективные оптимизации могут быть достигнуты применением инструментирования - методик, внедряющих дополнительный код в бинарный файл для сбора технической информации о производительности (профиля работы) программы в ходе ее работы. Данная информация может быть передана компилятору чтобы повысить эффективность оптимизаций. Для инструментирования применяются следующие флаги:
-g, чтобы иметь возможность отлаживать приложение с аннотациями исходного кода, компилятор должен предоставить дополнительную информацию в двоичном файле. Флаг -g указывает компилятору сделать это. Без этого флага отладчик будет показывать только имена глобальных символов и дизассемблер, поскольку он не может связать строку исходного кода со сборкой.-finstrument-functions. Этот флаг используется для инструментирования входа и выхода функций. Инструментирование позволяет получить представление о поведении программ. При использовании этого флага также необходимо определить две функции __cyg_profile_func_enter и __cyg_profile_func_exit, которые вызываются соответственно при входе и выходе из каждой вызываемой функции. Если есть функции, которые не должны быть инструментированы, к ним можно добавить __attribute__ ((no_instrument_function)).-fprofile-generate, -fprofile-arcs, -pg .Эти флаги используются для инструментирования программ с целью сбора профилей времени выполнения различных точек программы. Это позволяет компилятору проводить оптимизацию с учетом профиля в последующих компиляциях. В зависимости от того, какие флаги вы используете, могут быть достигнуты различные типы инструментирования. Подробный обзор различных флагов приведен на странице руководства.-fstack-protector, -fstack-protector-all, -fstack-protector-strong. Эти опции инструментируют уязвимые функции путем вставки защитных переменных в кадр стека. Перед возвратом функции проверяется, что защитная переменная не была перезаписана, что позволяет убедиться в том, что стек не был поврежден. Это тривиальный способ улучшить защиту от атаки на переполнение буфера. Однако это может увеличить размер кода приложения. В случае, если это создает накладные расходы, с этим флагом можно компилировать только критически важные для безопасности части приложения.Рассмотрим пример использования инструментирования для сбора профиля работы программы. Пример приведен для RISC-V ОС, однако его можно повторить и в гостевой ОС, используя команды для кросс-компиляции. Первое действие это компиляция с флагом -fprofile-generate:
$ gcc -O2 -fprofile-generate=/path/to/outputfile test.c -o a.out
Далее необходимо запустить программу (a.out) несколько раз для сбора профиля:
$ ./a.out
Профиль работы программы в виде файла с расширением .gcda будет сохранен в каталог /path/to/outputfile. После этого можно запустить компиляцию исходного кода с флагом -fprofile-use для дополнительной оптимизации:
$ gcc -O2 -fprofile-use=/path/to/outputfile test.c -o a.out
Важно понимать, что с одной стороны, инструментирование позволяет собрать больше информации о работе ПО, но с другой - за счет внедрения инструментального кода сам процесс измерения может повлиять на измеряемые величины или даже привести к сбоям в работе ПО. Для получения более детальной картины возможно применение внешних инструментов измерения производительности - профилировщиков, например perf, valgrind, callgrind, cachegrind. Такие профилировщики позволяют собрать информацию о самых низкоуровневых особенностях работы программы - эффективность работы кэш-памяти, качество предсказаний переходов, статистика работы с динамической памятью.
Для того, чтобы убедится в эффективности проводимых оптимизаций, необходимо использовать инструменты для измерения скорости работы программы, а также объема бинарных файлов. Выбор конкретных утилит, а также технологий измерения зависит от множества различных факторов. Например, для измерения времени работы программы:
Данные вопросы подробно изучаются в дисциплине "Метрология ПО". В данной работе мы предлагаем грубый и быстрый способ оценки эффекта ускорения работы ПО от оптимизаций.
Для неответственных измерений времени работы программы в ОС на базе Linux можно использовать утилиту time. Она измеряет время работы программы с момента запуска и до завершения, а также выводит детализацию. Для измерения времени работы скомпилированной ранее программы используйте команду:
$ time ./a.out
Вывод имеет вид:
# Вывод программы a.out … real 0m0,020s user 0m0,005s sys 0m0,015s
где real - общее время работы программы, user - время работы программы в режиме пользователя, sys - время работы программы в режиме ядра (использование системных вызовов и низкоуровневых операций ОС).
Важно отметить, что единичное измерение не обладает ценностью - для оценки производительности важны статистические (множественные наблюдения), проводимые в воспроизводимых условиях. Поэтому, необходимо сделать множество измерений в одинаковых условиях. Также, для оценки эффекта необходимо проводить измерения базового уровня (baseline) - показателей программы до оптимизации. Таким образом, примерный алгоритм измерений будет иметь вид:
Для автоматизации процесса измерений вы можете использовать уже готовую утилиту multitime - она дублирует функции time, а также умеет подводить статистику нескольких последовательных запусков. Например, команда для проведения десяти запусков и вычисления статистики:
$ multitime -n 10 ./a.out
Результат выполнения:
# Вывод программы a.out
…
===> multitime results
1: ./a.out
Mean Std.Dev. Min Median Max
real 0.016 0.002 0.012 0.016 0.021
user 0.007 0.005 0.000 0.007 0.013
sys 0.008 0.004 0.004 0.007 0.015
По умолчанию, multitime может быть не установлена в популярных Linux дистрибутивах (в том числе и в ОС Syntacore Kit), вы можете установить ее командами
$ apt update $ apt install multitime
Измерение эффекта от соответствующих оптимизаций аналогично описанному ранее алгоритму, за исключением того, что достаточно провести одно измерение объема файла. Для измерения самого объема бинарного файла можно использовать различные утилиты. Утилита командной строки ls позволяет понять общий размер файла в байтах без детализации о распределении объема:
$ ls -l a.out -rw-r--r-- 1 root root 13423 сен 26 2023 a.out
Значение в пятой колонке соответствует размеру файла в байтах. О значениях в прочих колонках можно подробнее узнать в справке.
Для более точной оценки можно использовать утилиту size. Она отображает размер каждой секции в рамках бинарного файла:
$ size a.out text data bss dec hex filename 1524 600 8 2132 854 a.out
Подготовьте исходный код программы на языке С, которая будет выполнять заданное вариантом действие со случайной целочисленной матрицей А размера 20 на 20. Необходимо самостоятельно скомпилировать программу с различными опциями для архитектуры RISC-V, а также убедится в работоспособности. Опции для компиляции:
С помощью флагов компилятора, постарайтесь добится наилучших характеристик для программ пунктов №1 и №2. Для полученных бинарных файлов с наилучшими результатами измерьте объем файла, а также время работы программы (количество измерений - 10).
Варианты заданий (действия для матрицы):
a[i][j] = a[i][j] * a[i][j]a[i][j] = a[i][j] % 9a[i][j] = a[i][j+1] + a[i][j-1]a[i][j] = a[i][j] * a[j][i]Примечание для преподавателей: данную лабораторную работу можно проводить не только в стандартном режиме, описанном выше, но и на соревновательной основе по аналогии с олимпиадами. Для этого предлагается следующий упрощенный алгоритм:
В качестве первого шага, необходимо реализовать программу согласно выбранному варианту. При реализации программы не забудьте реализовать вывод исходных данных и ответа в консоль. Выполним сборку программы без флагов оптимизации с помощью. компилятора gcc (или путем кросс компиляции, или запуская компилятор непосредственно в рамках RISC-V ОС). Программу необходимо отладить перед оптимизацией - с помощью данных меньшего объема и многократных запусков убедитесь, что она корректно справляется с поставленной задачей и успешно выполняется без сбоев и ошибок.
Далее, измерим исходные значения времени работы программы и объем бинарного файла с помощью утилит time / multitime и size. При измерении времени работы программы, убедитесь, что вы измеряете время работы программы, а не время ее запуска в рамках процесса qemu-riscv64:
$ qemu-riscv64 -L "$RISCV/sysroot" time ./hello # Корректное измерение $ time qemu-riscv64 -L "$RISCV/sysroot" ./hello # Некорректное измерение
Далее необходимо выполнить оптимизацию с помощью флагов компиляции. Мы рекомендуем увеличивать степень оптимизации постепенно, начиная с флага -O1, контролируя корректность работы самой программы по вводимым значениям и измеряя время работы. Аналогичные действия необходимо предпринять и для оптимизации объема бинарного файла. После окончания оптимизации необходимо повторить измерения и внести полученные данные в отчет.
Оптимизируйте время работы бинарного файла для программы суммирования остатков от деления на 3 для целочисленного случайного массива из 10 элементов. Предположим, что исходный код программы уже написан. Выполним сборку и измерим время работы на 10 запусках:
$ gcc main.c -o a.out
$ multitime -n 10 ./a.out
…
===> multitime results
1: ./a.out
Mean Std.Dev. Min Median Max
real 0.016 0.002 0.012 0.016 0.021
user 0.007 0.005 0.000 0.007 0.013
sys 0.008 0.004 0.004 0.007 0.015
Для начала оптимизации по времени рекомендуется выполнить компиляцию c флагом -O1 и оценить достигнутый эффект.
$ gcc -O1 main.c -o a.out
$ multitime -n 10 ./a.out
…
===> multitime results
1: ./a.out
Mean Std.Dev. Min Median Max
real 0.012 0.002 0.010 0.016 0.020
user 0.005 0.005 0.000 0.007 0.013
sys 0.008 0.004 0.004 0.007 0.015
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.