Как писать на ассемблере в visual studio 2019
Перейти к содержимому

Как писать на ассемблере в visual studio 2019

  • автор:

Сборка проекта на Ассемблер под Windows x64 в Visual Studio 2020

Дано задание посчитать выражение: X = (A * 2 + B * C) / (D – 3 ).

.model small .stack 100h .data a db ? b db ? c db ? ;Резерв памяти для d db ? ;A,B,C,D,X x dw ? .code start: mov ax,@data mov ds,ax mov a,3 mov b,4 mov c,2 mov d,5 mov al,2 mul a mov cx,ax mov al,b mul c add ax,cx mov cl,d sub cl,3 div cl mov x,ax mov ah,4ch int 21h end start 

errors

При сборке выводит кучу ошибок Объясните, пожалуйста, в чем причина и как это исправить. P.S. Проект собираю в Visual studio 2019 под х64.

Отслеживать

задан 23 окт 2021 в 14:49

11 1 1 бронзовый знак

23 окт 2021 в 18:51

0

Сортировка: Сброс на вариант по умолчанию

Знаете кого-то, кто может ответить? Поделитесь ссылкой на этот вопрос по почте, через Твиттер или Facebook.

  • visual-studio
  • ассемблер
  • x86-64

Русские Блоги

Язык ассемблера + Visual Studio 2019 — решение среды языка ассемблера в Visual Studio 2019

основная концепция

MASM: Microsoft Assembler (обычно известный как MASM) — это инструмент для разработки промышленного программного обеспечения, который поддерживается и обновляется основными поставщиками операционных систем более 30 лет. Он никогда не подвергался смягчению или компрометации как удобный для пользователя инструмент и разработан для профессиональных программистов, которые могут использовать его для кода уровня операционной системы и высокопроизводительных объектных модулей, исполняемых файлов и динамических библиотек.

MASM32 SDK: MASM32 SDK (далее MASM32) — это независимый проект, призванный упростить работу опытных программистов, начинающих программировать на ассемблере. Это сложная и требовательная форма программирования, которая требует высокой точности кодирования и требует хорошего понимания мнемоники Intel и архитектуры процессора x86, используемых в среде операционной системы Windows, но усилия были Обеспечивает небывалую гибкость и производительность. Лучший компилятор для достижения достаточно высокого уровня знаний. Чтобы

Официальный веб-сайт:http://www.masm32.com/

решение

1. УстановкаMASM32

скачать

Установка

Папка masm должна содержать как минимум эти 4 файла:masm.exe, link.exe, debug.exe, exe2bin.exe

среди них:

masm.exe: ассемблер, используемый для сборки исходной программы (.asm) для получения целевой программы (.obj);

link.exe: программа-ссылка, используемая для подключения к целевой программе для получения исполняемой программы (.exe);

debug.exe: программа отладки, используемая для отладки исполняемых программ.

Во-вторых, настройте Visual Studio 2019

Откройте Visual Studio 2019

Создать новый проект

Изменить зависимости

Щелкните проект правой кнопкой мыши и выберите «Сборка зависимостей — сборка по выбору».

КонфигурацияMASM32

Щелкните правой кнопкой мыши проект и выберите свойства проекта.

контрольная работа

Создать исходный файл

Тестовый код

TITLE Add and Subtract (AddSub.asm) ; This program adds and subtracts 32-bit integers. ; Last update: 2/1/02 INCLUDELIB kernel32.lib .MODEL flat,stdcall ;.code ExitProcess PROTO, ; exit program dwExitCode:DWORD ; return code .data .code main PROC mov eax,10000h ; EAX = 10000h add eax,40000h ; EAX = 50000h sub eax,20000h ; EAX = 30000h push 0h call ExitProcess main ENDP END main

Результаты теста

Решения среды ассемблера в других версиях Visual Studio

Как писать на ассемблере в Visual Studio 2019

