2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




Начать новую тему Ответить на тему На страницу Пред.  1, 2, 3, 4, 5, 6, 7 ... 9  След.

Нужна ли такая тема про ассемблер?
Опрос закончился 24.01.2024, 03:22
Да, почитаю. 50%  50%  [ 12 ]
Да, поспрашиваю. 25%  25%  [ 6 ]
Да, поотвечаю. 4%  4%  [ 1 ]
Мне всё равно, но не против, дерзайте. 17%  17%  [ 4 ]
Нет, не интересно, полно готовой литературы. 0%  0%  [ 0 ]
Нет, ничего в этом не понимаю и не собираюсь. 0%  0%  [ 0 ]
Нет, форум не про это, есть другие более подходящие. 4%  4%  [ 1 ]
Другое ... 0%  0%  [ 0 ]
Всего голосов : 24
 
 Re: Первые и последующие шаги в ассемблере x86/x64
Сообщение17.01.2024, 16:30 
Аватара пользователя


29/04/13
8307
Богородский
worm2 в сообщении #1626296 писал(а):
Да вот же она:

Так вот же она, более простая:

$t_n = \left\lfloor\dfrac{3(\frac{a+b+1}3)^n}{a^2+b^2+4} \right\rceil$

, где $a=\sqrt[3]{19+3\sqrt{33}}$ и $b=\sqrt[3]{19-3\sqrt{33}}$

 Профиль  
                  
 
 Re: Первые и последующие шаги в ассемблере x86/x64
Сообщение17.01.2024, 16:39 
Заслуженный участник


20/08/14
11867
Россия, Москва
Так, раз уж пошла такая пьянка, то где брать инфу, кроме как из вышеуказанных книжек (всё ещё не добрался глянуть их, ничего пока сказать не могу насколько они хороши).
Intel публикует описания всех процессоров и их команд, в частности есть вот такой огромный 325383-sdm-vol-2abcd.pdf со списком всех команд современных процессоров. Я себе для удобства вырезал (можно онлайн) только страницы с самими командами и пользуюсь. Здесь самая подробная и точная инфа. Есть два НО: она по современным процессорам и какие-то древние (P4, какие-нибудь целероны) могут такие команды и не схавать, например инструкции pdep/pext/bextr/mulx/lzcnt/... из подмножества BMI1/2, операции с битами, появились как бы не ранее архитектуры Haswell в 2013г. Вторая проблема: иногда встречаются опечатки, что особенно обидно для самой что ни на есть официальной документации от производителя. Так что стоит критически относится к тексту и особенно описанию алгоритма работы, видел как привели алгоритм выполнения совершенно от другой инструкции. И для этого надо неплохо понимать что каждая команда должна по идее делать.
В принципе этот документ пересказан во многих книгах по ассемблеру (и на русском), но в них часто информация сильно устаревшая и далеко не столь подробная. Есть и онлайн справочники по командам (например или ещё).

Второй супер полезный источник - инфа по таймингам команд, непонятно почему её Intel не публикует, но идём к Агнеру Фогу и берём у него файл из пункта "All five manuals". Там в архиве будет нужный instruction_tables.pdf - в нём приведены времена выполнения почти всех команд для самых разных процессоров. Цифирек много, нам важны только две, обычно они в самых правых колонках, это Latency и Reciprocal throughput, первая это на сколько тактов задерживается результат, вторая более важна для получения высокой скорости и означает сколько тактов команда займёт если следующие команды не будут пользоваться её результатом время Latency тактов. Так например для Haswell (мой проц) умножение 64х64 командой mulx r64,r64,r64 с указанными временами 4/1 выдаст результат через 4 такта, но если пустить скажем умножение 4-х разных чисел подряд с сохранением в разные регистры и не пользоваться результатами умножений минимум 4 такта после каждого, то все 4 команды выполнятся всего за 4 такта (каждая за 1). И даже если сразу за 4-я умножениями использовать их результаты (скажем складывать), то 4 умножения выполнятся за 7 тактов (4 такта последняя команда и по 1 предыдущие), а не за 16 (каждая по 4). И одной из задач получения высокой скорости счёта как раз и является разработка такого алгоритма чтобы соседние команды минимально зависели друг от друга (зависимость по данным) и соответственно могли выполняться быстрее (почти никогда не удаётся, но стремиться надо).
В документе есть ещё одна важная колонка, но о ней в другой раз, это ещё сложнее.
Если вдруг конкретно своего проца в списке не нашли, то надо брать предыдущий, желательно на той же архитектуре.

О, оказывается есть даже гораздо более подробная инфа по таймингам на сайте https://uops.info/ (ссылка есть у Фога). С примерами измерений по каждой команде. Однозначно в закладки.

Эти два pdf у меня постоянно открыты и когда пишу код, и когда придумываю новый алгоритм или способ его реализации. В основном нужен конечно pdf Фога, он и как напоминалка о командах подходит, а за деталями уже в документ Intel. Я потому про книги и сайты и мало что знаю что этих двух документов хватает за глаза, хоть и не совсем удобны (особенно если не знаешь какая тебе команда нужна, только что она должна делать). Но это совершенно не учебники! Это справочники. Для начинающих там почти всё абракадабра и слишком много (особенно у Intel с парой тысяч страниц).

 Профиль  
                  
 
 Re: Первые и последующие шаги в ассемблере x86/x64
Сообщение17.01.2024, 17:55 
Аватара пользователя


29/04/13
8307
Богородский
Dmitriy40 в сообщении #1626293 писал(а):