В этой статье мы получили представление о создании как EXE, так и DLL с помощью MASM в Visual Studio. Итак, новички должны иметь краткие знания об этих технологиях:

  • Visual Studio 2010 или более поздняя версия
  • Библиотека SDK MASM (Microsoft Macro Assembler)
  • Основные навыки программирования на ассемблере
  • VC++

Разработка EXE с помощью MASM

Открытие нового проекта

Нам нужно будет создать решение проекта VC++, которое позже будет сопровождаться файлом ассемблерного кода. Поэтому откройте Visual Studio и выберите Пустой проект типа шаблона VC++. Нет необходимости создавать подкаталог для этого пустого решения, поэтому снимите соответствующий флажок следующим образом:

После создания test_masm решения типа VC++ перейдите в обозреватель решений и щелкните правой кнопкой мыши, чтобы выбрать параметр Build Customization следующим образом:

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

Как мы уже говорили ранее, VS 2o1o не предоставляет шаблоны файлов сборки, однако выберите проект в обозревателе решений и щелкните правой кнопкой мыши, чтобы добавить текстовый файл, которому будет предоставлено расширение *.ASM следующим образом:

Теперь в наше решение test_masm добавлен пустой файл text.asm. Откройте его и вставьте следующий ассемблерный код, отвечающий за отображение окна сообщения, следующим образом:

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

Обязательные конфигурации проекта

Успешное выполнение файла ассемблерного кода в Visual Studio IDE зависит от файла внешней библиотеки, который будет доступен в MASM SDK. Следовательно, выберите проект Properties, щелкнув его правой кнопкой мыши в обозревателе решений. Здесь выберите Общие, развернув Linker и в Дополнительные каталоги библиотек вставьте путь к include, lib и макросы следующим образом:

Затем перейдите в раздел Input в Linker и укажите ссылку на файл masm32.lib в качестве дополнительных зависимостей:

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

Теперь перейдите в System из компоновщика и установите Windows в разделе subsystem следующим образом:

Наконец, настройте точку входа кода как начало от параметра Дополнительно в Компоновщике, который определяет поток выполнения кода. Мы можем определить точку входа в файл ASM из раздела .code.

Теперь перейдите в раздел Microsoft Macro Assembly из свойств решения, которое появляется в тот момент, когда мы добавляем файл сборки в каталог решения, иначе он будет скрыт. Здесь задайте имя каталога, в котором ранее был установлен MASM SDK, следующим образом:

Наконец все готово и решение скомпилировано. Если вся конфигурация верна, то в папке Debug решения создается файл test_masm.exe.

Тестирование и отладка

Пришло время протестировать исполняемый файл. При нажатии на исполняемый файл «Hello World!» окно сообщения будет выглядеть следующим образом:

Мы даже можем отлаживать ассемблерный код, вставляя точку останова в качестве определенного места, и через окно «Регистрация» в меню «Отладка» мы можем наблюдать за всеми регистрами ЦП с соответствующими флагами следующим образом:

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

Хотя этот раздел не относится к этой статье, но просто для ознакомления, мы можем дизассемблировать любой файл C++ в соответствующий код ASM. В интегрированную среду разработки Visual Studio встроен параметр Disassembly, который очень полезен для обнаружения ошибок во время выполнения, таких как переполнение буфера в коде, путем преобразования файла исходного кода в файл кода сборки следующим образом:

Разработка DLL с помощью MASM

Как мы все знаем, DLL-файлы — это библиотеки, содержащие определения методов. Точка входа обычно отсутствует в файле DLL. Следовательно, мы должны изменить этот параметр следующим образом:

Наконец, добавьте текстовый файл как masmlib с расширением ASM в решение, как и ранее, и поместите следующий код, который обычно содержит метод тестирования, который будет отображать некоторые предупреждения во время загрузки и выгрузки DLL. в клиентской программе следующим образом:

[простой]
включить masm32includemasm32rt.inc

.data
dLoading BYTE «ПРИВЕТ, DLL загружается…..», 0
dUnloading BYTE «DLL выгружается. «, 0
dOrdinal BYTE «До свидания», 0

.данные?
hInst DWORD ?

.code
testingMethod proc hInstDLL:DWORD, fdwReason:DWORD, lpvReserved:DWORD

.if fdwReason == DLL_PROCESS_ATTACH
вызов MessageBox, HWND_DESKTOP, смещение dLoading, NULL, MB_OK

отправить hInstDLL
отправить hInst

mov eax, TRUE
ret

.elseif fdwReason == DLL_PROCESS_DETACH
вызов MessageBox, HWND_DESKTOP, смещение dUnloading, NULL, MB_OK

.elseif fdwReason == DLL_THREAD_ATTACH

.elseif fdwReason == DLL_THREAD_DETACH

ret
testingMethod endp
ProcByOrdinal proc
вызов MessageBox, NULL, смещение dOrdinal, NULL, NULL
ret
ProcByOrdinal endp

метод завершения тестирования
[/plain]

Наконец, скомпилируйте эту программу и test_masm_dll. Файл DLL будет создан в папке Debug, на которую можно ссылаться в программе C++ или в самой клиентской программе MASM.

Заключение

сборка проекта Visual Studio

Создание проекта сборки x64 в Visual Studio 2019 может оказаться не таким простым делом при использовании большинства современных компиляторов.

Я покажу вам пошаговое руководство, чтобы получить полностью работающий проект сборки.

Это не так, так как я также покажу вам, как исправить распространенные неразрешенные внешние ссылки (чаще всего с printf), и последний пример!

Предварительные условия

Давайте настроим наш проект Assembly x64 в Visual Studio 2019!

Откройте Visual Studio 2019.

Создайте проект классического консольного приложения C++.

новый проект cpp Visual Studio 2019

Как только ваш проект будет готов, измените его с версии x86 на версию x64.

Щелкните правой кнопкой мыши свое решение -> Создать персонализацию

Проверьте масм, как на этом изображении.

проект сборки визуальной студии

Пришло время добавить исходный файл сборки!

Щелкните правой кнопкой мыши исходную папку и добавьте новый элемент.

Затем просто напишите имя файла сборки с расширением .asm !

Пришло время правильно настроить входной файл .cpp, чтобы включить необходимые библиотеки.

Добавьте эти включения в основной файл .cpp:

Давайте исправим неразрешенные внешние ссылки!

В последних версиях компиляторов многие стандартные функции, такие как printf, scanf и другие, не объявлены как внешние, что не позволяет вам использовать их в файлах сборки. Давайте это исправим.

Щелкните правой кнопкой мыши решение -> Свойства

Найдите параметр Компоновщик и измените Дополнительные зависимости, как показано на этом изображении:

проект сборки визуальной студии

Эти две устаревшие библиотеки решат проблему, поскольку они объявляют функции (такие как printf, scanf) как внешние и поэтому их можно использовать снаружи!

  • legacy_stdio_definitions.lib
  • legacy_stdio_wide_specifiers.lib

Сборочный проект Visual Studio: простой пример

Теперь, когда вы все настроили, я покажу вам простой пример.

Мы наладим связь между файлами .cpp и .asm. В частности, мы сможем вызывать функции, написанные на ассемблере, из нашего файла .cpp.

Этот форум перенесен в раздел вопросов и ответов Майкрософт. Посетите Microsoft Q&A, чтобы публиковать новые вопросы.

Ссылка на главную страницу Visual Studio

Вопрос:

Вопрос

Я убежденный программист masm. Я терпеливо ждал годами, пока Microsoft решит проблему отсутствия хороших инструкций по настройке Visual Studio для MASM. Не пора ли поручить сертифицированным программистам MASM написать хорошую процедуру для этой цели?