Yadryara в сообщении #1625921 писал(а):
А спрашивать уже можно или погодить пока?
Так что у Вас за вопрос то был?

Я уже и забыл пока годил :-)

Можно ли двигаться дальше по конкретным прогам, но так чтобы сложность увеличивалась плавно?

 Профиль  
                  
 
 Re: Первые и последующие шаги в ассемблере x86/x64
Сообщение17.01.2024, 17:58 
Заслуженный участник


12/07/07
4530
В сообщении post1626265.html#p1626265 в коде для FPU записал регистры не в синтаксисе fasm. (Под рукой fasm не было)/ st(i) надо заменить на sti.
Последний простой вариант под win32 (в котором можно найти 55 член последовательности). Протестировал в fasm.
код: [ скачать ] [ спрятать ]
Используется синтаксис ASM
format PE
include 'win32axp.inc'

n=50    ;Какое число Фибоначчи вычислить

.code
main:
   xor  edx, edx
   mov  eax, 1
   xor  esi, esi
   mov  ebx, 1
   mov  ecx,  2
 cycle:
   xchg edx, esi
   xchg eax, ebx
   add  eax, ebx
   adc  edx, esi
   add  ecx, 1
   cmp  ecx,  n
   jne  cycle

   invoke  wsprintf, buf, fmt1, n, edx, eax
   invoke  MessageBox, 0, buf, txt1, MB_OK + MB_ICONINFORMATION
   invoke  ExitProcess,0

   fmt1:   db      'a(%d)=$%08lx%08lx',13,10,0
   txt1:   db      'Fibonacci number',0

.data
buf:    rb      1000
.end main
Проверка на переполнение не добавлена. Текст минимально отличается от предложенного Dmitriy40.

Встревать больше не буду. Подожду изложение архитектуры (устройство процессора изнутри) от Dmitriy40. По этому вопросу книг не находил, а заметки в сети давно устарели (да и не очень подробно они написаны).

 Профиль  
                  
 
 Re: Первые и последующие шаги в ассемблере x86/x64
Сообщение17.01.2024, 19:16 
Заслуженный участник


20/08/14
11867
Россия, Москва
Поясню новые команды в коде GAA:
xchg меняет левое и правое местами, очень удобно иногда, жаль нет для SSE/AVX.
adc сложение с переносом от предыдущих команд, нужно для сложения длинных чисел не влезающих в регистр.
xor - "исключающее ИЛИ" левого и правого, результат в левом. Удобно (занимает меньше байтов в памяти) для обнуления регистров, как и показано. Можно и sub edx,edx делать, но тогда испортится (обнулится) флаг переноса, иногда это важно. И кстати я не согласен с такой вот оптимизацией, вне циклов лучше (точно понятнее) писать прямо mov edx,0, пусть и чуть больше размер программы. И даже в циклах это очень под вопросом, будет ли что-то оптимальнее или нет, у нас тут на форуме даже тема про это была. А вот в SSE/AVX нет команды mov R,0, там только или чтение нуля из переменной в памяти (или другом регистре) или обнуление регистра командами xor или sub.
add ecx,1 можно заменить на inc ecx (увеличение регистра на 1), но она не выставляет флаг переноса, здесь неважно, но бывает критично (причём как достоинство, так и недостаток).
Кроме сложений есть и команды вычитания: sub и sbb (с заёмом) и dec (минус 1, тоже без флага переноса, который для вычитания имеет смысл флага заёма), neg (инверсия знака в дополнительном коде, т.е. фактически вычисляет 0-x). И логические команды: and (логическое И), or (логическое ИЛИ), xor (логическое исключающее ИЛИ), not (логическое НЕ), сдвиги (13 разных), проверки и установки/сброса/поиска битов (bt/bts/btr/bsf/bsr, не во всех архитектурах).
Про умножение и деление поговорим отдельно, планировал в тестах простоты (чтоб поинтереснее) или в формуле Бине.

GAA в сообщении #1626304 писал(а):
Подожду изложение архитектуры (устройство процессора изнутри) от Dmitriy40. По этому вопросу книг не находил, а заметки в сети давно устарели (да и не очень подробно они написаны).
Зря, я планировал очень коротко и только необходимое программисту (АЛУ, 7 регистров плюс флаги, устройство управления, внешний интерфейс). Потом добавить FPU с регистрами. Потом SSE/AVX с регистрами. Потом регистры x64. Где-то по пути , когда про процедуры/функции речь зайдёт, добавить про xBP и xSP и соглашения о вызовах. В книгах сильно подробнее всё описывается, но программисту для начала (до оптимизаций) достаточно и этого. Про кэши уже только лишь при тонкой оптимизации. Как и про предсказание переходов и суперскалярность и конвейерность и ассоциативность кэшей и переименование регистров тоже очень далеко не сразу. Остальное в процессоре прикладному программисту в общем и не нужно (с трудом представляю задачу упирающуюся в скорость TLB или кэш мопов).
А так, если "поговорить", то там внутри много разных интересных штук. Для начала можно почитать вот тут хороший обзор (но сильно технический, чем и хорош), постоянно обращаюсь для сверки про свой проц: https://www.ixbt.com/cpu/intel-haswell.shtml

 Профиль  
                  
 
 Re: Первые и последующие шаги в ассемблере x86/x64
Сообщение17.01.2024, 19:18 
Заслуженный участник
Аватара пользователя