Сначала я пытался научиться выполнять эту задачу с помощью vs2015, и в конце концов мне это удалось. Затем, когда вы выпустили vs2017, мне, к моему огорчению, пришлось переучиваться заново. Теперь с vs2019 мне пришлось отказаться от обучения, потому что код vs2017 не применим должным образом к vs2019. Должен ли я задуматься, не трачу ли я время/деньги на ожидание?

В любом случае, я думаю, что Visual Studio значительно выросла с момента своего первого выпуска. При правильном управлении он должен стать вашим лучшим источником дохода. Только не забывайте все свои древние кодеры MASM. ПОЖАЛУЙСТА.

Тем временем, думаю, мне придется подождать или в конце концов переключиться на Linux или Nasm.

Все ответы

Я использую одни и те же инструкции для включения ассемблера в Visual Studio, начиная с Visual Studio 2010.

Поэтому, если вы не используете устаревшую версию MASM, предоставляемую MASM32 SDK (которая является версией ассемблера из Visual C++ 6 IIRC), заставить MASM работать в Visual Studio довольно просто.

1) Включите MASM для проекта в настройках сборки. При этом будет использоваться версия MASM, связанная с Visual Studio, поэтому для Visual Studio 2019 с обновлением 2 (16.2.0) это версия 14.22.27905.0.

C:\Program Files (x86)\Microsoft Visual Studio\2019\Community>ml
Microsoft (R) Macro Assembler Version 14.22.27905.0
Авторское право (C) Microsoft Corporation. Все права защищены.

C:\Program Files (x86)\Microsoft Visual Studio\2019\Community>

C:\Program Files (x86)\Microsoft Visual Studio\2019\Community>ml64
Microsoft (R) Macro Assembler (x64) Версия 14.22.27905.0
Авторское право (C) Microsoft Corporation. Все права защищены.

Это заставит Visual Studio запускать ML.exe или ML64.exe или даже armasm/armasm64.exe, если вы этого хотите, для каждого файла с расширением .asm.

2) При необходимости настройте пути включения. Следует помнить, что MASM также использует переменную среды INCLUDE, поэтому пути для C/C++ будут использоваться MASM.

И вот. Это Visual Studio, настроенная для сборки с использованием MASM.

Что вы хотите этим сказать?Сборка x86 — это сборка x86, сборка x64 — это сборка x64. Если вы не делаете что-то глупое, например, пытаетесь собрать 32-битный файл сборки в 64-битном проекте или 64-битный файл проекта в 32-битном проекте, тогда нет ничего, что изменилось бы настолько, чтобы вызвать проблемы. Самое большое изменение, о котором я недавно слышал, — это добавление новых инструкций, таких как набор инструкций AVX512.

Это подпись. Любые приведенные примеры не предназначены для проверки ошибок или демонстрации лучших практик. Они предназначены только для того, чтобы проиллюстрировать точку зрения. Я также могу дать неэффективный код или представить некоторые проблемы, чтобы препятствовать копированию/вставке кода. Это потому, что основная цель моих постов — помочь в процессе обучения.

Эта запись блога представляет собой пошаговое руководство по вставке кода языка ассемблера x64 и x86 в проект Visual Studio C++. В этом примере я буду использовать Visual Studio 2019, Community Edition.

Для краткости я предполагаю, что читатель знаком с инструкциями языка ассемблера x64 и x86, а также с соглашениями о вызовах Windows.

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

Прежде чем мы сможем начать добавлять некоторый код на ассемблере в наш проект Visual Studio C++, я должен указать, что с момента появления процессоров x64 компилятор Microsoft больше не позволяет встроенное включение кода ассемблера с ключевым словом __asm.

Таким образом, единственный способ добавить наш код на языке ассемблера — включить его в отдельный файл(ы). Каждый файл также должен быть включен в правильную конфигурацию сборки, чтобы убедиться, что он соответствует правильному синтаксису, в зависимости от выбранной разрядности ЦП проекта C++.

Давайте рассмотрим процесс шаг за шагом:

  1. Создайте проект C++ с помощью Visual Studio.
  2. Начнем с добавления в сборку Microsoft Macro Assembler. Щелкните правой кнопкой мыши имя проекта в обозревателе решений, затем перейдите в «Зависимости сборки» -> «Настройки сборки» и обязательно отметьте masm(.targets, .props) в списке и нажмите «ОК»:

Щелкните правой кнопкой мыши имя проекта в обозревателе решений, выберите «Добавить» -> «Новый элемент», затем выберите в списке «Файл заголовка» (.h) и дайте ему имя. Обязательно укажите расширение .asm. Для удобства чтения назовем этот файл asm64.asm :
В качестве теста я добавлю функцию asm_func, которая будет вызывать API MessageBox, но перед этим она вызовет нашу функцию C++ GetMsgBoxType. Итак, давайте закодируем все это:

Обратите внимание, что я использую префикс __imp_ при вызове API MessageBoxA. Прочтите этот пост в блоге, чтобы понять значение этого префикса.

Щелкните правой кнопкой мыши имя проекта в обозревателе решений, выберите «Добавить» -> «Новый элемент», затем выберите в списке «Файл заголовка» (.h) и дайте ему имя. Обязательно укажите расширение .asm. Для удобства чтения назовем этот файл asm32.asm :

Обратите внимание, что из-за соглашения о вызовах __fastcall я должен украшать имя функции asm_func префиксом и суффиксом @, а также заканчивать его количеством байтов, которые передаются в эту функцию. Аналогичный метод применяется к вызову MessageBoxA, использующему соглашение о вызовах __stdcall. Однако в этом случае к нему добавляется _ и суффикс @, а за ним следует количество байтов, которые передаются в этот API в качестве параметров. Кроме того, обратите внимание, что я также использовал __imp_ в этом вызове API, чтобы избежать вызова ненужного вызова JMP. Мне также пришлось удалить ведущее подчеркивание из него, чтобы учесть объявление .model C, которое будет указывать компилятору MASM добавлять _ к именам функций по умолчанию. Это зачаток устаревшего компилятора x86 MASM. Вызов директивы OPTION LANGUAGE: SYSCALL перед объявлением функции asm_func позволяет избежать автоматического префикса имен функций с символами подчеркивания.

Это исключит 32-разрядный файл на языке ассемблера из 64-разрядной сборки конфигурации.
Это исключит 64-битный файл на языке ассемблера из 32-битной сборки конфигурации.

Обратите внимание, что я объявляю нашу функцию языка ассемблера asm_func с помощью ключевого слова extern «C». Это заставит его придерживаться схемы искажения C-имени. Кроме того, я также объявляю нашу функцию обратного вызова GetMsgBoxType с тем же ключевым словом, чтобы мы могли ссылаться на нее по имени из нашего кода сборки.

  • Как нарисовать фигурную скобку в фотошопе
  • Утилитный драйвер Amd log что это такое
  • Как запустить большинство программ для Windows
  • Какой браузер лучше хром или гугл хром
  • С какого знака начинается структура функции в Excel

Как писать на ассемблере в 2021 году

Несмотря на наличие множества языков различной степени высокоуровневости, сегодня ассемблер не потерял своей актуальности и в индексе TIOBE находится на почётном 10-ом месте (на февраль 2021), обогнав такие модные языки как Go и Rust. Одна из причин его привлекательности – в простоте и максимальной близости к железу; с другой стороны, программирование на ассемблере всё ещё может рассматриваться как искусство и даёт совершенно особые эмоции.

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