01/08/06
3136
Уфа
Есть желание хотя бы чуть-чуть помочь Dmitriy40 в его титаническом труде (да, это я тут брякнул, что "поотвечаю").
Я бы мог коснуться двух тем:
1) Хранение целых чисел — очень простая тема: дополнительный код, big/little endian.
2) Аппаратный стек — тоже очень простые, базовые вещи.

Но, возможно, у Dmitriy40 уже есть что-то готовое, разумеется, тогда я воздержусь, лучше всё в едином стиле.
Также возможно, эти темы не нужны здесь, потому что слишком простые, тут все о них и так знают (а вдруг нет?).
К тому же предупреждаю, что меня не хватит на качественное изложение, только на неряшливое, возможно, будут ошибки/неточности/опечатки, а уж неудобства будут точно. Ввиду отсутствия неограниченного редактирования, даже при желании не получится всё это итеративно довести до ума.

 Профиль  
                  
 
 Re: Первые и последующие шаги в ассемблере x86/x64
Сообщение17.01.2024, 19:53 
Заслуженный участник


20/08/14
11867
Россия, Москва
Yadryara в сообщении #1626302 писал(а):
Можно ли двигаться дальше по конкретным прогам, но так чтобы сложность увеличивалась плавно?
Не вполне понимаю куда именно дальше. Не освещёнными остались переменные в памяти, их адресация (огромная тема), отличие переменных от констант для процессора и для компилятора, типы обрабатываемых данных (не только двойные слова по 32 бита), битовые операции (включая мегаполезные сдвиги). И для всего этого надо придумать качественные примеры.
Для обсуждения переменных в памяти можно взять решето Эратосфена, но блин это скучно и слишком объёмно ради довольно понятного вопроса. А его оптимизацией я заниматься не собираюсь, 5 или 20 секунд на миллиард - несущественно вообще. Можно оставить до тестов простоты (там пригодятся массивы). А коротко сказать прямо в следующем пункте про управление из командной строки (там оно уже необходимо).
Команды умножения можно ввести при следующей планируемой доработке программы чтобы она могла считать любое задаваемое в командной строке число Фибоначчи, чтобы не перекомпилировать каждый раз. Это очень полезно практически, добавить такую возможность управлять программой снаружи без перекомпиляции. Но деление там не нужно (хотя, если добавить вывод времени работы, то впендюрить и обсудить можно). Да и умножение тоже, можно обойтись арифметикой или логикой или сдвигами (3-5 разных вариантов). Заодно ввести понятие функции/процедуры и деления исходника на обозримые кусочки (include). Нужные процедуры у меня есть, сам ими пользуюсь (правда под x64), но надо причесать под декларируемые технологии/команды и оттестировать.
Получится превращение этой темы в заявленный цикл по асму ... Ну, можно и так.

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

worm2 в сообщении #1626318 писал(а):
Я бы мог коснуться двух тем:
1) Хранение целых чисел — очень простая тема: дополнительный код, big/little endian.
2) Аппаратный стек — тоже очень простые, базовые вещи.
Про первое я планировал куда-нибудь послать, не в лес, но хоть в вики, или в любую из книг. Слишком простая тема. В big endian не углубляться, упомянуть зачем нужно и всё. Но если желаете рассказать, то я совершенно не против. Правда хороших (простых и понятных) примеров я не придумал.
Второе планировал ввести когда будут сложные функции с локальными переменными, где они будут лежать на стеке и понадобятся регистры xBP и xSP (а xIP в топку). Как стек работает в теории - мало нужно, это скорее академический вопрос, на практике достаточно как им пользоваться для вызова функций, локальных переменных, сохранения регистров (медленно!).
Т.е. строго практически и в рамках концепции вычислительных задач.
В принципе про стек я за, но думаю лучше на конкретном примере, например вот прям следующим шагом хочу вызов процедур (с побочными эффектами! вот такой я противный, потому что так проще программисту). Можно даже будет показать во что превращается "магическая" invoke (и stdcall) с параметрами для процессора. Возьмёте разъяснение на себя - прекрасно, совсем готового у меня нет, только полуготовое из реальных программ (там сложновато для новичков, писал же под себя).

 Профиль  
                  
 
 Re: Первые и последующие шаги в ассемблере x86/x64
Сообщение17.01.2024, 20:19 
Заслуженный участник
Аватара пользователя


01/08/06
3136
Уфа
Понял. У меня слишком просто, хотя когда пытаюсь разжёвывать, такая длинная портянка получается!
Я на самом деле не очень хорошо понимаю, какие бывают типичные проблемы начинающих в понимании архитектуры CPU. Возможно, выстрел в пустоту будет. Поэтому лучше воздержусь.

 Профиль  
                  
 
 Re: Первые и последующие шаги в ассемблере x86/x64
Сообщение17.01.2024, 20:19 
Заслуженный участник


20/08/14
11867
Россия, Москва
worm2 в сообщении #1626318 писал(а):
Также возможно, эти темы не нужны здесь, потому что слишком простые, тут все о них и так знают (а вдруг нет?).
Шутку заценил, про стек. :-D Причины отказа от передачи параметров в стеке, почему xBP адресуется в сегмент SS вместо DS, функции с переменным числом параметров под x32, причины локальных переменных в стеке вместо кучи, причины записи нулей в стек да ещё и с шагом 4К, адресация к параметрам функций предыдущего уровня, специальные команды для работы со стеком и причины современного отказа от них, различия соглашения о вызовах между x32 и x64 (и разными ОС и даже разными компиляторами ЯВУ и директива stdcall в ЯВУ), организация программных стеков, адресация к локальным переменным не по xBP а по xSP и кучи проблем с этим (кроме выигрыша одного свободного регистра что бывает перевешивает) - и это я вспомнил ещё не всё что можно сказать про работу с аппаратным стеком. Половина этого нам вряд ли будет нужна, но упомянуть для полноты картины (как некоторые тут желают) можно. Если что, то почти ничего из этого я разъяснять не собирался, ну или совсем коротко когда реально встретится в примерах.
Кстати и про дополнительный код, надо ведь будет сказать и про знаковое переполнение и про команды js/jns/jo/jno. Да и про отличие команд например ja от jg. И придумать адекватный пример на них ... По моему мороки тоже немало, так что разумеется беритесь! :mrgreen:

UPD.
worm2 в сообщении #1626329 писал(а):
Я на самом деле не очень хорошо понимаю, какие бывают типичные проблемы обучающихся в понимании архитектуры CPU.
А я тем более не понимаю! У меня опыта пара человек, которые больше сами разбирались, хоть и с моей помощью (и в режиме вопросов и ответов). Это вообще лучше GAA припрячь, по ощущениям он как раз такие темы где-то преподаёт.
На мой взгляд типичные проблемы ровно как и у учеников начальной школы в математике - непонятно почему именно так, зачем оно вообще нужно, как разложить задачу на столь примитивные элементы, и "как это вычислить без иксов/логарифмов/интегралов/etc". Потому и думаю что теорию надо давать лишь как нагрузку к конкретным примерам. Иначе она "в одно ухо влетела, в другое вылетела". Я ещё помню себя в такой же ситуации, прочитаешь 10 книг про устройство процессора, а полезной инфы можно уложить на пару десяток страниц гораздо более понятным языком.

 Профиль  
                  
 
 Re: Первые и последующие шаги в ассемблере x86/x64
Сообщение17.01.2024, 20:25 
Заслуженный участник
Аватара пользователя


01/08/06
3136
Уфа
Я вообще хотел без примеров :-) Думал, если возникнут вопросы — отвечу.

Ну ладно, напишу, а то жалко выбрасывать. Надеюсь, не сильно испорчу.

Минимально необходимые сведения о хранении целых чисел.
Надеюсь, все знают, что такое бит, байт, двоичная система счисления и шестнадцатеричная система счисления.
Также надеюсь, что все знают про битовые операции OR, AND, XOR — они в ассемблере точно такие же, как в обычных языках программирования.
(на всякий случай: в 16-ричной системе счисления цифры A=10, B=11, C=12, D=13, E=14, F=15; имеется очень простое соотношение между 16-ричной и двоичной записями, а именно: 4 бита всегда соответствуют одной 16-ричной цифре: 0=0000, 1=0001, 2=0010, ..., 7=0111, ..., A=1010, ..., F=1111).
В ассемблерах по умолчанию числа записываются в десятичной системе счисления. Поддерживаются также двоичная (постфикс "b" после строки двоичных цифр), шестнадцатеричная (постфикс "h", число ОБЯЗАНО начинаться с цифры 0-9) и восьмеричная (постфикс "o", сейчас почти не используется):
11 = 0Bh = 13o = 1011b
Также некоторые ассемблеры (не знаю точно про FASM) поддерживают запись шестнадцатеричных чисел в нотации языка Си (0xCF) или Паскаля ($A000). Для иллюстративных целей путаницы не должно быть.
Оперативная память в программах под Windows представляется очень просто: это массив байтов, для 32-битной программы - длиной $2^{32}$, для 64-битной программы — длиной $2^{64}$. Это всё теоретически, конечно, некоторые области памяти недоступны и при попытке обращения к ним возникает исключение, а некоторые области памяти доступны только для чтения. В этом массиве индекс чего угодно (например, какой-либо структуры в памяти) называется адресом этой структуры. В 32-битных программах адрес 32-битный (4 байта), в 64-битной программе — 64-битный (8 байт).
Информацию в байтах памяти/регистрах, вообще говоря, можно интерпретировать как угодно. Например, значение 00000001b можно интерпретировать как "Январь" или как "Тёмно-синий цвет", но мы здесь интересуемся только целыми числами. Процессор умеет интерпретировать значения в регистрах и байтах памяти, как целые числа. Родными для процессоров x86 являются форматы 8-битного (ВНЕЗАПНО, называется byte), 16-битного (называется word), 32-битного (dword) и 64-битного (qword) целого числа. Команды ассемблера для размещения в памяти этих значений называются соответственно db ("Define Byte(s)"), dw ("Define Word(s)"), dd ("Define Double word(s)"), dq ("Define Quad word(s)"):
Используется синтаксис ASM
        db      13, 10, 255   ; три байта
        dd      1, 1000000000 ; 8 байт

Целые числа могут интерпретироваться как беззнаковые либо как знаковые.
Например, значение 0D1h = 11010001b, интерпретируемое как 8-битное значение без знака, представляет число 13*16+1=209, это обычная 16-ричная или двоичная арифметика. Числа без знака всегда неотрицательные и задают значения от 0 до $2^N-1$, где $N$ — разрядность: однобайтовые числа без знака представляют значения от 0 до $2^8-1=255$, 16-битные — от 0 до $2^{16}-1=65535$, 32-битные — от 0 до $2^{32}-1$ и т.д.
У чисел со знаком за знак отвечает старший двоичный разряд, если он равен 0 — число положительное или 0, если 1 — отрицательное. Однако тут новичков ждёт сюрприз: число -1 представляется не значением 81h = 10000001b, как можно было бы подумать, а значением 0FFh = 11111111b. Почему так?
Дело в том, что операции сложения и вычитания целых чисел в процессоре работают одинаково, независимо от того, без знака у нас число или со знаком. Можно смело сказать, что процессор не знает, какие значения он складывает или вычитает: знаковые или беззнаковые. При этом эти операции всегда успешны при любых слагаемых и вычитаемых, никогда не вызывают ошибок из-за переполнения. Как мы уже знаем, переполнение при вычислениях игнорируется, в назначении (регистре или памяти) сохраняются младшие 8, 16, 32 или 64 бита результата, а если был перенос двоичной единицы в следующий (9-й, 17-й, 33-й или 65-й), то он "пропадает". На самом деле пропадает не совсем, он попадает в так называемый "флаг переноса", который можно проверить инструкциями JC/JNC/SETC/SETNC. Выше уже упомянули инструкции ADC/SBB, которые незаменимы для организации сложения/вычитения чисел с произвольно большой разрядностью, например, для арифметических действий с 64-битными числами в 32-битной программе.
Так вот, если мы к 8-битному значению 0FFh добавим единицу, мы получим в назначении 0. Значит, если мы сделаем обратную операцию и отнимем от 0 единицу, мы должны получить 8-битное значение 0FFh, значит, именно оно должно интерпретироваться как -1. И т.д.: 0FEh должно интерпретироваться как -2, 0FDh = -3, ..., 80h = -128. Вышеупомянутое значение 0D1h = 11010001b интерпретируется как 209-256 = -47.
Такой способ записи целых отрицательных чисел называется "дополнительный код" (англ. two's complement). Насколько мне известно, он используется во всех современных процессорах, так что можно считать, что других способов записи не существует.
При попытке отнять от -128 = 80h единицу мы получим 7Fh = 01111111b = 127 (знаковый бит 0, значит, число положительное). Получается переполнение, хотя если бы мы интерпретировали 80h как беззнаковое, то переполнения не было бы: 128-1=127. Кроме "флага переноса" в процессоре есть ещё и "флаг знакового переполнения" или же просто "флаг переполнения", который устанавливается, когда результат операции отличается от результата знакового сложения. Проверить флаг знакового переполнения можно инструкциями JO/JNO/SETO/SETNO.
Итак, получается удивительная штука: процессор складывает и вычитает числа, однако сам не ведает, какие они: знаковые или беззнаковые. Только мы сами интерпретируем операнды и результат как знаковые или беззнаковые и проверяем, было ли переполнение или нет, анализируя флаги "переноса" и "переполнения".
Итак, знаковые 8-битные значения представляют числа от -128 до +127. И в общем, знаковые N-битные значения представляют числа от $-2^{N-1}$ до $+2^{N-1}-1$.
Например, 16-битные значения со знаком представляют $-2^{15}=-32768$ до $+2^{15}-1=+32767$. На всякий случай: 32-битное значение со знаком -1 будет записываться как 0FFFFFFFFh.
Другие операции, помимо сложения и вычитания, могут "знать" о знаковости числа. Например, если нам нужно перевести 8-битное число 0FFh в 16-битную форму, нам нужно знать, оно знаковое или беззнаковое. Если оно беззнаковое, то это 255, и оно должно быть представлено как 00FFh. Но если оно знаковое, то это -1 и должно быть представлено как 0FFFFh. Для "расширения" разрядности знаковых чисел в процессоре есть специальные инструкции (MOVSX, CBW, CWD, CWDE, CDQ) в то время как для беззнаковых используются другие инструкции (MOVZX либо явное обнуление старших разрядов инструкцией AND). Также существуют разные инструкции для знакового и для беззнакового умножения и деления, почему — попробуйте разобраться самостоятельно.

При хранении многобайтных значений в памяти размещается сначала байт, представляющий 8 самых младших разрядов, а затем байты, представляющие более старшие разряды. Это так называемый Little endian (LE) byte order (порядок байт), он справедлив для процессоров x86. Для других, в т.ч. и широко распространённых процессоров, может применяться Big Endian (BE) порядок байт, когда старшие байты размещаются вначале! Но мы не будем рассматривать архитектуру Big Endian. Поэтому, например, команда
Используется синтаксис ASM
        dd      -1633911

будет у нас всегда эквивалентна команде
Используется синтаксис ASM
        db      89h, 11h, 0E7h, 0FFh

(-1633911 в 16-ричном виде равно 0FFE71189h)
Многобайтные значения в памяти адресуются своим первым (в нашем случае младшим) байтом. В некоторых случаях инструкция записывается так, что непонятно, сколько байт она должна прочитать/записать. Например, инструкция "прибавить единицу к числу по адресу 012345678h" могла бы быть записана так:
Используется синтаксис ASM
        add     [012345678h], 1

однако из такого способа записи непонятно, нужно ли добавить единицу к 8-битному значению по этому адресу, к 16-битному, к 32-битному или же к 64-битному. Здесь мы вынуждены явно указывать размер операнда:
для 8-битных значений — byte ptr
для 16-битных значений — word ptr
для 32-битных значений — dword ptr
для 64-битных значений — qword ptr:

Используется синтаксис ASM
        add     word ptr [012345678h], 1


В некоторых инструкциях это излишне, поскольку один из операндов - регистр, у которого разрядность видна сразу из названия (значит, у другого операнда разрядность такая же):
Используется синтаксис ASM
        sub     AX, [edi+012345678h]

в этом случае AX — 16-разрядный регистр, поэтому указание "word ptr" для второго операнда излишне.

 Профиль  
                  
 
 Re: Первые и последующие шаги в ассемблере x86/x64
Сообщение17.01.2024, 20:27 
Аватара пользователя


29/04/13
8307
Богородский
Dmitriy40 в сообщении #1626323 писал(а):
Не вполне понимаю куда именно дальше.

От простого к сложному. К 6-парам простых чисел (23, 29 - 47, 53 - ...) например. Прога, которая печатает первые 10 таких пар сильно сложная?

 Профиль  
                  
 
 Re: Первые и последующие шаги в ассемблере x86/x64
Сообщение17.01.2024, 21:15 
Заслуженный участник


20/08/14
11867
Россия, Москва
Yadryara
Скажите, а вот пост worm2 выше Вам достаточно понятен? Пусть не смысл или синтаксис упомянутых команд, но хотя бы про числа и операции с ними? У меня бы так коротко не получилось.
Потому что на мой взгляд в пару к нему нужен сравнимого размера (и как бы не больше) пост про регистры, команды, адресацию, разделение кода и данных, может и ещё что-то.

Yadryara в сообщении #1626332 писал(а):
К 6-парам простых чисел (23, 29 - 47, 53 - ...) например. Прога, которая печатает первые 10 таких пар сильно сложная?
Ну как сказать ... А Вы собственно какую именно прогу подразумеваете? В процессоре нет команды проверки числа на простоту, соответственно это надо писать.
Числа можно перебирать решетом Эратосфена (и тогда тест не нужен), по "формулам" (паттерну, с шагом).
Проверять на простоту можно полудесятком разных способов. И каждый способ допускает ещё по 2-3-4 варианта реализации. Плюс комбинации (потому что так быстрее!). И все с разной скоростью! И обычно чем быстрее способ, тем сложнее код.
Плюс таблицы (простых и/или "формул") формировать внешним способом (проще) или рассчитывать "на лету" в самой программе (сильно сложнее).

Решетом и без теста простоты - нет, несложная, но и польза мала, скорость решета понятно какая (сотни миллионов в секунду), а оптимизировать его до скорости primesieve я тупо не хочу - слишком сложно и нет практической пользы, рекорды "кто быстрее 2с посчитает до 10млрд" не интересуют (одно из неписанных правил программиста: всё что считает меньше нескольких часов - в оптимизации не нуждается!).

С тестом простоты - да, сложная. Несколько страниц кода. А если хоть сколько-нибудь быстро - половина кода будет довольно нетривиальной, не только по командам, но и по идеям. И учтите что у меня нет готовой проги полностью на асме, я всегда пользуюсь другим языком (PARI/GP или дельфи) и на асме лишь самый критичный участок. А такую связку надо объяснять дополнительно.
У Вас есть исходники для пентадекатлона, посмотрите туда и оцените сколько ассемблерного кода надо объяснить (а ведь там проверки простоты чисел нет! это ещё минимум страница кода и три страницы пояснений). Хотите могу прямо по нему объяснять, просто получится в обратном порядке (от сложного к простому) и вообще ни разу не систематично. Помните, я в теме про пентадекатлон как-то описал "начало статьи" (по каким принципам написан код, не сам код), и то вышло страницы 2-3, только описание применённых идей (и не всех), а уж как они превращаются в реальный код это отдельная эпопея. И кстати в том коде под SSE нет специфичных для AVX2 оптимизаций. И пары оптимизаций не дающих заметного выигрыша конкретно на таком коде (но дающих на конкретно моём компе).

-- 17.01.2024, 21:26 --

worm2 в сообщении #1626331 писал(а):
Такой способ записи целых отрицательных чисел называется "дополнительный код" (англ. two's complement). Насколько мне известно, он используется во всех современных процессорах, так что можно считать, что других способов записи не существует.
Поделюсь хаком: иногда приходится использовать инвертированный дополнительный код (отрицательные числа со старшим 0) чтобы беззнаковые команды работали как знаковые (и положительные числа были больше отрицательных при беззнаковом сравнении). А иногда наоборот очень удобно что отрицательные числа в дополнительном коде больше положительных при беззнаковом сравнении.

-- 17.01.2024, 21:51 --

Yadryara
Тесты простоты для небольших (до 4млрд) чисел (некоторые можно и для больших чисел применить):
Делим на все нечётные до корня из. Ничего не надо, зато медленно.
Делим на простые до корня. Нужна таблица таких простых (где брать? их надо от 6500шт для чисел до 4млрд до сотен миллионов). И тоже медленно.
Тест Ферма. Нетривиален. Имеет исключения (как проверять? их немало). Медленно. Непонятно как сделать для длинных чисел (больше 32 бит), точнее понятно, но выходит страшно медленно.
Его улучшение (быстрое возведение в степень по модулю). Ещё более нетривиален. Исключения те же. Всё равно медленно.
Ещё его улучшение до сильно псевдопростых. Столь же нетривиален. Исключений в разы меньше (но пара тысяч до 4млрд и 32млн до 2e19). Так же медленно.
Повтор последнего варианта несколько раз (сколько? может до десятков раз). Весьма медленно. Зато без исключений и таблиц.
Повтор последнего варианта от двух до семи раз. Исключений нет до 2^64. Без таблиц. По прежнему медленно.
К любому из этих тестов можно добавить быструю проверку делимости на несколько малых простых (умножение вместо деления), отсеется до 75% составных. Вопрос сколько простых проверять. Нетривиально и красиво. Но нужна таблица по 2 числа на каждое простое (считать её это ещё полстраницы кода).

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

Мои программы работают быстро потому что до проверки чисел на простоту там выполняются много гораздо более быстрых проверок (по модулям малых простых). И это ещё более нетривиально. Ещё страница кода. Без особых оптимизаций и таблиц (которые беру готовые иначе ещё пара страниц кода их генерации). А уж оптимизаций там (включая и не реализованные в исходнике пентадекатлона) ... ещё на страницу-две только описания идей и способа реализации.
Правда думаете что несколько страниц кода можно легко и быстро объяснить? Там только используемой математики (часть которой я и сам не понимаю но использую) на пару страниц объяснений.

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

 Профиль  
                  
 
 Re: Первые и последующие шаги в ассемблере x86/x64
Сообщение17.01.2024, 22:34 
Заслуженный участник


20/08/14
11867
Россия, Москва
Вот реальный код самого внутреннего цикла программы поиска паттерна 19-252 (19 простых чисел с фиксированными интервалами, да впрочем и любого) на x32 AVX2, выполняющего более 99% работы:
код: [ скачать ] [ спрятать ]
Используется синтаксис ASM
                vzeroupper                              ;Порты     L/T     Конвейер
                vmovdqu         ymm7,[EBP+32*4]         ;23     3/0.5   маски битов внутри байта
.block:         mov             ECX,0
.loop:          mov             ESI,[EBP+4*0]           ;23     3/0.5   avx.a
                add             ESI,[EBP+4*2]           ;23     3/0.5   avx.kk
                mov             EDI,[EBP+4*6]           ;23     3/0.5   avx.ra
                mov             EBX,-1                  ;0156   1/0.25
.l32:           vmovdqu         ymm6,[EDI+32*31]        ;23     3/0.5   128 битная маска
                vmovdqu         ymm0,[EDI+ECX]          ;23     3/0.5   y0 - остатки на начало диапазона x32
                vpaddb          ymm0,ymm0,[ESI]         ;15     1/0.5   y0 - текущие остатки
                vpsubb          ymm1,ymm0,[EDI+32*30]   ;15     1/0.5   вычитаем проверяемое простое число
                vpminub         ymm0,ymm0,ymm1          ;15     1/0.5
                vpand           ymm1,ymm0,[EBP+32*2]    ;015    1/0.33  маска 0x78
                vpand           ymm0,ymm0,[EBP+32*3]    ;015    1/0.33  маска 0x07
                vpsrlw          ymm1,ymm1,3             ;0      1/1
                vpshufb         ymm0,ymm7,ymm0          ;5      1/1
                vpshufb         ymm1,ymm6,ymm1          ;5      1/1
                vpand           ymm0,ymm0,ymm1          ;015    1/0.33
                vpxor           ymm1,ymm1,ymm1          ;015    1/0.33  0x00
                vpcmpeqb        ymm0,ymm0,ymm1          ;15     1/0.5   маска 0x00
                vpmovmskb       EAX,ymm0                ;0      3/1
                not             EAX                     ;0156   1/0.25
                and             EBX,EAX                 ;0156   1/0.25
                jz              .next                   ;06     1/0.5
                add             ESI,[EBP+4*3]           ;0156   1/0.25  avx.nk
                add             EDI,32*32               ;0156   1/0.25  sizeof(r128[i])
                cmp             EDI,[EBP+4*7]           ;0156   1/0.25  avx.rb
                jc              .l32                    ;06     1/0.5
.scan:          bsf             EAX,EBX
                jz              .next
                btr             EBX,EAX
                add             EAX,[EBP+4*0]           ;EAX=i (EAX+=avx.a)
;Тут в EAX индекс кандидата на решение в проверяемом блоке, надо с ним что-то сделать (не показываю) и продолжить счёт
                test            EBX,EBX
                jnz             .scan
.next:          add             ECX,32
                cmp             ECX,32*30               ;цикл по 30-ти блокам
                jnz             .loop
                mov             ECX,[EBP+4*0]           ;avx.a
                add             ECX,32
                mov             [EBP+4*0],ECX           ;avx.a
                cmp             ECX,[EBP+4*1]           ;avx.w
                jc              .block
В EBP передаётся указатель на структуру параметров счёта, первым в которой лежит указатель на ту огромную таблицу с миллионами строк остатков по простым 41...127, собственно и обрабатываемую этим кодом. Всё что с EBP+xxx это в структуру параметров счёта, она всего килобайты.
Объяснить каждую команду не проблема, многие Вам понятны или догадаетесь. Проблема объяснить для половины из них из середины почему именно она и как до них дошёл.
Вокруг этого кода ещё раз в 15 больше кода уже на дельфи (!) для расчёта таблиц, обработки параметров запуска, вывода статистики счёта, результатов, организации многопоточнго счёта, допроверки кандидатов (причём не до конца! тест простоты под x32 делать откровенно поленился даже на дельфи не говоря уж про асм, отправляю в PARI/GP).
Хотите могу про это рассказывать. И про математику, и про код, и про превращению первой во второй, и про оптимизацию (в принципе уже где-то это рассказывал).

 Профиль  
                  
 
 Re: Первые и последующие шаги в ассемблере x86/x64
Сообщение17.01.2024, 23:57 
Заслуженный участник


20/08/14
11867
Россия, Москва
Yadryara
Извините, я выше всё же погорячился.

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

(Лирическое отступление)

Вот есть такой язык, Форт. Интереснейший язык, и по синтаксису, и по принципам организации, да и по эффективности (поспорит с С в некоторых реализациях). Так вот, его автор писал во вступлении мол "обычно все ставят финальную задачу и реализуют её сверху вниз, от крупных кусков к более мелким, вплоть до кода на конкретном языке (в том числе машинного). Я же предлагаю идти наоборот, снизу вверх, сначала научить делать простейшие вещи, потом их усложнить комбинируя, потом ещё, и ещё и в финале дойти до решения задачи.". Ещё приводит пример секретарши, которая сначала ничего не умеет и вы её учите получать письма, потом их вскрывать, потом читать, потом раскладывать по темам, потом отвечать на простые стандартными шаблонами, потом распределять по отделам на проработку, потом добавляете ей контроль за прохождением писем, потом ещё что-то, и ещё, и ещё ... и получаете секретаря-референта заточенного точно под ваши желания. По идее правильно и красиво.
В реальности - не работает! Ни тот, ни другой подход! Ни сверху вниз, ни снизу вверх. Я много раз пробовал и так и так (конечно не с секретаршей, с программами). Получается или чудовищно избыточный код, или надо прямо заранее, априори, представлять в деталях результирующее решение. Обычно первого жалко, а второго просто не бывает.
Объясню почему. На примере поиска простых по паттерну. Задача простейшая. Но как решать априори неясно (ну кроме решета, тут понятно). Попытка писать код сверху вниз, от задачи к коду, даст кучи вызывающих друг друга функций с кучей параметров и реализующих каждый шаг весьма неоптимальным образом. Попытка решать снизу, с выбора структур данных и кода их обрабатывающих, требует чёткого понимания как эта обработка должна привести к решению всей задачи. Вот скажем таблица остатков по малым простым всего паттерна вовсе не очевидно что так уж нужна. И даже эту таблицу я обрабатывал четырьмя разными способами! И соответственно она была в 4-х (если не больше) разных форматах. И код её формирующий тоже. А уж обрабатывающий по 2-5 варианта на каждый формат (многие лишь в уме, только просчитывал скорость в теории и бросал). А про методы без такой таблицы (с другой или вовсе без) совсем молчу. И я не знаю как научиться придумывать вот эти десятки вариантов кода без хорошего знания и системы команд процессора, и внутреннего устройства, и стандартных алгоритмов (банально двоичный поиск нужен очень много где, например в модификации теста Ферма на простоту).
Так что применять приходится оба подхода, вместе и вперемешку. Сначала делить задачу на вменяемые подзадачи, пробовать их решать, думать как их решать более оптимально, менять разбиение и взаимосвязи, менять структуры данных и соответственно генерирующий и обрабатывающий куски кода (а это до 90% всего кода), и так по кругу. И что от чего тут зависит так сразу и не скажешь, всё и от всего. Расширение диапазона чисел приводит к замене теста простоты, а замена 19-ти команд сравнения на две команды векторного выбора и тройку логических приводит к такому причудливому перекручиванию трёхмерной таблицы, что в разных частях таблицы младшим индексом становятся разные! Пока пытался представить - голову сломал. А ведь надо код генерирующий такую вывернутую таблицу ... Да хотелось бы на асме, да ещё чтоб побыстрее ... А там среди прочего и КТО надо вычислять ... Угу, на асме ... Которую вообще не сразу поймёшь как считать даже по готовой инструкции. :facepalm:
И придумать вот более-менее простые примеры, но в то же время хоть чем-то полезные и интересные - отдельный труд. Вместо Фибоначчи хотел начать с факториала, но он оказался проще и не интереснее. Пример для деления вообще не знаю какой взять, только и вспомнил про тесты простоты. А их немало ваще-то, и весьма замороченных. Благо что тест Ферма (и его усиление) у меня уже написан и отлажен, пусть и только для x64, но ведь ещё переделывать под x32 ... А там блин математика важнее кода. Но хоть практически полезно.
Ладно, о чём я? А, про траекторию рассказа. Наверное и правда надо было взять учебник и по нему идти только поясняя непонятное, зря я захотел срезать дорогу. Вплоть до задачи оптимизации, её по учебникам изучать не слишком разумно, устарели.


-- 18.01.2024, 00:26 --

(Оффтоп)

Вот уже какой день думаю что если ту уже хитро вывернутую таблицу с миллионами строк ещё немного перекрутить/вывернуть, то похоже можно ещё в 1.3 раза быстрее обрабатывать! Просто немного изменив порядок обхода, даже без изменения алгоритма вычислений. Это ж сколько кода опять переписывать ... Процентов 40. И обработку на асме (30%) и генерацию на дельфи (10%). И нигде не накосячить.
И будет ли это выгодно для других паттернов - очень хороший вопрос. Пока без ответа.

 Профиль  
                  
 
 Re: Первые и последующие шаги в ассемблере x86/x64
Сообщение18.01.2024, 06:31 
Аватара пользователя


29/04/13
8307
Богородский
Да, worm2 излагает простым и понятным языком. Спасибо. В знаковые тонкости вникать пока не стал.

Dmitriy40, по поводу простых у Вас был план:

Dmitriy40 в сообщении #1626119 писал(а):
6. Проверка числа на простоту. Сначала тупо проверкой делимости. Потом тут же сразу и красивый метод замены медленного деления на быстрое умножение.

Вот на эти два метода мне ещё рано смотреть? Вам должно быть виднее, как именно перейти от простого кода к сложному так, чтобы пытающийся разобраться не запутался.

 Профиль  
                  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 133 ]  На страницу Пред.  1, 2, 3, 4, 5, 6, 7 ... 9  След.

Модераторы: Karan, Toucan, PAV, maxal, Супермодераторы



Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group