Исходя из этого, наиболее удобной средой для программирования будет Visual Studio, в состав которой уже входит MASM. Подключить его к проекту на с/c++ можно через контекстное меню проекта Build Dependencies – Build Customizations…, поставив галочку напротив masm, а сами программы на ассемблере будут располагаться в файлах с расширением .asm (в свойствах которого Item Type должно иметь значение Microsoft Macro Assembler). Это позволит не просто компилировать и вызывать программы на ассемблере без лишних телодвижений – но и осуществлять сквозную отладку, «проваливаясь» в ассемблерный исходник непосредственно из c++ или c# (в том числе и по точке останова внутри ассемблерного листинга), а также отслеживать состояния регистров наряду с обычными переменными в окне Watch.

Подсветка синтаксиса

B Visual Studio нет встроенной подсветки синтаксиса для ассемблера и прочих достижений современного IDE-строения; но её можно обеспечить с помощью сторонних расширений.

AsmHighlighter — исторически первый с минимальным функционалом и неполным набором команд — отсутсвуют не только AVX, но и некоторые из стандартных, в частности fsqrt. Именно этот факт побудил к написанию собственного расширения —

ASM Advanced Editor. В нём, помимо подсветки и сворачивания участков кода (с использованием комментариев «;[«, «;[+» и «;]») реализована привязка подсказок к регистрам, всплывающих по наведению курсора ниже по коду (также через комментарии). Выглядит это так:

;rdx=ссылка на текущий элемент входного массива
mov rcx, 8;=счётчик цикла

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

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

Asm Dude — обнаружился чуть позже. В нём автор пошёл другим путём и сфокусировал силы на встроенном справочнике команд и автодополнении, в том числе и с отслеживанием меток. Сворачивание кода там также присутствует (по «#region / #end region»), но привязки комментариев к регистрам вроде ещё нет.

32 vs. 64

С тех пор, как появилась 64-битная платформа, стало нормой писать по 2 варианта приложений. Пора завязывать с этим! Сколько можно тянуть легаси. Это же касается и расширений — найти процессор без SSE2 можно разве что в музее – к тому же, без SSE2 64-битные приложения и не заработают. Никакого удовольствия от программирования не будет, если писать по 4 варианта оптимизированных функций для каждой платформы. Только 64 бит/AVX, только хардкор! Хотя может быть и прямо противоположный взгляд — новые процессоры и так работают быстро, и оптимизацию стоит делать под старые. В общем, всё зависит от конкретной задачи.

Преимущество 64-битной платформе вовсе не в «широких» регистрах – а в том, что этих самых регистров стало в 2 раза больше – по 16 штук как общего назначения, так и XMM/YMM. Это не только упрощает программирование, но и позволяет значительно сократить обращения к памяти.

FPU

Если ранее без FPU было никуда, т.к. функции с вещественными числами оставляли результат на вершине стека, то на 64-битной платформе обмен проходит уже без его участия с использованием регистров xmm расширения SSE2. Intel в своих руководствах также активно рекомендует отказаться от FPU в пользу SSE2. Однако есть нюанс: FPU позволяет производить вычисления с 80-битной точностью — что в некоторых случаях может оказаться критически важным. Поэтому поддержка FPU никуда не делаcь, и рассматривать её как устаревшую технологию совершенно точно не стоит. Например, вычисление гипотенузы можно делать «в лоб» без опасения переполнения,

а именно

fld x fmul st(0), st(0) fld y fmul st(0), st(0) faddp st(1), st(0) fsqrt fstp hypot

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

Пример оптимизации: преобразование Хартли

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

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

static void ht_csharp(double[] data, double[] result) < int n = data.Length; double phi = 2.0 * Math.PI / n; for (int i = 0; i < n; ++i) < double sum = 0.0; for (int j = 0; j < n; ++j) < double w = phi * i * j; sum += data[j] * (Math.Cos(w) + Math.Sin(w)); >result[i] = sum / Math.Sqrt(n); > >

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

код (комментарии удалены)

ht_asm PROC local sqrtn:REAL10 local _2pin:REAL10 local k:DWORD local n:DWORD and r8, 0ffffffffh mov n, r8d mov r11, rcx xor rcx, rcx mov r9, r8 dec r9 shr r9, 1 mov r10, r8 sub r10, 2 shl r10, 3 finit fld _2pi fild n fdivp st(1), st fstp _2pin fld1 fild n fsqrt fdivp st(1), st ; mov rax, r11 mov rcx, r8 fldz @loop0: fadd QWORD PTR [rax] add rax, 8 loop @loop0 fmul st, st(1) fstp QWORD PTR [rdx] fstp sqrtn add rdx, 8 mov k, 1 @loop1: mov rax, r11 fld QWORD PTR [rax] fld st(0) add rax, 8 fld _2pin fild k fmulp st(1),st fsincos fld1;=u fldz;=v mov rcx, r8 dec rcx @loop2: fld st(1) fmul st(0),st(4) fld st(1) fmul st,st(4) faddp st(1),st fxch st(1) fmul st, st(4) fxch st(2) fmul st,st(3) fsubrp st(2),st fld st(0) fadd st, st(2) fmul QWORD PTR [rax] faddp st(5), st fld st(0) fsubr st, st(2) fmul QWORD PTR [rax] faddp st(6), st add rax, 8 loop @loop2 fcompp fcompp fld sqrtn fmul st(1), st fxch st(1) fstp QWORD PTR [rdx] fmulp st(1), st fstp QWORD PTR [rdx+r10] add rdx,8 sub r10, 16 inc k dec r9 jnz @loop1 test r10, r10 jnz @exit mov rax, r11 fldz mov rcx, r8 shr rcx, 1 @loop3:;[ fadd QWORD PTR [rax] fsub QWORD PTR [rax+8] add rax, 16 loop @loop3;] fld sqrtn fmulp st(1), st fstp QWORD PTR [rdx] @exit: ret ht_asm ENDP

Обратите внимание: тут нет ни разворачивания цикла, ни SSE/AVX, ни косинусных таблиц, ни снижения сложности за счёт «быстрого» алгоритма преобразования. Единственная явная оптимизация — это итеративное вычисление синуса/косинуса во внутреннем цикле алгоритма непосредственно в регистрах FPU.

Поскольку речь идёт об интегральном преобразовании, то помимо скорости, нас ещё интересует точность вычисления и уровень накопленных погрешностей. В данном случае посчитать её очень просто — делая два преобразования подряд, мы должны получить (в теории) исходные данные. На практике они будут слегка отличаться, и можно будет посчитать ошибку через среднеквадратическое отклонения полученного результата от аналитического.

Результаты авто-оптимизации программы на c++ также могут сильно зависеть от настроек параметров компилятора и выбора допустимого расширенного набора инструкций (SSE/AVX/etc). При этом есть два нюанса:

  1. Современные компиляторы склонны всё возможное вычислять на этапе компиляции – поэтому вполне возможно в откомпилированном коде вместо алгоритма увидеть заранее посчитанное значение, что при замере производительности даст преимущество компилятору в 100500 раз. Во избежание этого в моих замерах используется внешняя функция zero(), добавляющая неопределённости входным параметрам.
  2. Если компилятору указать «не использовать AVX» — это ещё не значит, что в полученном коде будет отсутствовать AVX. Внешние библиотеки сами проверяют доступный набор команд на текущей платформе и выбирают соответствующую реализацию. Поэтому единственно надёжный способ сравнения производительности в таком случае – испытывать код на платформе, где AVX отсутствует в принципе.

Итак, компилятор Visual Studio 2019, целевая платформа AVX2, Floating Point Model=Precise. Чтобы было ещё интереснее — будет измерять из проекта на c# на массиве из 10000 элементов:

C# ожидаемо оказался медленнее с++, а функция на ассемблере оказалась быстрее в 9 раз! Однако ещё рано радоваться — установим Floating Point Model=Fast:

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

В данном случае наш вариант оказался и быстрее, и точнее. Но так бывает не всегда – и выбирая FPU для хранения результатов мы неизбежно будем терять в возможности оптимизации векторизацией. Также никто не запрещает комбинировать FPU и SSE2 в тех случаях, когда это имеет смысл (в частности, такой подход я использовал в реализации double-double арифметики, получив 10-кратное ускорение при умножении).

Дальнейшая оптимизация преобразования Хартли лежит уже в другой плоскости и (для произвольного размера) требует алгоритма Блюстейна, который также критичен к точности промежуточных вычислений. Ну а этот проект можно скачать на GitHub, и в качестве бонуса там также можно найти реализацию функций для суммирования/масштабирования массивов на FPU/SSE2/AVX.

Что почитать

Литературы по ассемблеру навалом. Но можно выделить несколько ключевых источников:
1. Официальная документация от Intel. Ничего лишнего, вероятность опечаток минимальна (кои в печатной литературе встречаются повсеместно).
2. Официальная документация от Microsoft.
3. Онлайн справочник, спарсенный из официальной документации.
4. Сайт Агнера Фога, признанного эксперта по оптимизации. Также содержит образцы оптимизированного кода на C++ с использованием интринсиков.
5. SIMPLY FPU.
6. 40 Basic Practices in Assembly Language Programming.
7. Все, что нужно знать, чтобы начать программировать для 64-разрядных версий Windows.

Appendix: Почему бы просто не использовать интринсики (Intrinsics)?

Скрытый текст

С тех пор, как были придуманы интринсики, этот вопрос возникает каждый раз, как только заходит речь о какой-либо оптимизации на ассемблере — ведь они для того и были придуманы, чтобы иметь возможность использовать SIMD-инструкции без необходимости программирования на ассемблере. Поэтому короткий ответ — используйте.

  • Не вся оптимизация делается векторизацией. Если во времена DOS оптимизация делалась за счёт экономии тактов и количества обращений к памяти, то сейчас основной инструмент оптимизации – это организация оптимальной работы с памятью во избежание промахов кеша.
  • Интринсики к новым инструкциям появляются с некоторым запаздыванием. Недостающие интринсики нельзя добавить простым включением заголовочного файла – их поддержка должна быть реализована на уровне компилятора.
  • Не на все инструкции возможно сделать интринсики. Когда оптимизация касается точности математических выражений, можно добиться значительных улучшений, программируя непосредственно на математическом сопроцессоре (FPU). Для команд сопроцессора же интринсики отсутствуют. Они также отсутствуют для команд, на результат которых влияют флаги переноса/переполнения/etc.
  • Интринсики не представляют из себя какой-либо высокоуровневый фреймворк – по факту, это не более чем лишь ещё один уровень мнемоник над ассемблерными инструкциями, мало чем отличающийся от ассемблерных вставок. Что автоматически означает, что код вовсе не становится более читаемым, а при его написании так или иначе нужно придерживаться ассемблерного стиля мышления.
  • Также, из C++ кода вовсе не очевидно, что существуют какие-то ограничения на количество одновременно используемых SIMD-регистров. В 32-х битном ассемблере вы не можете использовать XMM8 наравне с XMM0 / XMM7 – код просто не откомпилируется. А интринсиками вы можете определить хоть тысячу одновременно используемых регистров — и компилятор вынужден будет сам решать, что хранить в регистрах, что в памяти, и как между собой их оптимально комбинировать. В таком случае применение интринсиков не даёт никаких преимуществ в плане увеличения производительности – компилятор работает с ними так же, как и с обычными структурами на C++.
  • Программировать на ассемблере не так уж и сложно, особенно если не пытаться делать на нём хитрые высокоуровневые конструкции. Многое можно почерпнуть, изучая ассемблерный листинг простых алгоритмов в режиме отладки и с различными уровнями оптимизации – компилятор от Microsoft производит достаточно лёгкий для понимания и в то же время эффективный код, комментируя его исходным кодом на C++.
  • Программирование
  • Assembler

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *