tag:blogger.com,1999:blog-9356226239244085312024-02-19T07:48:33.998+02:00блог alexanius'аalexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.comBlogger84125tag:blogger.com,1999:blog-935622623924408531.post-53986641550658550682020-09-11T22:00:00.001+02:002020-09-11T22:00:49.849+02:00Переезд<p>Итак, <strike>фирма</strike> блог переезжает.</p><p>Теперь я обзавёлся собственным сайтом <a href="http://alexanius.ru">alexanius.ru</a>, все новые публикации будут появляться там, все старые по мере возможности обновляться и переноситься.</p>alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com0tag:blogger.com,1999:blog-935622623924408531.post-29605621781729706062019-12-22T17:29:00.001+02:002019-12-23T00:06:56.194+02:00Межъязыковые тесты производительности<div dir="ltr" style="text-align: left;" trbidi="on">
<br />
<br />
Недавно попался на глаза <a href="https://benchmarksgame-team.pages.debian.net/benchmarksgame/">сайт</a>, идеей которого является сравнение производительности разных языков. Посмотрел на этот сайт и решил подумать над тем какие вообще есть подобные проекты, какие у них проблемы и как вообще следовало бы делать измерения производительности.<br />
<a name='more'></a>Для начала надо сказать что задача измерения производительности вычислительной системы довольно сложная, справляются с ней очень немногие. Если говорить именно про производительность центрального процессора (или компиляторов языка Си), но лучше пакета тестов <a href="http://spec.org/">SPEC</a> я ничего не видел.<br />
<br />
Если производительность оценивается на основе одного-двух тестов, тем более без перекомпиляции, то к производительности этот тест никакого отношения не имеет. Тут можно было бы упомянуть, например, тесты <a href="https://www.7-cpu.com/">7zip</a>, которые таки интересные, но надо понимать что измеряют они только скорость конкретного алгоритма. <br />
<br />
Но вернёмся к Benchmark Game. Мне очень понравился подход автора, который заключается в следующем: берём какой-то алгоритм, пишем его решение на соответствующем языке, сравниваем время работы получившейся программы с реализациями алгоритма на других яхыках. Однако в данном проекте есть проблемы, которые не позволяют сделать реальную оценку производительности ни языков, ни компиляторов, ни процессоров.<br />
<br />
Во-первых каждый тест умещается в один файл, более того в одну процедуру. Т.е. мы сразу теряем возможность оценки работы оптимизатора и скорости вызова разных процедур. Более того, в тестах не предоставляется неоптимизированный вариант что сужает нашу картину.<br />
<br />
Во-вторых все тесты очень короткие. Они исполняются по нескольку секунд, а на таком времени любой случайный выброс будет давать погрешность в десятки процентов. Нормальный тест должен проходить хотя бы несколько сотен секунд чтобы уменьшить данный эффект.<br />
<br />
В-третьих в тестах нет разделения на однопоточный/монгопоточный режим. Это ещё больше сужает наше видение относительной скорости системы.<br />
<br />
Кажется были ещё какие-то идеи касательно этого проекта, но сейчас я уже немножко про них подзабыл. Я смог найти несколько схожих проектов (хотя их довольно мало). Во-первых это старый проект, из которого вырос benchmark game: <a href="https://web.archive.org/web/20010124090400/http://www.bagley.org/~doug/shootout/">Great Language Shootout</a>. Это был проектик, изначально замерявший скорость скриптовых языков. Что довольно интересно, хотя и имеет не так много смысла (жаль что в актуальном проекте их нет just for fun).<br />
<br />
Ещё из крупных пузомерок можно назвать проект <a href="https://www.techempower.com/benchmarks/">Web Framework Benchmarks</a> с довольно говорящим названием. Но в нём измеряется преимущественно скорость веб-фреймворков, уж не знаю на сколько качественно - я в них совершенно не разбираюсь.<br />
<br />
Есть ещё один интересный любительский <a href="https://github.com/kostya/benchmarks">проектик</a> от некоего Кости. В нём неплохая подборка языков, но он нацелен не на поиск самого быстрого языка, а на сравнительный анализ программ, написанных "средним разработчиком" (полагаю автор имел ввиду себя). Тестовых задания в проекте немного, но много реализаций на разных языках. Особенно понравилось сравнение скорости реализации языка Brainfuck. У автора явно имеется неплохое чувство юмора.<br />
<br />
Если у кого-либо есть ещё ссылки на аналогичные интересные проекты - буду рад услышать.<br />
<br />
А теперь подумаю над тем как бы я видел проект по измерению скорости работы различных языков.<br />
<br />
1. В таком проекте должно быть множество тестов, направленных на измерение различных аспектов современных программ. Должны быть тесты на различные типы вычислений, на работу с памятью, на работу с большим количеством вызовов и т.п. Т.е. покрыто максимально большое количество областей применений языка программирования. Причём очень желательно чтобы со временем тесты пополнялись актуальными для данного времени задачами.<br />
<br />
2. Мне очень нравится соревновательная идея, поэтому для каждого теста должно быть хорошо описанное задание и набор тестов. Причём должен быть определённый эталон, из которого можно понять как следует реализовывать данный алгоритм.<br />
<br />
3. Время работы теста должно быть достаточно большим - для замеров важно чтобы выбросы +/- 2 секунды не влияли на итоговый результат. Вообще, каждый тест следует запускать по нескольку раз и брать минимальное значение (мы же ведь наилучший случай ищем).<br />
<br />
4. Измерение времени должно быть точным: нужно предоставлять sys,user,real время для каждого запуска. Заодно позволит проверить корректность замера.<br />
<br />
5. Программы должны быть корректными: тестовые данные должны быть разнообразными, программа должна уметь с ними работать. В идеале каждая программа должна быть оттестирована различными способами, не только на заранее известных входных данных. Более того, каждая программа должна соответствовать стандарту (привет -Wall -Werror для C/C++).<br />
<br />
6. Программы следует измерять в различных режимах. Во-первых, как я уже упоминал, следует отдельно держать тесты для однопоточной и многопоточной реализации. Кроме того, следует иметь отдельные измерения для базового (т.е. без компиляторных оптимизаций) и пикового (т.е. со специально подобранными опциями) режимов.<br />
<br />
7. В результатах следует указывать характеристики машины, на которой всё запускалось. Т.е. нужно знать архитектуру, микроархитектуру, свойства памяти, версию ядра, версию компилятора, вообще всё что может хоть как-то влиять на результаты.<br />
<br />
8. Для каждого процессора более оптимальной может быть своя версия реализации алгоритма. Более того, существует практика подбора специальных библиотек для накрутки результата (привет, jmalloc, которым на спеках результат накручивают). Для тестов SPEC мне это кажется не особо честным, но в случае соревонавания по получению наиболее быстрой программы сойдёт.<br />
<br />
9. Сложность реализации каждого теста. Это очень большой вопрос, т.к. простые тесты не дают понимания реальной производительности, а сложные тесты на разных языках никто не будет поддерживать. Так что я понимаю почему в benchmark game был выбран именно такой подход.<br />
<br />
Это всё мысли, которые приходят при беглом анализе существующих бенчмарков. Даже из них можно видеть что это очень сложная задача, которая не поднимается одиночками, а хорошие наборы стоят тысячи долларов. В то же время, эти (и многое другие вещи) стоит помнить когда кто-то пытается рассуждать о том что язык X быстрее языка Y.<br />
<br />
Обновление:<br />
<br />
В обсуждения принесло <a href="https://rosettacode.org/wiki/Rosetta_Code">ссылку</a> на весьма занятный проект "Розетского камня" для языков программирования. Хотя там не делается замеров, но объясняется как на каждом языке программирования реализовать ту или иную конструкцию. В общем, мне понравилось.<br />
<br /></div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com0tag:blogger.com,1999:blog-935622623924408531.post-8389110914350950942018-05-02T17:45:00.000+02:002018-05-02T21:10:25.546+02:00Answer to "Is software prefetching (__builtin_prefetch) useful for performance?"<div dir="ltr" style="text-align: left;" trbidi="on">
Recently I've read a short <a href="https://lemire.me/blog/2018/04/30/is-software-prefetching-__builtin_prefetch-useful-for-performance/">note</a> where an author tells that software prefetch is useless for performance. I think that it would be interesting to listen to another point of view. <br />
<a name='more'></a>First of all let's say a few words about software prefetch itself. Prefetch is an instruction in modern processor that makes possible to load data from given address into cache. That is done before some read/write access to eliminate memory latency during the effective access. Prefetch may be hardware or software. In the current note we will speak about software prefetch.<br />
<br />
The first thing that should be said about prefetch is that it mainly should be filled in by a compiler. This proposition grows up from the principal of its work. The success of prefetch optimization depends on the guess about the time when the data is needed by a program. If we insert prefetch too early, the data by the address will not be in the cache. If we insert prefetch too late the given cache line will be useless for us. The exact time of the prefetch instruction strongly depends on the target hardware memory system and it is not very portable.<br />
<br />
Even for a compiler it may be difficult to find a good place for a prefetch. So there is a <a href="https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html">builtin</a> to make available to insert prefetch by hand.<br />
<br />
Now let us see a loop that we are going to make faster with prefetch (<a href="https://github.com/alexanius/algorythms/tree/master/prefetch_test">code</a> on github):<br />
<br />
<code> for(int i = 0; i < ARR_SIZE; i+= unroll)<br />
{ <br />
res += data[i] * A * B + C - D * E;<br />
res += data[i + 1] * A * B + C - D * E;<br />
}</code><br />
<br />
We can see that this is simple loop counting some kind of sum of all elements of an array. It is unrolled with factor 2 by hand to make good effect for prefetch. In normal case this is the work for a compiler to find optimal combination of optimizations with a prefetch instruction. Our prefetch version is:<br />
<br />
<code> int interval = 32;<br />
<br />
for(int i = 0; i < ARR_SIZE; i+= unroll)<br />
{<br />
__builtin_prefetch(&data[i + interval], 0, 0);<br />
res += data[i] * A * B + C - D * E;<br />
res += data[i + 1] * A * B + C - D * E;<br />
}</code><br />
<br />
In this version we see a regular prefetch of an element that will be used only after 32 iterations of a loop. This is heuristic value and of course <b>you should not do this in real application</b>. But my aim was to make a simple example were prefetch speed ups a program.<br />
<br />
So let us see the geometrical mean of five launches of two versions of loops. The launches are made by the line: <code>gcc regular_load.c && taskset -c 1 ./a.out && gcc -DPREF regular_load.c && taskset -c 1 ./a.out</code>. The mean results:<br />
<br />
<br />
<table>
<tbody>
<tr><td>Simple</td><td>3.9906</td><td></td></tr>
<tr><td>Prefetch</td><td>3.9372</td></tr>
</tbody></table>
<br />
Let us see what happens with cache:<br />
<br />
<code>$ gcc regular_load.c && perf stat -B -e cache-misses,cache-references ./a.out <br />
Simple start<br />
Simple seconds: 3.935150<br />
<br />
Performance counter stats for './a.out':<br />
<br />
111 588 965 cache-misses:u # 71,735 % of all cache refs <br />
155 557 440 cache-references:u <br />
<br />
6,468928214 seconds time elapsed</code><br />
<br />
<code>$ gcc -DPREF regular_load.c && perf stat -B -e cache-misses,cache-references ./a.out <br />Prefetch start<br />Prefetch seconds: 3.962791<br /><br /> Performance counter stats for './a.out':<br /><br /> 71 325 602 cache-misses:u # 29,053 % of all cache refs <br /> 245 499 956 cache-references:u <br /><br /> 6,456740799 seconds time elapsed</code>
<br />
<br />
We can see that prefetch allowed us to decrease cache misses from 71.735% to 29.053%. The performance improvement is 1.36%. We can say this is not a very good result and we will be right. The main problem is that prefetch itself takes time for execution. A good solution for that case will be the asynchronous array access unit as it was implemented in the <a href="https://en.wikipedia.org/wiki/Elbrus-8S">Elbrus</a> processors.<br />
<br />
The other case when prefetch may be useful is loads by non-regular addresses for example in recursive structures as lists or trees. May be I will write about it later.<br />
<br /></div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com0tag:blogger.com,1999:blog-935622623924408531.post-81464927906312223832017-10-13T00:45:00.000+02:002017-10-13T00:45:05.293+02:00Выступил в SDCast<div dir="ltr" style="text-align: left;" trbidi="on">
Недавно поучаствовал в двух выпусках подкаста <a href="https://sdcast.ksdaemon.ru/">SDCast</a>. Если кто не в курсе, подкаст - это что-то вроде радиопередачи, только по интернету :)<br />
<br />
<a name='more'></a><br />
<a href="https://sdcast.ksdaemon.ru/2017/09/sdcast-62/">Первый</a> выпуск прошёл совместно с интересными коллегами из Интела - Александром Титовым и Амиром Аюповым. Выпуск получился очень интересным, разделённым на два больших тематических блока. Сначала по большей части ребята рассказывали про актуальное развитие аппаратуры, а во второй половине обсудили состояние образование в сфере ИТ (благо и я и Александр являемся действующими преподавателями).<br />
<br />
<a href="https://sdcast.ksdaemon.ru/2017/10/sdcast-63/">Второй</a> выпуск прошёл только со мной, я рассказывал истории из жизни, немного про МЦСТ, немного про Эльбрус, немного про компиляторы.<br />
<br />
В общем, надеюсь, что получилось интересно, слушайте и подписывайтесь на SDCast, потому как это, наверное, один из наиболее интересных подкастов в русскоязычном сегменте. Отдельное спасибо автору подкаста - Константину Буркалёву за предоставленную возможность и вообще интересный проект :)</div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com0tag:blogger.com,1999:blog-935622623924408531.post-21473135070307796272017-01-14T19:51:00.000+02:002017-01-14T19:51:13.141+02:00Как бы я изменил язык Си в 2017 году<div dir="ltr" style="text-align: left;" trbidi="on">
В этом посте мне хотелось бы порассуждать на тему того что я бы поменял/убрал/добавил в язык Си. Си является моим основным языком, более того уже больше 4 лет я занимаюсь разработкой оптимизирующего компилятора Си, который пишется на Си. За это время у меня накопились некоторые мысли на тему того что должен и чего не должен современный язык, какие проблемы есть в Си, как их можно было бы решить.<br />
<br />
<a name='more'></a><h4 style="text-align: left;">
Введение </h4>
<br />
Для начала объясню чем мне нравится Си - он простой, предсказуемый, быстрый язык, который позволяет взять и сделать то что тебе нужно. Если писать по стандарту, то вероятность огрести неадекватную плоходиагностируемую проблему мала. Но к сожалению, Си позволяет писать не по стандарту, что приводит к большому количеству проблем. Более того некоторые пункты стандарта написаны не лучшим образом, некоторые его пункты не отвечают современным требованиям. Поэтому мне хотелось бы сохранить простоту и скорость языка, устранив из него устаревшие или неправильные на мой взгляд моменты. Возможно при этом добавить ещё несколько простых возможностей.<br />
<br />
Сначала пройдёмся по уже существующим возможностям. Чтобы упростить чтение подчёркиванием я выделил основной вердикт по тому или иному пункту. <br />
<br />
<h4 style="text-align: left;">
Убрать или изменить </h4>
<h3 style="text-align: left;">
</h3>
<h3 style="text-align: left;">
Преобразование указателя в целое и наоборот</h3>
<br />
Начнём с не самого очевидного пункта, но от него надо <u>избавиться вообще</u> полностью. Почему это вдруг мешает? Во-первых результат таких действий <b>implementation-defined</b>, т.е. зависит от реализации компилятора. <a href="https://www.securecoding.cert.org/confluence/display/c/INT36-C.+Converting+a+pointer+to+integer+or+integer+to+pointer">Здесь</a> очень хорошо расписано к чему могут приводить такие операции. Есть ещё одна вещь, о которой почти никто не задумывается - потеря информации о типе указателя. В Эльбрусе, например, <a href="http://elbrus.ru/arhitektura_elbrus">существует</a> аппаратная возможность контроля типов, но такое приведение указателя полностью перечёркивает её.<br />
<br />
<h3 style="text-align: left;">
Приведение типов указателей</h3>
<br />
Это очень частый источник ошибок, которые игнорируются программистами. Кратко: в Си нельзя к переменной типа <code>int</code> обращаться через указатель типа <code>float</code>:<br />
<br />
<code>int i;<br />
float * f = (float *) &i;<br />
*f = 5.0; // Undefined behaviour </code>
<br />
<br />
К сожалению мало того что компилятор позволяет творить такое безобразие, его ещё часто используют на практике. <u>От него надо также полностью избавиться.</u><br />
<br />
Более сложная проблема - указатели на <code>void</code> и <code>char</code>. От них также необходимо избавиться, но на данный момент я не понимаю получится ли сделать это без противоречий к последующим пунктам того что я хочу.<br />
<br />
<h3 style="text-align: left;">
Неявные приведения типов</h3>
<br />
Неявные приведения типов необходимо <u>полностью запретить</u>. Они служат источником неочевидностей и ошибок. На всякий случай - описание правил приведения типов в Си: <a href="https://www.safaribooksonline.com/library/view/c-in-a/0596006977/ch04.html">вот</a> и <a href="https://www.securecoding.cert.org/confluence/display/c/INT02-C.+Understand+integer+conversion+rules">вот</a>. Я хочу видеть статический строго типизированный язык, это позволит устранить ошибки и ускорить исполнение.<br />
<br />
<h3 style="text-align: left;">
<b>Синтаксис typedef</b></h3>
<br />
На данный момент он ужасен. Как только нам надо сделать alias для указателя на функцию жизнь превращается в боль. Сейчас это выглядит примерно так:<br />
<br />
<code>typedef void (*SignalHandler)(int);</code><br />
<br />
Каждый раз приходится вспоминать что как и за чем следует. На мой взгляд подобный синтаксис должен быть примерно таким:<br />
<br />
<code>alias SignalHandler = * (void)(int);</code><br />
<br />
Также я уже писал про проблемы с <a href="http://alexanius-blog.blogspot.ru/2012/09/typedef-const.html">typedef и const</a>, это ещё один пример совершенно безумного синтаксиса, и его <u>нужно переделать</u>.<br />
<br />
<h3 style="text-align: left;">
void в сигнатуре функции</h3>
<br />
Сейчас если функция не содержит аргументов, то необходимо писать <code>void</code> в её сигнатуре, иначе компилятор будет считать что это K&R стиль и множество оптимизаций от неё тупо отвалят:<br />
<br />
<code>int foo() {return 1;} // K&R - плохо<br />
int bar(void) {return 1} // Си - хорошо</code><br />
<br />
Это историческое наследие, от которого <u>давно пора избавиться</u>.<br />
<br />
<h3 style="text-align: left;">
Прототипы функций</h3>
<br />
С ними ситуация тоже сложная. К сожалению в некоторых случаях Си позволяет использовать функции без прототипа, достраивая его самостоятельно. Я уже <a href="http://alexanius-blog.blogspot.ru/2015/12/c.html">писал</a> про проблемы, к которым это приводит. <u>Как минимум нужно всегда обязывать писать прототипы функций</u>. Но я бы пошёл дальше и вообще запретил бы их :) Ниже будет понятно почему, если в кратце, то компилятор должен видеть всю компилируемую программу, соответственно всегда должна быть видна реализация функции.<br />
<br />
<h3 style="text-align: left;">
Проблемы с enum</h3>
<br />
Ещё один источник проблем - то что <code>enum</code> не является отдельным типом. На самом деле это <code>int</code>, что тоже приводит к ошибкам. В частности, есть возможность присвоения типа <code>int</code> объекту типа <code>enum</code>, и, что ещё хуже, есть возможность присвоения значения объекта одного enum'а объекту другого enum'а. Такие вещи должны быть запрещены. <u>Сам enum - самостоятельный тип со всеми вытекающими</u>.<br />
<br />
<h3 style="text-align: left;">
Signed и unsigned типы</h3>
<br />
Есть с ними интересная проблема. Так по стандарту <code>signed int</code> не может переполняться, значит компилятор всегда подразумевает что поведение таких переменных предсказуемо и всегда справедливо неравенство: <code>(i+1) > i</code>. С <code>unsigned</code> всё не так, и мы не можем исходить из такого предположения. Это не позволяет применяться некоторым оптимизациям. Сейчас мне видится что <u>их поведение должно быть унифицировано и переполнения должны быть исключены</u>.<br />
<br />
<h3 style="text-align: left;">
Массивы переменной длины (vla)</h3>
<br />
В c99 были введены variable length arrays - массивы переменной длины. Это объект, память под который выделяется на стеке, но при этом его размер неизвестен во время компиляции. Особо много проблем они доставляют если помещать их в середине структуры (непонятно как считать её размер, как вообще это обрабатывать). Да и просто работа оптимизаций с ним крайне затруднительна. <u>В нормальном языке VLA быть не должно</u>.<br />
<br />
<h3 style="text-align: left;">
union</h3>
<br />
Тоже очень больная тема для Си. Для них стандарт прописан очень криво и муторно, основная проблема с ними в том что в одной области памяти могут лежать данные разных типов, и во время компиляции мы не знаем конкретный тип на данный момент. Совсем кошмар начинается если на union внезапно берут указатель (а ещё хуже если на его поле). Тогда компилятор полностью теряет возможность отслеживать происходящее. <u>У меня есть понимание что union'ы нужны, но пока нет понимания как их грамотно сделать</u>.<br />
<br />
<h3 style="text-align: left;">
Глобалы</h3>
<br />
<u>Глобалы нужно запретить</u>. Никаких <code>extern int</code>. Самое глобальное что только можно делать - <code>static</code> объекты, которые видны только внутри модуля. Если кому-то понадобится прочитать/записать глобальное значение, то это очень легко реализуется через <code>extern</code> функции, меняющие <code>static</code> объект.<br />
<br />
<h3 style="text-align: left;">
Арифметика указателей</h3>
<br />
Тоже довольно интересный момент. Она даёт большую гибкость в работе с памятью, но в реальности выливается в совершенно уродливое хаккерство, нарушающее стандарт и убивающее переносимость. <u>Для всех объектов и типов (кроме, возможно char) её следует запретить</u>.<br />
<br />
<h3 style="text-align: left;">
Конструкция switch</h3>
<br />
Сейчас она ужасна и приводит к ошибкам, <u>её нужно полностью переделать</u>. Во-первых каждый case должен быть отдельным лексическим блоком, окончанием которого должен быть break. Во-вторых имеет смысл добавить нормальный синтаксис для перечисления диапазонов значений switch. <u>Нужно всегда явно требовать default ветки</u>.<br />
<br />
<h3 style="text-align: left;">
inline</h3>
<br />
<u>Убрать</u>. Сейчас компиляторы всё равно по дефолту игнорируют это ключевое слово. В реальности же программист сам не может знать нужно делать подстановку функции или нет, часто это приводит к деградациям. Этим вопросом должен заведовать компилятор. Также неплохо бы избавиться от других устаревших ключевых слов (register, auto и т.п.).<br />
<br />
<h3 style="text-align: left;">
Макросы</h3>
<br />
С ними тоже очень неоднозначная ситуация. Макросы очень полезны для условной компиляции, поэтому в том или ином виде <u>я бы оставил <code>#ifdef</code> и <code>#if</code>.</u> <u>Я бы полностью избавился от <code>#include</code>.</u> Ещё <u>необходимо полностью запретить конкатенацию макросов</u> - генерация имени функции в compile time это сущий ад, за такое хочется убивать. Далее <code>#define</code>. С одной стороны он позволяет делать функции высшего порядка. Например мы в качестве аргумента можем подавать участки кода:<br />
<br />
<code>#define debug(actions) \<br />{ \<br /> if ( enablePrint ) \<br /> { \<br /> actions; \<br /> } \<br />}</code>
<br />
С другой - она является источником ошибок. Не являясь конструкцией языка, она не делает проверку типов своих аргументов, <a href="https://www.securecoding.cert.org/confluence/pages/viewpage.action?pageId=158237185">здесь</a> довольно много пунктов как с ними следует обращаться. <u>Поэтому я скорее склоняюсь к тому что <code>#define</code> необходимо убрать.</u><br />
<u><br /></u>
<h3 style="text-align: left;">
goto, longjump</h3>
<br />
<u>Убрать</u>. Я знаю что есть техники, в которых goto может быть красив и полезен. Но это не отменяет вреда от его использования. Более того я знаю что есть техники где без longjump не обойтись, но всё же он доставляет больше проблем, а места его использования следует переписать.<br />
<br />
<h3 style="text-align: left;">
Система сборки</h3>
<br />
Текущая система сборки Си не отвечает современным требованиям. В Си есть "единица трансляции" - один модуль, т.е. .c файл. Компилятор генерирует из них объектный файл, потом линкует. Такая схема приводит к множеству проблем. Про проблемы с сигнатурами функций я уже писал, более того это приводит к <a href="http://stackoverflow.com/questions/45135/why-does-the-order-in-which-libraries-are-linked-sometimes-cause-errors-in-gcc">зависимости от порядка линковки</a>! Ну и как бонус - такая система не позволяет делать межмодульные оптимизации, что не позволяет нормально оптимизировать программы. <u>Современный компилятор для современного языка должен собирать всё в режиме "вся программа"</u>. Это более продвинутая (и более сложная) техника чем lto, но только так можно обеспечить качественные и быстрые приложения. Тут есть проблемы с библиотеками (особенно подключением динамических библиотек), пока что я не знаю как их разрешить.<br />
<br />
<h3 style="text-align: left;">
One Definition Rule</h3>
<br />
Как следствие из предыдущего пункта в языке должен действовать ODR. Это правило есть в C++, оно говорит о том что в лексическом блоке одному имени может соответствовать только одна реализация класса. <u>Это правило должно быть обязательно</u>.<br />
<br />
<h3 style="text-align: left;">
static</h3>
<br />
На данный момент все переменные вне функций и сами функции неявно считаются extern'ами, т.е. видны другим модулям. <u>По умолчанию функции должны быть static, глобалы вообще могут быть только static.</u><br />
<u><br /></u>
<h3 style="text-align: left;">
Подсказки компилятору</h3>
<br />
Сейчас подобные вещи реализиуютсячерез #pragma или через __attribute__. <u>Я бы убрал оба варианта и сделал унифицированный способ подачи метаинформации</u>. Пока сложно сказать как это должно выглядеть, потому как метаинформация может быть нужна для типов, для объектов, для синтаксических конструкций.<br />
<br />
<h3 style="text-align: left;">
Unspecified и Implementation-defined behavior</h3>
<br />
В Си <a href="http://stackoverflow.com/questions/18420753/unspecified-undefined-and-implementation-defined-behavior-wiki-for-c">существует</a> три типа неопределённого поведения: unspecified, implementation-defined и undefined. <u>Первые два типа я бы убрал полностью</u>. <u>Если же компилятор может статически доказать undeifned behavior, программа не должна собираться</u>.<br />
<br />
<h4 style="text-align: left;">
Добавить</h4>
<br />
Выше были пункты, которые я бы убрал/переделал. А теперь хотелось бы показать то что я в язык добавил бы. Некоторые пункты можно легко добавить без накладных расходов на реализацию и изменения концептов языка, некоторые могут противоречить моим требованиям, поэтому я не уверен на сколько их стоит добавлять.<br />
<br />
<h3 style="text-align: left;">
JIT</h3>
<br />
Под jit может подразумеваться несколько вещей, поэтому поясню. Во-первых мне кажется интересной возможность выполнить eval в языке. Т.е. скомпилировать строку прямо во время исполнения и обращаться к фунциям и неё. Ещё одной возможностью является перекомпиляция функций если во время исполнения выясняется что они были соптимизированы неоптимально. Это довольно сложная фича и у меня пока нет понимания возможно ли её реализовать "малой кровью", т.е. без переноса исполнения в виртуальную машину. <br />
<br />
<h3 style="text-align: left;">
Обобщённые функции</h3>
<br />
В Си есть некоторые проблемы с полиморфизмом. Наименьшая - его отсутствие, но она тянет все другие. Разделим проблему на две части. Первая - это полиморфизм по отношению к вложенным структурам. На самом деле его можно делать на вполне законных основаниях (тут strict-aliasing нарушаться не будет), но т.к. я хочу запретить адресную арифметику, с этим будут проблемы. Вторая проблема - каждый тип данных требует реализации отдельной функции, например если мы делаем список, то у нас будет отдельная функция для добавления целого, отдельная для плавающего и т.д.<br />
<br />
Это заставляет задумать о механизме обобщённых функций (или перегрузке), которые избавят нас от всех этих проблем. Но тут возникнет другая сложность - я хочу избежать манглирования. Основная идея в том что имя функции из дизассемблера должно легко находиться в исходнике. Поэтому перед введением такой вкусной фичи надо много думать и хорошенько всё взвесить.<br />
<br />
<h3 style="text-align: left;">
Классы</h3>
<br />
Большие и сложные проекты на Си в любом случае сводятся к написанию собственной системы объектов и классов, иногда даже с наследованием. Такие вещи хотелось бы иметь из коробки. Т.е. как минимум хотелось бы уметь создавать методы объектов, конструкторы/деструктры. Но методы опять же усложняют язык, что противоречит моей изначальной цели. Поэтому тут тоже следует всё хорошенько обдумать.<br />
<br />
<h3 style="text-align: left;">
Параметры по умолчанию, именованные параметры</h3>
<br />
Очень полезным было бы добавление в функцию параметров по умолчанию и именованных параметров. По идее это не должно сильно усложнять компилятор и язык, но при этом является весьма полезной возможностью.<br />
<br />
<h3 style="text-align: left;">
Инициализация полей структуры</h3>
<br />
Хотелось бы иметь возможность делать так:<br />
<br />
<code>typedef struct {<br />
int a = 1;<br />
float b = 2.0;<br />
} MyStruct_t;</code> <br />
<h3 style="text-align: left;">
<br /></h3>
<h3 style="text-align: left;">
Неизменяемые поля</h3>
<div style="text-align: left;">
<br />
Хочется уметь навешивать признак <code>immutable</code> на поля структуры, чтобы показать что они не будут меняться в течении работы программы. Вообще это некоторого рода синтаксический сахар, но иметь такою возможность было бы полезно, благо её легко поддержать в оптимизаторе.<br />
</div>
<h3 style="text-align: left;">
Вложенные комментарии</h3>
<br />
Можно спокойно жить и без них, но мне кажется это было бы удобно.<br />
<br />
<h3 style="text-align: left;">
Многострочные строки</h3>
<br />
В python есть отличная возможность создавать много строчные литералы:<br />
<br />
<code>"""<br />
текст<br />
текст<br />
текст<br />
"""</code><br />
<br />
Хотелось бы иметь такою же возможность в своём языке.<br />
<br />
<h3 style="text-align: left;">
Синтаксис для регулярных выражений</h3>
<br />
В C++11 был добавлен специальный синтаксис для описания регулярных выражений:<br />
<br />
<code>regex integer("(\\+|-)?[[:digit:]]+");</code><br />
<br />
В он был бы крайне полезен.<br />
<br />
<br />
<h4 style="text-align: left;">
Заключение</h4>
<br />
В этом посте я поразмышлял над тем каким я хотел бы видеть Си, что поменял бы в нём. Это очень субъективный пост, на которой во многом повлияло то что я занимаюсь разработкой компилятора. Когда я только начинал думать на этот счёт, казалось что я получу просто более строгий Си, но в реальности получается принципиально другой язык.<br />
<br />
<ol style="text-align: left;">
</ol>
</div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com0tag:blogger.com,1999:blog-935622623924408531.post-30242927709204134652016-07-16T21:16:00.000+02:002016-07-16T21:19:48.331+02:00Эльбрусы: информационное поле и пропаганда<div dir="ltr" style="text-align: left;" trbidi="on">
Сегодня будет несколько необычный пост для моего технического бложика. В комментах недавнего <a href="http://alexanius-blog.blogspot.ru/2016/05/hello-world.html">поста</a> где развели очередное трололо про Эльбрус, я пообещал одному уважаемому Анониму ответить на его высказывание отдельным постом. Господа тролли, это пост для вас!<br />
<br />
<a name='more'></a>Для начала приведу два его комментария.<br />
<br />
<a href="http://alexanius-blog.blogspot.ru/2016/05/hello-world.html?showComment=1468509287950#comment-2786863539">Первый</a>: <br />
<blockquote class="tr_bq">
<span id="bc_0_27b+seedH_d3D" kind="d">Скажите, а можно объяснить
постоянные теоретические рассуждения тем, что проект Эльбруса
представлен как теоретический проект в основном в виде каких-то ссылок
на результаты, полученные где-то. А возможно ли такое, что если Эльбрус
станет доступен практически, то количество теоретических рассуждений
будет уменьшаться?<br /><br />Если Эльбрус изначально задуман, как проект
для военных и для "узкого круга ограниченных людей", то зачем тогда о
нем писать в технических новостях общего назначения?<br /><br />Конкретной
ссылке в интернете доверять нельзя, оценку делать можно статистикой.
Почему 80% ссылок на Эльбрус негативные, а только 20% - позитивные. В
чем тут может быть проблема?<br />...</span></blockquote>
<span id="bc_0_27b+seedH_d3D" kind="d"><a href="http://alexanius-blog.blogspot.ru/2016/05/hello-world.html?showComment=1468509287950#comment-2786863528">Второй</a>:</span><br />
<blockquote class="tr_bq">
<span id="bc_0_27b+seedH_d3D" kind="d"><span id="bc_0_27b+seedH_d3D" kind="d">Да, есть много других
политических блогов, на которых ничего не узнаешь. Пишу тут, потому что
хочется узнать из первых рук мнение профи, кто пишет на ассемблере под
эльбрус. Давайте, чтобы было все в порядке, я буду спрашивать общий
вопрос и сразу что-то по ассемблеру. (Если так плохо, то напишите, чтобы
я больше не писал вообще, и я больше писать не буду).</span></span><br />
<span id="bc_0_27b+seedH_d3D" kind="d"><span id="bc_0_27b+seedH_d3D" kind="d">Однажды к
Путину на передачу Ярмольник привел Шевчука из ДДТ. Шевчук спросил
Путина, почему все так плохо. В ответ получил, что не надо все
уравнивать, были отдельные частные хорошие случаи, про которые не надо
забывать, и начал их перечислять в качестве ответа. У Вас ответы очень
похожие - на вопрос о тенденции, Вы отвечаете перечислениями частных
случаев, которые не меняют этой тенденции. (80% случаев - это тенденция
:)</span></span> <br />
...</blockquote>
Вообще с определённого момента я стал отслеживать информационное поле вокруг процессоров Эльбрус и кажется пришло время поделиться моими наблюдениями. Но для начала выделим тезисы, высказанные Анонимом:<br />
<ol style="text-align: left;">
<li>Множественные рассуждения про Эльбрус не имеют под собой фактов т.к. Эльбрус, он как бы есть, но его как бы и нет.</li>
<li>Возможно ли уменьшение количества теоретических рассуждений при более массовой доступности Эльбруса?</li>
<li><span id="bc_0_27b+seedH_d3D" kind="d">Если Эльбрус изначально задуман, как проект
для военных и для "узкого круга ограниченных людей", то зачем тогда о
нем писать в технических новостях общего назначения?</span></li>
<li>80% ссылок на Эльбрус негативные, а 20% - позитивные. Т.е. статистически можно считать проект Эльбруса неудачным. Справедливо ли данное высказывание и как это можно объяснить?</li>
<li>Мои ответы сводятся к перечислению отдельных хороших частных случаев но статистически их довольно мало.</li>
</ol>
Начнём с <b>первого</b> тезиса. Конечно же он в корне неверен. Для начала давайте оговоримся что мы рассматриваем процессоры с архитектурой e2k, т.к. процессоры серии R, основанные на Sparc v9, используются преимущественно <a href="https://ru.wikipedia.org/wiki/%D0%9C%D0%A6%D0%A1%D0%A2-R1000">военными</a> ставить под сомнение их существование странно. Итак, у нас есть процессоры <a href="http://www.mcst.ru/elbrus_2c_plus">Эльбрус-2С+</a>, <a href="http://www.mcst.ru/mikroprocessor-elbrus2sm">Эльбрус-2СМ</a> и <a href="http://www.mcst.ru/mikroprocessor-elbrus4s">Эльбрус-4С</a>. Новостей о данных процессорах предостаточно в совершенно различных источниках: например известный обзор от <a href="http://zoom.cnews.ru/publication/item/51820">CNews</a>, <a href="http://www.interfax.ru/business/487824">Интерфакс</a>, <a href="https://lenta.ru/articles/2015/07/16/baikal/">Лента</a>, даже, прости, Господи, <a href="https://meduza.io/feature/2015/05/29/nestydnoe-importozameschenie">Медуза</a>, внезапно не облившая всё потоком фекалий. Более того, МЦСТ постоянно <a href="http://alexanius-blog.blogspot.ru/2014/03/blog-post.html">участвует</a> в различных <a href="http://ammo1.livejournal.com/730326.html">выставках</a> микроэлектроники где любой желающий может подойти и поиграться с машинами. Более того можно попробовать написать в МЦСТ и попросить удалённый доступ к машине. Я уже не говорю о том что они доступны любому студенту ФРТК кафедры системного программирования. В последнее время стали появляться очень интересные статьи от <a href="https://habrahabr.ru/company/smartengines/blog/304750/">пользователей</a> Эльбруса, и от <a href="https://geektimes.ru/post/270382/">людей</a>, которые вообще никак с МЦСТ не связаны.<br />
<br />
Т.е. видно что доступ к машинам имеет довольно широкий круг людей. Но зачем заниматься проверкой фактов, попыткой получить доступ к машине или поиском людей, которые хоть немного в курсе происходящего? Легче просто полить говном, обычно даже не читая статью (с.м. комменты к почти любым статьям в социальных сетях). В общем если кто-то сомневается в существовании Эльбрусов могу только порекомендовать носить шапочку из фольги.<br />
<br />
Надеюсь что ответ на первый тезис дан. Теперь перейдём ко <b>второму</b>.<br />
<br />
Я думаю что сразу после этого в интернете окажется поток совершенно технически неграмотных статей в стиле "оно тормозит", "не смог поставить винду", "ааа, там не проигрывается flash", "почему там не запускается Crysis". Рассмотрим даже вполне адекватный случай - человек взял какой-нибудь бенчмарк типа UnixBench, он покажет плохие результаты и вроде как это даже следствие объективного замера. Но есть одно "но". Тесты из данного бенчмарка устарели и для современных машин не подходят в принципе. Вообще сравнение производительности это довольно сложная тема, в которой нужно понимать что именно и как замеряется. Обычно люди совершенно не представляют что такое VLIW и какую роль в нём играет оптимизирующий компилятор, и, как следствие, качественный код.<br />
<br />
В общем я думаю что при массовом распространении Эльбрусов поток мифотворчества только увеличится, а грамотные статьи будут тонуть в потоке негатива.<br />
<br />
Переходим к <b>третьему</b> тезису.<br />
<br />
Сначала автор жалуется на малое количество информации по теме, а потом задаётся вопросом "а зачем мне эта информация"? Уж как-то определиться надо :) Вопрос "<span id="bc_0_27b+seedH_d3D" kind="d">зачем тогда о
нем писать в технических новостях общего назначения</span>" особенно шикарен, т.к. само МЦСТ этим практически не занимается. Т.е. о фактах внедрения данного процессора пишут либо СМИ, либо непосредственно пользователи. Так что рекомендую задать вопрос именно им :)<br />
<br />
<b>Четвёртый</b> тезис.<br />
<br />
Во-первых мне не очень понятно как именно автор вывел данные цифры (даже если это просто условные цифры для того чтобы показать общие настроения). Большинство новостей которые я периодически читаю - это просто обычные сообщения от СМИ про выход той или иной модели, заключение какого-то контракта и т.п. которые совершенно нейтральны. Есть различные технические новостные ленты, в которых проскальзывают новости про Эльбрус. Обычно они тоже крайне нейтральны, например <a href="http://www.engineering-info.ru/na_baze_elbrusa_sozdali_uvm/">вот</a>, <a href="http://www.pcweek.ru/infrastructure/news-company/detail.php?ID=186570">вот</a>, или <a href="http://www.elinform.ru/news_13313.htm">вот</a> (в поисковиках дофига таких ссылок).<br />
<br />
Далее есть статьи в бложиках. Это могут быть либо отчёты с каких-нибудь конференций (уже приводил), либо <a href="http://thesz.livejournal.com/1448450.html">собственные</a> <a href="http://thesz.livejournal.com/1448727.html">мысли</a> авторов. <a href="https://tjournal.ru/p/elbrus-price">Совершенно</a> <a href="http://www.flenov.info/blog.php?catid=2382">разные</a> <a href="http://www.liveinternet.ru/users/zerg_from_hive/post385155338/">мысли</a>. Постов от людей, тестировавших реальные Эльбрусы я не припомню. Вообще тут сложно судить об общем настроении постов, оно довольно разное. Плюс ещё встаёт вопрос выборки, ведь если включать сайт "<a href="http://sdelanounas.ru/blogs/?search=%D0%AD%D0%BB%D1%8C%D0%B1%D1%80%D1%83%D1%81">Сделано у нас</a>", то можно сильно повлиять на результаты.<br />
<br />
Ну и чаще всего упоминания Эльбрусов можно встретить в комментах. В комментах действительно открывается <a href="http://ammo1.livejournal.com/730326.html">портал в ад</a>. На нетехнических ресурсах их читать смысла вообще не имеет, т.к. понабежит толпа креаклов и расскажет про откаты и распилы. На технических ресурсах читать комменты... тоже смысла не имеет, но там могут попадаться люди, которые непосредственно работают с Эльбрусами, и их комментарии резко <a href="https://www.linux.org.ru/forum/talks/12592793?lastmod=1464070109992#comment-12597272">выделяются</a> на фоне остальных "почему стоит так дорого, но медленней чем Atom".<br />
<br />
Т.о. что мы видим. Характер сообщений очень зависит от типа сообщения (СМИ, блоги, комменты). Сообщения из класса блогов и комментов очень зависят от компетенции автора и от того имел ли он дела с реальным Эльбрусом. Опираться на мнение людей, которые не имеют никакого представления о теме я смысла не вижу, а остальных можно по пальцам пересчитать.<br />
<br />
И теперь <b>пятый</b> тезис.<br />
<br />
Собственно ответ на пятый тезис следует из четвёртого. Я привожу примеры статей от людей, которые либо понимают в теме, либо имели возможность реально протестировать данные машины. Даже к ним у меня могут быть определённые замечания по методике исследований, но там хотя бы можно вести предметный разговор. <br />
<br />
Это то что касается моего ответа на коммент. А теперь пару интересных собственных наблюдений.<br />
<br />
Иногда я натыкаюсь на самую настоящую пропаганду. К сожалению не нашёл статью 2014 или 2015 года про ноутбук и жёсткий диск, но от каклов была целая волна смешков по этому поводу. Но нет ссылки - нет и разговора. Из недавних поступлений - <a href="http://worldcrisis.ru/crisis/2169645">такая</a> статья или вот этот весьма забавный <a href="http://ammo1.livejournal.com/730326.html?thread=47188182#t47188182">товарищ</a>. Это примеры политической пропаганды. Кому и зачем она нужна оставим за рамками поста. Из этих примеров видно что авторы ради приличия почитали википедию чтобы совсем уж идиотами не казаться (однако не особо помогает). Но определённые ляпы их всё равно выдают. Основная задача таких деятелей - влияние на людей, незнакомых с предметом и вкладывание в их умы мысли о том что всё плохо, что у России нет микроэлектронной промышленности и что надо срочно выходить на улицы. Ещё частенько вспыхивают всякие фейки про ноутбуки и т.п., но их рассматривать вообще не за чем.<br />
<br />
Помимо специальных политических троллей есть просто разные <a href="http://blogerator.ru/page/innovacii-rossijskij-processor-elbrus-rossija-otkaty-offshornye-shemy-vorovstvo-nanoteh">каклы-балаболы</a>. В целом посыл их статей схож с политической пропагандой, но не думаю что им платят деньги непосредственно за такую подачу материала. Скорей всего это просто работа на свою целевую аудиторию (что не исключает того что они могут сами верить в то что пишут).<br />
<br />
Ещё есть идеологические противники импортозамещения. Что движет ими я сказать не могу, но <a href="http://forum.ixbt.com/topic.cgi?id=8:24948-133">тут</a> есть хороший пример того как умные дяденьки сидят и уже много лет в рабочее время троллят на форуме. Интересно то что у них нет возможности оценить актуальное состояние Эльбрусов, но трололо всё ещё продолжается.<br />
<br />
В общем что я могу сказать по всему этому... Ищите информацию в достоверных источниках, а не у анонимных аналитиков или <a href="http://www.pcworld.com/article/2920988/russias-homegrown-elbrus-processor-and-pc-would-be-fantastic-in-1999.html">журналистов</a>.<br />
<br />
А вообще у меня идея - кидайте мне в комменты статьи про Эльбрусы, а я через какое-то время сделаю подборку и классификацию. <strike>Это очень поможет когда придёт приказ 66.</strike><br />
<br /></div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com0tag:blogger.com,1999:blog-935622623924408531.post-84484616322262207172016-06-30T14:08:00.000+02:002016-06-30T14:08:16.804+02:00Управляющие конструкции в ассемблере<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
<a href="http://alexanius-blog.blogspot.ru/2016/05/hello-world.html">Первый</a> урок по ассемблеру состоял из обычного вывода сообщения. Теперь давайте посмотрим на управляющие конструкции.<br />
<br />
Содержание:<br />
<ol style="text-align: left;">
<li><a href="http://alexanius-blog.blogspot.ru/2016/06/blog-post_30.html#intro">Введение</a></li>
<li><a href="http://alexanius-blog.blogspot.ru/2016/06/blog-post_30.html#amd64">amd64</a></li>
<li><a href="http://alexanius-blog.blogspot.ru/2016/06/blog-post_30.html#sparcv9">sparc v9</a></li>
<li><a href="http://alexanius-blog.blogspot.ru/2016/06/blog-post_30.html#elbrus">Эльбрус</a></li>
<li><a href="http://alexanius-blog.blogspot.ru/2016/06/blog-post_30.html#epilogue">Заключение</a></li>
<li><a href="http://alexanius-blog.blogspot.ru/2016/06/blog-post_30.html#lit">Источники</a></li>
</ol>
<a name='more'></a><h3>
<a href="https://www.blogger.com/null" name="intro"></a>1. Введение</h3>
Итак, сформулируем задание: написать программу, сравнивающую два числа. Если первое число больше, программа выводит "gt", если равно - "eq", если меньше - "le".<br />
<br />
На Си программа выглядит следующим образом:<br />
<br />
<code>
#include <stdio.h></code><br />
<code><br />
</code><code><code>int a, b;</code></code><br />
<code><code><br />
</code>
int main()<br />
{<br />
scanf("%d%d", &a, &b);<br />
<br />
if( a > b )<br />
printf("gt\n");<br />
else if( a == b )<br />
printf("eq\n");<br />
else<br />
printf("le\n");<br />
<br />
return 0;<br />
}<br />
<br />
</code>
Сборка и запуск:<br />
<br />
<code>
$ gcc comp.c -o comp<br />
$ ./comp <br />
3 4<br />
le<br />
$ ./comp <br />
3<br />
3<br />
eq<br />
$ ./comp<br />
4 3<br />
gt<br />
</code>
<br />
<br />
Здесь мы уже используем стандартную библиотеку чтобы не возиться с системными вызовами. Использование глобальных переменных сделано специально (чтобы пока не объяснять работу со стеком).<br />
<br />
Теперь посмотрим как эту программу писать на ассемблерах.<br />
<br />
<h3>
<a href="https://www.blogger.com/null" name="amd64"></a>2. amd64</h3>
Программа писалась для процессора Core i5, ОС Gentoo GNU/Linux, синтаксис AT&T.<br />
<br />
<code>
.section .data<br /> scanf_str:<br /> .string "%d%d\0"<br /> gt_str:<br /> .string "gt\n</code><code><code>\0</code>"<br /> eq_str:<br /> .string "eq\n</code><code><code>\0</code>"<br /> le_str:<br /> .string "le\n</code><code><code>\0</code>"<br />
<br />.section .bss<br /> .lcomm a, 32<br /> .lcomm b, 32<br /><br />.section .text<br /> .globl _start<br /><br />_start:<br /><br /> # Считываем два числа<br /> mov $scanf_str, %rdi # Первый аргумент - форматная строка<br /> mov $a, %rsi # Второй аргумент - адрес первого числа<br /> mov $b, %rdx # Третий аргумент - адрес второго числа<br /> call scanf # Вызов scanf<br /><br /> # Кладём считанные сравнения на регистры<br /> mov a, %rax<br /> mov b, %rbx<br /><br /> # Сравниваем значения регистров<br /> cmp %rbx, %rax<br /><br /> jg .print_gt # Если больше, то идём на участок, печатающий "gt"<br /> je .print_eq # Если равно, то идём на участок, печатающий "eq"<br /><br /> # Если переходов не было, то печатаем "le"<br /> mov $le_str, %rdi<br /> call printf<br /><br /> # Теперь безусловно идём на выход<br /> jmp .exit<br /><br />.print_gt:<br /> mov $gt_str, %rdi<br /> call printf<br /> jmp .exit<br /><br />.print_eq:<br /> mov $eq_str, %rdi<br /> call printf<br /><br /># Здесь выходим из программы<br />.exit:<br /> mov $60, %rax<br /> mov $0, %rdi<br /> syscall<br />
</code>
<br />
<br />
Сборка и запуск:<br />
<br />
<code>
$ as t.s -o t.o && ld t.o -o a.out -lc --dynamic-linker /lib/ld-2.23.so<br />
$ ./a.out<br />
3 4<br />
le<br />
$ ./a.out<br />
3<br />
3<br />
eq<br />
$ ./a.out<br />
4 3<br />
gt<br />
</code>
<br />
<br />
Видно, что теперь к сборке добавились опции <b>-lc --dynamic-linker /lib/ld-2.23.so</b>. Опция <b>-lc</b> говорит о том, что нам надо линковаться с libc.a (стандартная библиотека), <b>--dynamic-linker</b> задаёт конкретный бинарник динамического линковщика.<br />
<br />
Теперь посмотрим на новые элементы в исходном коде. Во-первых мы задействовали секцию <code>.bss</code>. В ней хранятся статические переменные (т.е. локальные для данного модуля). <br />
<br />
Операция <code>.lcomm symbol, length</code> является псевдо операцией. Она резервирует length байт для локальной переменной, обозначаемой symbol. Т.о. мы выделили память для двух локальных переменных, в которые будем записывать результаты <b>scanf</b>.</div>
<br />
Теперь немного про код, вызывающий <b>scanf</b>. Сейчас мы используем передачу аргументов через регистры. В соответствии с соглашениями <a href="http://alexanius-blog.blogspot.ru/2016/06/blog-post_30#amd64abi">[amd64abi]</a> для передачи аргументов используются следующие регистры:<br />
<ul>
<li><b>rdi</b> - первый аргумент</li>
<li><b>rsi</b> - второй аргумент</li>
<li><b>rdx</b> - третий аргумент</li>
<li><b>rcx</b> - четвёртый аргумент</li>
<li><b>r8</b> - пятый аргумент</li>
<li><b>r9</b> - шестой аргумент</li>
</ul>
Последующие аргументы передаются через стек.<br />
<br />
Далее рассмотрим инструкцию <code>cmp</code>. Она вычисляет разницу между двумя целочисленными операндами и в зависимости от результата обновляет один из следующих флагов: <b>OF</b>, <b>SF</b>, <b>ZF</b>, <b>AF</b>, <b>PF</b>, <b>CF</b>.<br />
<br />
Немного про данные флаги. В процессоре Intel существует специальный регистр <b>EFLAGS</b>, содержащий группу статусных флагов, флаг управления и группу системных флагов. Графически их можно представить так (взято из <a href="http://alexanius-blog.blogspot.ru/2016/06/blog-post_30.html#intel1">[intel1]</a>):<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuZfTtXuYhBywKVblOYLJmjaXrn-42w15KEU4IgTM50-nuf-L0hz7_5uDOECfBBlvtFF_QiNbAXZAt0V2YLoIBBFhIPVdYeaeFMBhypF4jKOjIwAW6VIMNtTLVLwrPJKgyAmK17dV8Pg2H/s1600/eflags.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="259" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuZfTtXuYhBywKVblOYLJmjaXrn-42w15KEU4IgTM50-nuf-L0hz7_5uDOECfBBlvtFF_QiNbAXZAt0V2YLoIBBFhIPVdYeaeFMBhypF4jKOjIwAW6VIMNtTLVLwrPJKgyAmK17dV8Pg2H/s320/eflags.png" width="320" /></a></div>
Рассмотрим флаги, на которые влияет cmp:<br />
<ul style="text-align: left;">
<li><b>OF</b> - Overflow Flag (флаг переполнения). Выставляется если целочисленный результат - слишком большое положительное или слишком малое отрицательное число.</li>
<li><b>SF</b> - Sign Flag (флаг знака). Выставляется если результат, являющийся знаковым целым отрицателен. Иначе равен нулю.</li>
<li><b>ZF</b> - Zero Flag (флаг нуля). Устанавливается если результат равен нулю.</li>
<li><b>AF</b> - Auxiliary Carry Flag (вспомогательный флаг переноса). Выставляется если произошёл перенос из третьего бита.</li>
<li><b>PF</b> - Parity Flag (флаг чётности). Выставляется если самый младший байт результата содержит чётное количество битов, равных 1.</li>
<li><b>CF</b> - Carry Flag (флаг переноса). Выставляется в случае переполнения unsigned арифметики</li>
</ul>
Операции <code>jg</code> и <code>je</code> являются операциями условного перехода. Они передают управление на указанный адрес в случае выполнения соответствующего условия. В случае его невыполнения, исполнение продолжается со следующей команды. Бывают следующие операции условного перехода: <br />
<table>
<tbody>
<tr><th>Инструкция</th><th>Условие (Состояния флагов)</th><th>Описание</th></tr>
<tr><td colspan="3"><i>Беззнаковые условные переходы</i></td></tr>
<tr><td><code>JA/JNBE</code></td><td>(CF or ZF) = 0</td><td>Больше (>)</td></tr>
<tr><td><code>JAE/JNB</code></td><td>CF = 0</td><td>Больше или равно (>=)</td></tr>
<tr><td><code>JB/JNAE</code></td><td>CF = 1</td><td>Меньше (<)</td></tr>
<tr><td><code>JBE/JNA</code></td><td>(CF or ZF) = 1</td><td>Меньше или равно (<=)</td></tr>
<tr><td><code>JC</code></td><td>CF = 1</td><td>Взведён флаг переноса (Carry)</td></tr>
<tr><td><code>JE/JZ</code></td><td>ZF = 1</td><td>Равно/ноль (=)</td></tr>
<tr><td><code>JNC</code></td><td>CF = 0</td><td>Взведён флаг переноса (Carry)</td></tr>
<tr><td><code>JNE/JNZ</code></td><td>ZF = 0</td><td>Не равно/не ноль (!=)</td></tr>
<tr><td><code>JNP/JPO</code></td><td>PF = 0</td><td>Не взведён влаг чётности</td></tr>
<tr><td><code>JP/JPE</code></td><td>PF = 1</td><td>Взведён флаг чётности</td></tr>
<tr><td><code>JCXZ</code></td><td>CX = 0</td><td>Нулевой регистр CX</td></tr>
<tr><td><code>JECXZ</code></td><td>ECX = 0</td><td>Нулевой регистр ECX</td></tr>
<tr><td colspan="3"><i>Знаковые условные переходы</i></td></tr>
<tr><td><code>JG/JNLE</code></td><td>((SF xor OF) or ZF) = 0</td><td>Больше (>)</td></tr>
<tr><td><code>JGE/JN</code>L</td><td>(SF xor OF) = 0</td><td>Больше или равно (>=)</td></tr>
<tr><td><code>JL/JNGE</code></td><td>(SF xor OF) = 1</td><td>Меньше (<)</td></tr>
<tr><td><code>JLE/JNG</code></td><td>((SF xor OF) or ZF) = 1</td><td>Меньше или равно (<=)</td></tr>
<tr><td><code>JNO</code></td><td>OF = 0</td><td>Нет переполнения</td></tr>
<tr><td><code>JNS</code></td><td>SF = 0</td><td>Не отрицательное</td></tr>
<tr><td><code>JO</code></td><td>OF = 1</td><td>Переполнение</td></tr>
<tr><td><code>JS</code></td><td>SF = 1</td><td>Отрицательное</td></tr>
</tbody></table>
<br />
Последней не рассмотренной инструкцией осталась <code>jmp</code>. Это безусловный переход. Он передаёт управление программы по указанному адресу не сохраняя адрес возврата. Адрес перехода может быть как относительным, так и абсолютным. В нашем случае мы прыгали по адресу метки, расположенной прямо перед участком кода, завершающим программу.<br />
<br />
С версией для amd64 пожалуй всё, теперь посмотрим как условные переходы выглядят в других системах команд.<br />
<br />
<h3>
<a href="https://www.blogger.com/null" name="sparcv9"></a>3. Sparc v9</h3>
<div style="text-align: left;">
Переходим к спарку. Тестовые машины те же - TI UltraSparc III+ (Cheetah+) с ОС Gentoo и Эльбрус R1000 c ОС Эльбрус. Переходим к примеру:<br />
<br />
<code>.section .data<br /><br /> scanf_str:<br /> .ascii "%d%d\0"<br /> gt_str:<br /> .ascii "gt\n\0"<br /> eq_str:<br /> .ascii "eq\n\0"<br /> le_str:<br /> .ascii "le\n\0"<br /><br /> .global _start<br /><br />.section .bss<br /> .lcomm a, 32<br /> .lcomm b, 32<br /><br />.section .text<br /><br />_start:<br /><br /> ! Готовим аргументы для scanf<br /> set scanf_str, %o0 ! Кладём адрес строки на регистр<br /> set a, %o1 ! Кладём адрес a на регистр<br /> set b, %o2 ! Кладём адрес b на регистр<br /><br /> ! Вызываем scanf<br /> call scanf<br /> nop<br /><br /> set a, %g1 ! Кладём адрес a на регистр<br /> ld [%g1], %g1 ! Загружаем значение, лежащее по адресу в регистре %g1<br /><br /> set b, %g2 ! Кладём адрес b на регистр<br /> ld [%g2], %g2 ! Загружаем значение, лежащее по адресу в регистре %g2<br /><br /> cmp %g1, %g2 ! Сравниваем значения<br /><br /> bg .print_gt ! Если больше, то переходим на ветвь с gt<br /> nop<br /> be .print_eq ! Если равно, то переходим на ветвь с eq<br /> nop<br /><br /> ! В остальных случаях продолжаем исполнять ветвь с le<br /> set le_str, %o0<br /> call printf<br /> nop<br /><br /> ba .exit ! Безусловно идём на выход<br /> nop<br /><br />.print_gt:<br /> set gt_str, %o0<br /> call printf<br /> nop<br /><br /> ba .exit ! Безусловно идём на выход<br /> nop<br /><br />.print_eq:<br /> set eq_str, %o0<br /> call printf<br /> nop<br /><br />.exit:<br /> ! Готовим аргументы для exit<br /> mov 0, %o0<br /> mov 1, %g1<br /><br /> ! Вызываем exit<br /> ta 0x10</code><br />
<br />
Сборка и запуск:<br />
<br />
<code>
$ as -Av9 -64 sparc.s -o sparc.o && ld --dynamic-linker /lib64/ld-2.17.so -Av9 -m elf64_sparc sparc.o -lc<br />
$ ./a.out <br />3 4<br />le<br />$ ./a.out<br />3<br />3<br />eq<br />$ ./a.out<br />4 3<br />gt<br />
</code>
</div>
<br />
Тут всё на столько похоже на программу для интела, что не сразу понятно что нуждается в комментариях :) Подготовка аргументов для <b>scanf</b>, думаю, понятна, эти инструкции описывались в предыдущем посте. Перейдём сразу к инструкции вызова.<br />
<br />
Итак, мы можем видеть инструкцию <code>call scanf</code>, которая на самом деле является синтетической инструкцией, разворачивамой следующим образом:<br />
<br />
<code>call address -> jmpl address, %o7</code><br />
<br />
Но переходы - это тема для отдельного поста, поэтому пока будем считать что это просто вызов процедуры. Отдельно отмечу что после каждого перехода стоит операция <code>nop</code> (т.е. пустышка). Это связано с тем что большинство управляющих инструкций sparc'а работают через <b>delay slot</b> [<a href="http://alexanius-blog.blogspot.ru/2016/06/blog-post_30.html#delay1">delay1</a>,<a href="http://alexanius-blog.blogspot.ru/2016/06/blog-post_30.html#delay2">delay2</a>]. Если в кратце, то процессор при подаче инструкции перехода безусловно исполнит следующую за переходом инструкцию. Т.к. нам это свойство сейчас не нужно, то мы забиваем эти инструкции nop'ами.<br />
<br />
После того как <b>scanf</b> вернёт управление, нам нужно будет загрузить значения, введённые пользователем. Этим занимается инструкция <code>ld</code>, осуществляющая чтение значения из памяти в регистр. В спарке доступ в память осуществляется только через инструкции ld/st. Инструкции <code>ld</code> бывают следующих видов:<br />
<ul style="text-align: left;">
<li><code>ldsb [address], regrd</code> - Загрузить знаковый байт (Load Signed Byte)</li>
<li><code>ldsh [address], regrd</code> - Загрузить знаковое полуслово (Load Signed Halfword)</li>
<li><code>ldsw [address], regrd</code> - Загрузить знаковое слово (Load Signed Word)</li>
<li><code>ldub [address], regrd</code> - Загрузить беззнаковый байт (Load Unsigned Byte)</li>
<li><code>lduh [address], regrd</code> - Загрузить беззнаковое полуслово (Load Unsigned Halfword)</li>
<li><code>lduw [address], regrd</code> (синоним: ld) - Загрузить беззнаковое слово (Load Unsigned Word)</li>
<li><code>ldx [address], regrd</code> - Загрузить расширенное (Load Extended Word)</li>
<li><code>ldd [address], regrd</code> - Загрузить двойное (Load Doubleword)</li>
</ul>
Положив полученные значения на регистр нам нужно их сравнить. И тут мы видим инструкцию <code>cmp</code>, которая... тоже является синтетческой! Она раскрывается следующим образом:<br />
<br />
<code>cmp regrs1, reg_or_imm -> subcc regrs1, reg_or_imm, %g0</code><br />
<br />
Да, в спарке нет отдельной инструкции сравнения. Большинство арифметических инструкций имеют два режима работы - с выработкой результатов сравнения в качестве побочного эффекта и без неё. Результат сравнения складывается в <b>CCR</b> (Condition Codes Register) - 8-битный регистр условных кодов. <b>CCR</b> используется для целочисленных операций, причём делится на две части:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHi57NwHEncLBen1RL4xu3kRWwNb79inAV1I29pCD2J7-aKnjiWB-wY_6MpFXX0cnBtAkwCowpdkhHchrXMAHXgHgQaHopq36JuLzGoSqplHtGaogKumDuPHhXHhps92v_HDZbJ62__Nem/s1600/ccr.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="53" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHi57NwHEncLBen1RL4xu3kRWwNb79inAV1I29pCD2J7-aKnjiWB-wY_6MpFXX0cnBtAkwCowpdkhHchrXMAHXgHgQaHopq36JuLzGoSqplHtGaogKumDuPHhXHhps92v_HDZbJ62__Nem/s200/ccr.png" width="200" /></a></div>
Регистр <b>icc</b> используется для 32-х битных операций, а <b>xcc</b> - для 64-х битных. При этом арифметические операции модифицируют обе части <b>CCR</b>. Каждая часть регистра делится на 4 поля по одному биту:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpAmZg5C9kvOXl7K0mSPVE8S1QJIP3BYXoxVneZOOkPS5INw5T-L8tUdF9bnrmQtTu6Vm7WbtCfchtB5M1laDIHpqoE_bJNDCHupq0npADb802LEgBjVOWVHN5eknDaOKLsqf0uDkR_V0m/s1600/ccr2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="108" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpAmZg5C9kvOXl7K0mSPVE8S1QJIP3BYXoxVneZOOkPS5INw5T-L8tUdF9bnrmQtTu6Vm7WbtCfchtB5M1laDIHpqoE_bJNDCHupq0npADb802LEgBjVOWVHN5eknDaOKLsqf0uDkR_V0m/s200/ccr2.png" width="200" /></a></div>
Поля имеют следующие значения:<br />
<ul style="text-align: left;">
<li><b>N</b> - показывает что результат вычисления был отрицательным</li>
<li><b>Z</b> - показывает что результат был равен нулю</li>
<li><b>V</b> - показывает что во время последней арифметической операции было переполнение</li>
<li><b>C</b> - флаг переноса (carry flag)</li>
</ul>
Т.о. оттранслированная операция cmp задаёт нам соответствующие флаги <b>CCR</b>, на основе которых мы совершаем переход:<br />
<br />
<table>
<tbody>
<tr><th>Инструкция</th><th>Условие (icc test)</th><th>Описание</th></tr>
<tr><td><code>ba</code></td><td>1</td><td>Branch Always</td></tr>
<tr><td><code>bn</code></td><td>0</td><td>Branch Never</td></tr>
<tr><td><code>bne</code> (или <code>bnz</code>)</td><td>not Z</td><td>Branch on Not Equal</td></tr>
<tr><td><code>be</code> (или <code>bz</code>)</td><td>Z</td><td>Branch on Equal</td></tr>
<tr><td><code>bg</code></td><td>not (Z or (N xor V))</td><td>Branch on Greater</td></tr>
<tr><td><code>ble</code></td><td>Z or (N xor V)</td><td>Branch on Less or Equal</td></tr>
<tr><td><code>bge</code></td><td>not (N xor V)</td><td>Branch on Greater or Equal</td></tr>
<tr><td><code>bl</code></td><td>N xor V</td><td>Branch on Less</td></tr>
<tr><td><code>bgu</code></td><td>not (C or Z)</td><td>Branch on Greater Unsigned</td></tr>
<tr><td><code>bleu</code></td><td>C or Z</td><td>Branch on Less or Equal Unsigned</td></tr>
<tr><td><code>bcc</code></td><td>not C</td><td>Branch on Carry Clear (Greater than or Equal, Unsigned)</td></tr>
<tr><td><code>bcs</code></td><td>C</td><td>Branch on Carry Set (Less than, Unsigned)</td></tr>
<tr><td><code>bpos</code></td><td>not N</td><td>Branch on Positive</td></tr>
<tr><td><code>bneg</code></td><td>N</td><td>Branch on Negative</td></tr>
<tr><td><code>bvc</code></td><td>not V</td><td>Branch on Overflow Clear</td></tr>
<tr><td><code>bvs</code></td><td>V</td><td>Branch on Overflow Set</td></tr>
</tbody>
</table>
<br />
По инструкции ba, как можно догадаться, мы безусловно переходим на участок программы, вызывающий <b>exit</b>. Оставшая часть программы должна быть понятна.<br />
<br />
<h3>
<a href="https://www.blogger.com/null" name="elbrus"></a>4. Эльбрус</h3>
Версия для Эльбруса несколько сложнее, но гораздо интересней. Здесь будет затронуто больше концептов чем хотелось бы рассказывать для данного поста. Машина, на которой всё это проверялось та же - Эльбрус-4С с системой команд v3 под управлением ОС Эльбрус. Собственно, сам код:<br />
<br />
<code>.section .data<br /><br />$scanf_str:<br /> .ascii "%d%d\0"<br />$gt_str:<br /> .string "gt\n\0"<br />$eq_str:<br /> .string "eq\n\0"<br />$le_str:<br /> .string "le\n\0"<br /><br />.section .bss<br /> .lcomm a, 32<br /> .lcomm b, 32<br /><br />.section .text<br /> .global _start<br /><br />_start:<br /> {<br />! база размер текущий<br /> setbn rbs = 0x4, rsz = 0x3, rcur = 0x0<br />! размер окна<br /> setwd wsz = 0x8, nfx = 0x1<br /> disp %ctpr1, $scanf ! Подготовка перехода на scanf<br /> getsp, 0 _f32s,_lts1 0xffffffd0, %r1 ! Получаем адрес стека<br /> }<br /><br /> ! Здесь подготовливаем аргументы scanf<br /> ! ABI Эльбруса говорит что в случае процедур с элипсом следует<br /> ! резмещать аргументы на стеке, поэтому будут применены операции st<br /> {<br /> addd, 0 0x0, [ _f64,_lts0 $scanf_str], %b[0] ! Кладём адрес форматной строки на регистр<br /> addd, 1 0x0, [ _f64,_lts2 $a], %b[1] ! Кладём адрес первого глобала на регистр<br /> }<br /> {<br /> addd, 0 0x0, [ _f64,_lts2 $b], %b[2] ! Кладём адрес второго глобала на регистр<br /> std, 2 %b[0], 0x0, %r1 ! Кладём содержимое регистра с адресом строки на стек<br /> }<br /> {<br /> std, 2 %b[1], 0x8, %r1 ! Кладём содержимое регистра с адресом первого глобала на стек<br /> std, 5 %b[2], 0x10, %r1 ! Кладём содержимое регистра с адресом второго глобала на стек<br /> }<br /><br /> ! Непосредственно вызов<br /> call %ctpr1, wbs = 0x4 ! Вызываем подготовленную функцию scanf<br /><br /> {<br /> ldw, 2 0x0, [_f64,_lts0 $a], %b[1] ! Кладём значение глобала a на регистр<br /> ldw, 5 0x0, [_f64,_lts2 $b], %b[2] ! Кладём значение глобала b на регистр<br /> }<br /><br /> {<br /> cmplsb, 1 %b[1], %b[2], %pred0 ! Производим сравнение a < b<br /> cmplsb, 0 %b[2], %b[1], %pred1 ! Производим сравнение b < a<br /> disp %ctpr1, $printf ! Подготавливаем вызов printf<br /> }<br /><br /> ! В этой ШК вычисляем третий предикат (т.е. условие == )<br /> {<br /> pass %pred0, @p0 ! Записываем результат сравнения a < b в локальный предикат<br /> pass %pred1, @p1 ! Записываем результат сравнения b < a в локальный предикат<br /> andp ~@p0, ~@p1, @p4 ! Вычисляем !pred1 & !pred2<br /> pass @p4, %pred2 ! Записываем результат в глобальный предикат<br /> }<br /><br /> ! Готовим аргументы для printf<br /> {<br /> addd, 0 0x0, [ _f64,_lts0 $le_str ], %b[0] ? %pred0 ! Если a < b, то в качестве аргумента кладём адрес строки "le" в регистр<br /> addd, 2 0x0, [ _f64,_lts2 $gt_str ], %b[0] ? %pred1 ! Если a > b, то в качестве аргумента кладём адрес строки "gt" в регистр<br /> }<br /> addd, 0 0x0, [ _f64,_lts0 $eq_str ], %b[0] ? %pred2 ! Если a = b, то в качестве аргумента кладём адрес строки "eq" в регистр<br /> std, 2 %b[0], 0x0, %r1 ! Кладём содержимое регистра с адресом строки на стек<br /><br /> ! Вызываем printf<br /> call %ctpr1, wbs = 4<br /><br /> ! Готовим аргументы для exit<br /> {<br /> sdisp %ctpr2, 0x3<br /> addd, 0 0x0, 0x0, %b[1]<br /> addd, 1 0x0, 0x1, %b[0]<br /> }<br /><br /> ! Вызываем exit<br /> call %ctpr2, wbs = 4</code><br />
Сборка и запуск:<br />
<br />
<code>
$ las t.s -o t.o && ld t.o -o a.out -lc --dynamic-linker /lib/ld-2.21.so<br />
$ ./a.out <br />
3 4<br />le<br />$ ./a.out <br />3<br />3<br />eq<br />$ ./a.out <br />4 3<br />gt<br /><br />
</code>
<br />
Начало программы полностью идентично предыдущим вариантам и в пояснениях не нуждается, поэтому перейдём к первой ШК (широкой команде).<br />
<code> {<br /> setbn rbs = 0x4, rsz = 0x3, rcur = 0x0<br /> setwd wsz = 0x8, nfx = 0x1<br /> disp %ctpr1, $scanf ! Подготовка перехода на scanf<br /> getsp, 0 _f32s,_lts1 0xffffffd0, %r1 ! Получаем адрес стека<br /> }</code><br />
Первые две инструкции я подробно описывать не буду, т.к. для этого примера они значения не имеют, но расскажу что они делают. Инструкция <code>setbn</code> устанавливает базу циклических регистров, инструкция <code>setwd</code> изменяет размер окна стека процедур. Эти инструкции являются частью процедурного механизма, о котором я расскажу в других постах. Также в этой ШК присутствует инструкция <code>getsp</code>, которая возвращает свободную область в незащищённом стеке пользователя.<br />
<br />
Ну и отдельно рассмотрим инструкцию <code>disp</code>. Инструкция имеет следующий синтаксис:<code>disp ctp_reg, label</code>. Здесь <code>ctp_reg</code> - регистр перехода, а <code>label</code> - адрес перехода. Как мы помним из предыдущего поста, в Эльбрусах есть механизм подготовки переходов, начинающий подкачку кода из указанного адреса. Это позволяет избавиться от накладных расходов при непосредственно переходе. <code>disp</code> подготавливает переход на известный адрес, в нашем случае это адрес функции <b>scanf</b>.<br />
<br />
Следующие три ШК подготавливают аргументы для вызова <b>printf</b>. С инструкциями <code>add</code> мы уже знакомы, поэтому рассмотрим только инструкции <code>std</code>. Это инструкция записи в незащищённое пространство памяти. Синтаксис инструкции следующий: <code>st(b/h/w/d) src3, [ address ]</code>. В зависимости от суффикса мы можем записать следующее:<br />
<ul style="text-align: left;">
<li>stb - запись байта</li>
<li>sth - запись полуслова</li>
<li>stw - запись одинарного слова</li>
<li>std -запись двойного слова</li>
</ul>
Рассматривая инструкцию <code>std, 2 %b[0], 0x0, %r1</code> можно сказать что мы запишем содержимое регистра <b>%b[0]</b> по адресу, хранящемуся в регистре <b>%r1</b> со смещением 0x0 используя АЛК номер 2.<br />
<br />
А теперь зачем это было нужно (и почему не было в примерах для amd64 и sparc). В соответствии с ABI Эльбруса если мы вызываем функцию с эллипсисом (т.е. с переменным количеством аргуменов), то мы должны все аргументы размещать на стеке. Т.о. в рассматриваемых ШК мы положили адреса строки и двух глобалов на стек для вызова <b>scanf</b>.<br />
<br />
Далее у нас идёт уже знакомая иструкция <code>call</code>. Здесь особенностью является то что она не обрамлена фигурными скобками. Это означает что наша ШК состоит только из одной инструкции. Это неприятно, но в таком примере ШК особо ничем полезным не набьёшь :)<br />
<br />
После выполнения <code>call</code> у нас идёт ШК следующего содержания:<br />
<code>{</code><br />
<code> ldd, 3 0x0, [_f64,_lts0 $a], %b[1] ! Кладём значение глобала a на регистр</code><br />
<code> ldd, 5 0x0, [_f64,_lts2 $b], %b[2] ! Кладём значение глобала b на регистр</code><br />
<code>}</code><br />
В ней использованы инструкции <code>ldd</code>, которые, как можно догадаться, обратны <code>std</code>. Общий синтаксис инструкции таков: <code>ld(b/h/w/d) [ address ], dst</code>. Эта инструкция выполняет чтение из незащищённого пространства. В зависимости от суффикса возможны следующие варианты:<br />
<ul style="text-align: left;">
<li>ldb - считывание байта</li>
<li>ldh - считывание полуслова</li>
<li>ldw - считывание одинарного слова</li>
<li>ldd - считывание двойного слова</li>
</ul>
Т.о. инструкцию <code>ldw, 2 0x0, [_f64,_lts0 $a], %b[1]</code> следует читать: прочтём данные по адресу символа a со смещением 0x0 и положим их в регистр <b>%b[1]</b>.<br />
<br />
Теперь у нас на регистрах есть значения, введённые пользователем, и мы можем приступить к сравнению значений. В Эльбрусах операции сравнения несколько отличаются от интела. Здесь у нас нет отдельного регистра для eflags (хотя мы можем запустить арифметическую операцию с выработкой значения в формате IFL), но есть отдельная проверка под каждый случай:<br />
<br />
<table>
<tbody>
<tr><th>Инструкция</th><th>Описание</th></tr>
<tr><td colspan="2"><i><it>CMP(s/d)b группа из 8 операций сравнения</it></i></td></tr>
<tr><td><code>CMPO(s/d)</code></td><td>сравнение 32/64 "переполнение"</td></tr>
<tr><td><code>CMPB(s/d)b</code></td><td>сравнение 32/64 "< без знака"</td></tr>
<tr><td><code>CMPE(s/d)b</code></td><td>сравнение 32/64 "равно"</td></tr>
<tr><td><code>CMPBE(s/d)b</code></td><td>сравнение 32/64 "<= без знака"</td></tr>
<tr><td><code>CMPS(s/d)b</code></td><td>сравнение 32/64 "отрицательный"</td></tr>
<tr><td><code>CMPP(s/d)b</code></td><td>сравнение 32/64 "нечетный"</td></tr>
<tr><td><code>CMPL(s/d)b</code></td><td>сравнение 32/64 "< со знаком"</td></tr>
<tr><td><code>CMPLE(s/d)b</code></td><td>сравнение 32/64 "<= со знаком"</td></tr>
<tr><td colspan="2"><i><it>CMPAND(s/d)b группа из 4 операций проверки</it></i></td></tr>
<tr><td><code>CMPANDE(s/d)b</code></td><td>поразрядное "and" и проверка 32/64 "равно 0"</td></tr>
<tr><td><code>CMPANDS(s/d)b</code></td><td>поразрядное "and" и проверка 32/64 "отрицательный"</td></tr>
<tr><td><code>CMPANDP(s/d)b</code></td><td>поразрядное "and" и проверка 32/64 "нечетный"</td></tr>
<tr><td><code>CMPANDLE(s/d)b</code></td><td>поразрядное "and" и проверка 32/64 "<=0 со знаком"</td></tr>
</tbody>
</table>
<br />
Операции CMP вычитают операнд 2 из операнда 1, определяют флаги результата и проверяют указанное условие. Операции CMPAND выполняют поразрядное логическое "и", а далее по состоянию флагов проверяют заданное условие. Результатом данных операций будет сформированный предикат "true" или "false".<br />
<br />
И тут начинается ещё более интересный механизм, применяемый во VLIW процессорах - предикаты. Процессор Эльбрус имеет предикатный файл, содержащий в себе первичные и вторичные предикаты. Первичные предикаты - это битовые значения, вырабатываемые операциями сравнения, вторичные - результат логических операций над первиными предикатами. Всего у нас есть 32 первичных предиката и 7 вторичных. Первичные предикаты записываются как <b>%pred0</b> - <b>%pred31</b><br />
<br />
Рассмотрим ШК со сравнением:<br />
<code> {<br /> cmpldb, 1 %b[1], %b[2], %pred0 ! Производим сравнение a < b<br /> cmpldb, 0 %b[2], %b[1], %pred1 ! Производим сравнение b < a<br /> disp %ctpr1, $printf ! Подготавливаем вызов printf<br /> }</code> <br />
В ней мы получили результат для двух сравнений a < b и b > a. В первом случае мы записали предикат в регистр <b>%pred0</b>, во втором - в <b>%pred1</b>. В принципе самым простым (и быстрым) способом было бы в этой же ШК выполнить третье сравнение и получить третий предикат, но мне хотелось продемонстрировать вычисление одного предиката на основе других.<br />
<br />
Переходим к следующей ШК:<br />
<code> ! В этой ШК вычисляем третий предикат (т.е. условие == )<br /> {<br /> pass %pred0, @p0 ! Записываем результат сравнения a < b в локальный предикат<br /> pass %pred1, @p1 ! Записываем результат сравнения b < a в локальный предикат<br /> andp ~@p0, ~@p1, @p4 ! Вычисляем их !a & !b<br /> pass @p4, %pred2 ! Записываем результат в глобальный предикат<br /> }</code> <br />
Она довольно необычна. В ней мы выполняем запись значения в реистр, вычисление с ним и запись результата. И всё в одной ШК! Давайте по подробнее рассмотрим что тут происходит. Мы не можем выполнять вычисления с первичными предикатами. Для этих целей мы должны записать их в локальные предикаты командой <code>pass</code>. Всего у нас есть 7 локальных предикатов, обозначаемых <b>@p0</b> - <b>@p6</b>. При этом предикаты <b>@p0</b> - <b>@p3</b> могут быть использованы только для хранения первичных предикатов, а предикаты <b>@p4</b> - <b>@p6</b> для хранения результатов вычислений и записи в первичные предикаты.<br />
<br />
Для вычислений с предикатами нам доступна только инструкция <code>andp</code>, выполняющая операцию "и". Тильда перед предикатом означает отрицание. Т.о. рассматривая инструкцию <code>andp ~@p0, ~@p1, @p4</code> можно сказать, что если у нас не выполнилось условие a > b и b > a, то a == b, и мы записываем это в локальный предикат <b>@p4</b>. После этого пересылаем его в первичный предикат <b>%pred2</b>. <br />
<br />
И теперь, глядя на следующую ШК, можно понять как используются предикаты. Рассмотрим инструкцию <code>addd, 0 0x0, [ _f64,_lts0 $le_str ], %b[0] ? %pred0</code>. Помимо уже известного синтаксиса сюда добавился хвостик `<code>? %pred0</code>'. Он означает что данная инструкция будет исполнена только если предикат <b>%pred0</b> имеет значение true. Такой способ называется "условным исполнением", он же "предикатный режим". Под предикат можно поставить почти любую инструкцию. Это обеспечивает возможность исполнять код, содержащий большоее количество ветвей не используя инструкции переходов, при этом плотно забивая ШК.<br />
<br />
Остаток программы должен быть более или менее понятен, поэтому описание программ можно заканчивать :)<br />
<h3>
<a href="https://www.blogger.com/null" name="epilogue"></a>5. Заключение</h3>
<br />
Уже на таком простом примере можно видеть довольно сильные различия между системами команд различных процессоров. Так процессоры intel имеют инструкцию mov, способную работать как с регистрами, так и с памятью, в то время как в процессорах sparc и Эльбрус работа с памятью ведётся через отдельные команды. Случай с Эльбрусом вообще очень показателен, т.к. из него мы можем видеть что программу можно довольно красиво и органично избавить от ветвлений (что рекомендуется делать при любой возможности). Интересно заметить что в sparc'е половина применённых инструкций является "псевдо" и раскрывается в какие-то другие, что, правда, добавляет читабельность коду и делает его красивым. А вот ассемблер Эльбруса довольно сложно читать и писать, хотя для этого есть и объективные причины.<br />
<br />
<h3>
<a href="https://www.blogger.com/null" name="lit"></a>Источники</h3>
<br />
<a href="https://www.blogger.com/null" name="0xax"></a><a href="https://www.blogger.com/null" name="0xax"></a><a href="http://0xax.blogspot.ru/2014/09/say-hello-to-x64-assembly-part-2.html">[0xax]</a> Продолжение серии постов про основы ассемблера, которые меня вдохновили<br />
<a href="https://www.blogger.com/null" name="intel1"></a><a href="http://download.intel.com/design/processor/manuals/253665.pdf">[intel1]</a> Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 1: Basic Architecture<br />
<a href="https://www.blogger.com/null" name="amd64abi"></a><a href="http://www.x86-64.org/documentation/abi.pdf">[amd64abi]</a> System V Application Binary Interface<br />
<a href="https://www.blogger.com/null" name="sparcv9"></a><a href="https://www.blogger.com/null" name="sparcv9"></a><a href="https://cr.yp.to/2005-590/sparcv9.pdf">[sparcv9]</a>The SPARC Architecture Manual Version 9<br />
<a href="https://www.blogger.com/null" name="sparcasmbook"></a><a href="http://www.amazon.com/SPARC-Architecture-Assembly-Language-Programming/dp/0130255963">[sparcasmbook]</a> SPARC Architecture, Assembly Language Programming, and C. Очень хороший учебник по ассемблеру и по спарку <br />
<a href="https://www.blogger.com/null" name="delay1"></a><a href="https://en.wikipedia.org/wiki/Delay_slot">[delay1]</a> Описание delay slot на wiki<br />
<a href="https://www.blogger.com/null" name="delay2"></a><a href="http://www.pagetable.com/?p=313">[delay2]</a> Хорошее объяснение про delay slot и вообще годный бложик одного ассемблериста<br />
<a href="http://www.cs.princeton.edu/courses/archive/spr96/cs217/">[cs217]</a> Introduction to Programming Systems - учебный курс, включающий в себя описание sparc-машин.<br />
<a href="https://www.blogger.com/null" name="elbrusbook"></a><a href="https://www.blogger.com/www.mcst.ru/doc/book_121130.pdf">[elbrus]</a> Микропроцессоры и вычислительные комплексы семейства «Эльбрус»<br />
<a href="https://www.blogger.com/null" name="wasm"></a><a href="http://wasm.ru/">[wasm]</a> Вновь оживший ресурс wasm.ru. Я им не пользовался, но на нём довольно много материала по разным ассемблерам и живой форум.
</div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com1tag:blogger.com,1999:blog-935622623924408531.post-35951093820191904522016-06-04T12:43:00.000+02:002016-06-04T12:43:38.848+02:00Список учебных курсов по оптимизирующим компиляторам<div dir="ltr" style="text-align: left;" trbidi="on">
<br />
Как вы помните, я читаю курс по оптимизирующим компиляторам. В процессе подготовки к лекциям приходится искать много материала по теме, часто бывает что какой-то информации нету в книгах, и гугл выбрасывает меня на лекции с других ВУЗов или публикации. В этом посте поделюсь ссылками на другие курсы лекций, относящиеся к компиляторам, из которых я брал информацию.<br />
<a name='more'></a><ul style="text-align: left;">
<li><a href="http://courses.cs.washington.edu/courses/cse501/">CSE501</a> - Implementation of Programming Languages, University of Washington.<br />Курс довольно разнообразный и не совсем по оптимизациям. Меня интересовал раздел по анализу потока данных и немного про анализ указателей. Но помимо этого там есть интересный материал по верификации и по высокопроизводительным вычислениям. В разделе prerequirements есть ссылки на более базовые курсы.</li>
<li><a href="http://www.cis.upenn.edu/~cis570/">CIS570</a> - Modern Programming Language Implementation, Penn Engineering.<br />Общий курс по компиляторам, затрагиваются вопросы анализов потока данных, анализа указателей, распределения регистров.</li>
<li><a href="http://www.seas.harvard.edu/courses/cs252/2015fa/">CS252</a> - Advanced Topics in Programming Languages, Harvard School of Engeneering and Applied Sciences.<br />Очень интересный курс по анализу программ. Затрагивает как статические, так и динамические языки.</li>
<li><a href="http://users.ices.utexas.edu/~lenharth/cs378/fall14/">CS378</a> - Programming For Performance, The University of Texas.<br />Весьма интересный курс по высокопроизводительным системам с большим упором на аппаратуру. Затронуты особенности работы процессора, памяти, многопоточности. Отдельно можно отметить хорошую лекцию по цикловым оптимизациям.</li>
<li><a href="http://web.stanford.edu/class/cs143/">CS143</a> - Compilers, Stanford University.<br />Почти классический курс по компиляторам - большое внимания лексерам/парсерам, довольно мало внимания оптимизациям. Но даже там встречается полезный материал. (Не, на самом деле хороший курс, сделан и продуман лучше некоторых предыдущих).</li>
<li><a href="https://www.clear.rice.edu/comp512/">COMP512</a> - Advanced Compiler Construction, Rice University.<br />Его читает сам K. D. Cooper, автор книги "Engineering a compiler"! Теперь о курсе. В общем очень клёвый курс, посвящённый оптимизирующим компиляторам. Подразумевает что студенты уже имеют определённое представление о компиляторах.</li>
<li><a href="https://www.cs.rice.edu/~vs3/comp515/COMP515_F11_Home.html">COMP515</a> - Advanced Compilation for Vector and Parallel Processors<span class="style_1" style="line-height: 23px;">, </span><br />Rice University.<br />Курс посвящён оптимизациям для архитектур с явным параллелизмом. Т.о. основное внимание уделяется анализу зависимостей, векторизации, конвейеризации, работе с кэшем.</li>
<li><a href="https://www.cs.princeton.edu/courses/archive/spring16/cos320/">COS320</a> - Compiling Techniques, Princeton University.<br /> Ещё один классический курс по компиляторам. Изюминку ему придаёт наличие материала по компиляции ML, и вообще повышенное внимание к структуре промежуточного представления программы. Радуют отдельные лекции по AST и по системе типов.</li>
<li><a href="https://www.cs.purdue.edu/homes/xyzhang/spring11/">CS352</a> - Compilers: Principles and Practice, Purdue Universit.<br />Довольно базовый курс, ближе к классическому. Про оптимизации почти ничего нет, но есть про структуру программы и про проверки типов (что не так часто встречается).</li>
<li><a href="http://suif.stanford.edu/~courses/cs243/">CS243</a> - Program Analysis and Optimization, <span class="fn org">Stanford University.<br />Это почти канонъ хотя бы потому что одним из лекторов является сама М. Лам (одна из авторов Dragon Book'а)! Курс, как можно догадаться, посвящён анализам и оптимизациям. Рассказывается про анализ и оптимизацию потока данных, конвейеризацию, прочие цикловые оптимизации, анализ указателей, немного про динамическую компиляцию.</span></li>
<li><span class="fn org"><a href="http://www.cs.cmu.edu/afs/cs/academic/class/15745-s12/www/">CS745</a> - </span>Optimizing Compilers, Carnegie Mellon University.<br />Базовый курс по компиляторам, минимум треть которого посвящена анализу потока данных и локальным оптимизациям. Но прочие темы, необходимые для данного курса присутствуют. Несколько лекций посвящено LLVM, что делает курс интересным.</li>
<li><span class="fn org"><a href="https://www.cs.cmu.edu/~15745/index.html">CS745</a> - </span>Optimizing Compilers, Carnegie Mellon University.<br />Нет, я не опечатался. Это тот же курс из того же университета. Но он читается другой группой преподавателей, и поэтому немного отличается. Набор тем в целом такой же, но слайды иногда различаются. В любом случае стоит иметь его ввиду.</li>
<li><a href="http://www.cs.cornell.edu/courses/cs412/2008sp/">CS412/413</a> - Introduction to Compilers, Cornell University.<br />Тоже весьма годный классический курс по компиляторам, но для оптимизаций тоже есть несколько хороших лекций. Радует большое внимание семантическому анализу и представлению программы в компиляторе.</li>
<li><a href="http://www.eng.utah.edu/~cs5470/">CS5470</a> - Compiler Principles and Techniques, The University of Utah.<br />Данный курс тоже является довольно классическим. Его особенность в том что теоретический курс идёт в тесной привязке к практической работе - разработке компилятора MiniJava, поэтому он является весьма целостным и хорошо структурированным. На самом деле как-то так и должен выглядеть хороший курс по программированию.</li>
<li><a href="http://www.cs.colostate.edu/~cs553/">CS553</a>: Programming Language Design and Implementation (Algorithmic Language Compilers), Colorado State University.<br />Курс по оптимизирующим компиляторам. Тоже уделено внимание LLVM, есть видео с лекций. К сожалению внешним пользователям не все материалы доступны.</li>
</ul>
Можно заметить что в списке нет ни одного русскоязычного ресурса. В первую очередь это связано с тем что при поиске англоязычных терминов в выдаче сначала присутствуют англоязычные ресурсы.<br />
<br />
Но есть и другая проблема - в России почему-то не принято выкладывать материалы курса в общий доступ. Если вы посмотрите на приведённые мной ссылки, то тут у каждого курса есть своя страничка с описанием и материалами. У нас такое встречается крайне редко. Часто по причине банального отсутствия материалов (ну нету у преподов слайдов, всё на бумажках и из головы читается).<br />
<br />
У меня есть мечта выложить свои материалы, но пока я этого не делаю просто потому что курс пока не готов. Только после второго года чтения я начинаю понимать как правильно строить структуру этого курса. А ведь ещё нужно придумать практические задания. В общем пока ещё надо много над чем поработать.<br />
<br />
PS. Если кто подкинет ссылочек на хорошие русскоязычные курсы - буду очень благодарен ;)<br />
<ul style="text-align: left;"></ul>
</div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com0tag:blogger.com,1999:blog-935622623924408531.post-33418432830289059832016-05-26T23:39:00.000+02:002016-06-04T12:44:14.808+02:00Профиль программы и его предсказание<div dir="ltr" style="text-align: left;" trbidi="on">
Сегодня хотел бы рассказать про то как в компиляторе представлена профильная информация и как она предсказывается. Студентам и просто людям часто выносит мозг тот факт что компилятор статически (т.е. без реального исполнения) может предсказывать такие вещи как количество итераций у цикла или просто вероятности переходов, поэтому расскажу об этом по подробнее.<br />
<a name='more'></a>Для начала опишу проблему. В некоторых процессорах с прямым порядком исполнения команд (in-order) нужно уметь хорошо планировать код. Более того ситуация становится совсем плохой если в процессоре отсутствует предсказатель переходов. Т.о. компилятору становится необходимо брать все эти функции на себя. Необходимо понять по какой ветке и с какой вероятностью пойдёт исполнение, какой цикл является горячим и стоит ли его раскручивать/конвейеризовывать, какая функция является горячей и стоит ли её инлайнить. (Кстати, из интеловских лекций следует что они также используют предсказатель и для x86 процессоров).<br />
<br />
Чтобы уметь делать всё выше перечисленное, мы приходим к понятию профиля. Внутри компилятора он представляет из себя немного не то к чему привыкли пользователи gprof/perf/etc. Надеюсь что читатель уже знает что такое <a href="https://ru.wikipedia.org/wiki/%D0%93%D1%80%D0%B0%D1%84_%D0%BF%D0%BE%D1%82%D0%BE%D0%BA%D0%B0_%D1%83%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F">cfg</a>, поэтому перейдём к описанию. Профилем программы является информация о количестве проходов по узлам cfg и вероятность перехода по каждой дуге. Чтобы было понятно, можно посмотреть на рисунок:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi01daewHEWqlTy5ogzXaPdj1j4DXveCSn-OKLSg-QVy07yclOC5n9Os66cSe-GrMSqyMAZKc8whSuaqg9x08WjtBfIY2Ah45jcsaaiWIBokq26aYHe3uV5Gd0h7uU5f2VdqrRll42N1wid/s1600/prof.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="132" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi01daewHEWqlTy5ogzXaPdj1j4DXveCSn-OKLSg-QVy07yclOC5n9Os66cSe-GrMSqyMAZKc8whSuaqg9x08WjtBfIY2Ah45jcsaaiWIBokq26aYHe3uV5Gd0h7uU5f2VdqrRll42N1wid/s320/prof.png" width="320" /></a></div>
<br />
На нём видно, что каждая дуга (исходящая, но к входящим это тоже относится) имеет 2 цифры. Первая - это счётчик. Он говорит сколько раз мы прошли по данной дуге. Вторая - это вероятность. Она говорит с какой вероятностью мы переходим на данную дугу из исходного узла. Эти две цифры вазимозаменяемы и должны постоянно поддерживаться в согласованном состоянии. Для узла вероятность смысла не имеет (на самом деле имеет, но не в данном контексте), поэтому у него есть только счётчик.<br />
<br />
Откуда берётся данная информация? Есть два способа её получить. Первый, и довольно очевидный - исполнить программу и посмотреть. Но такой метод имеет много минусов, и к сожалению используется редко (а ведь он может значительно ускорить программа на Эльбрусах, да и не только).<br />
<br />
Второй метод - предсказать. Предсказание проходит по следующему принципу: мы полагаем что стартовый узел имеет счётчик 1. Далее для всех исходящих дуг мы вычисляем счётчики по формуле:<br />
$$<br />
C(E_{\text{out}}) = C(N) C(P_{\text{out}})<br />
$$ <br />
Далее для каждого узла мы вычисляем счётчик по формуле:<br />
$$ <br />
C(N) = \sum\limits_{i = 1}^K C(E^{in}_i)<br />
$$ <br />
Таким образом проходим по всей процедуре и предсказываем профиль. Возникает логичный вопрос: откуда взять вероятности? Их <strike>берём с полка</strike> выставляем исходя из данных, известных во время компиляции или некоторых эвристик. Например если мы можем статически прикинуть результат условного оператора, то вероятность вычислить легко. Если нет, то компилятор имеет определённую статистику по вероятностям переходов при определённой структуре cfg.<br />
<br />
Теперь вопрос: что делать если у нас встретилась обратная дуга? Т.е. имеем cfg следующего вида:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVJhhT3YgFIbWEed4hMYmqXLorsbDmzFIrVyR180h9tviLcWZWElrGBWMKVRHXMimoammMAIGu1MnC_ZmWnH87xNKxC1PtkPPhWW54luiGIf_WUEK_vC0rkZySyAlZKOWM5-ULCbqMPKlT/s1600/prof2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVJhhT3YgFIbWEed4hMYmqXLorsbDmzFIrVyR180h9tviLcWZWElrGBWMKVRHXMimoammMAIGu1MnC_ZmWnH87xNKxC1PtkPPhWW54luiGIf_WUEK_vC0rkZySyAlZKOWM5-ULCbqMPKlT/s200/prof2.png" width="194" /></a></div>
<br />
Если будем действовать по описанному выше алгоритму, то зациклимся и будем постоянно наращивать счётчик. Решается это поиском вероятности выйти из цикла (т.е. без учёта обратной дуги) и проставления счётчиков в соответствии с этой вероятностью. Формула для этого на удивление простая, а реализация её вычисления на удивление сложное:<br />
$$ <br />
I = \frac1{P_{loop\_out}(E^{out}_i)}<br />
$$ <br />
Самая большая хитрость в том чтобы её посчитать. Не буду описывать здесь как это делается (это долго и скучно), скажу только что сложность алгоритма немного возрастает если идёт гнездо циклов, и сильно возрастает если цикл несводимый.<br />
<br />
Касательно точности такого предсказания могу сказать что никогда не делал исследования этого вопроса, но попытки отключения профиля или неаккуратная его корректировка могут просаживать производительность в разы.</div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com2tag:blogger.com,1999:blog-935622623924408531.post-51509954190320522082016-05-12T19:50:00.000+02:002016-06-26T12:45:07.877+02:00Пишем "Hello, world" на ассемблере<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
Так сложилось, что я совсем не знаю ассемблера. Даже несмотря на то, что я разрабатываю компиляторы, на уровень близкий к аппаратуре я почти не спускаюсь. Была пара попыток его выучить, но я просто не находил подходящего материала. В итоге решил что если его нет, то нужно написать самому. В этой заметке я планирую показать как написать простой Hello world на ассемблере.<br />
<br />
В данной статье я преследую несколько целей:<br />
<ul style="text-align: left;">
<li>Изучить основы работы с ассемблером</li>
<li>Сравнить ассемблеры процессоров различных архитектур и, как следствие, показать разные аппаратные особенности</li>
<li>Написать материал по которому новички далее смогут самостоятельно продолжить изучение ассемблера</li>
</ul>
Содержание:<br />
<ol style="text-align: left;">
<li><a href="http://alexanius-blog.blogspot.ru/2016/05/hello-world.html#intro">Введение</a></li>
<li><a href="http://alexanius-blog.blogspot.ru/2016/05/hello-world.html#amd64">amd64</a></li>
<li><a href="http://alexanius-blog.blogspot.ru/2016/05/hello-world.html#sparcv9">sparc v9</a></li>
<li><a href="http://alexanius-blog.blogspot.ru/2016/05/hello-world.html#elbrus">Эльбрус</a></li>
<li><a href="http://alexanius-blog.blogspot.ru/2016/05/hello-world.html#epilogue">Послесловие</a></li>
<li><a href="http://alexanius-blog.blogspot.ru/2016/05/hello-world.html#lit">Источники</a></li>
</ol>
<a name='more'></a><h3>
<a href="https://www.blogger.com/null" name="intro"></a>1. Введение</h3>
<br />
Я буду стараться давать минимум теории, т.к. её рассказывают много где, гораздо более подробно и понятно. Поэтому буду описывать только то, что касается данного примера.<br />
Итак, задача: написать программу, выводящую на экран сообщение "Hello, world". В качестве эталона возьмём программу на C:<br />
<br />
<code>
#include <unistd.h><br /><br />int main()<br />{<br /> const char * msg = "Hello, world\n";<br /> write(0, msg, 13);<br /> return 0;<br />}
</code>
<br />
<br />
Сборка и запуск:<br />
<br />
<code>
$ gcc t.c && ./a.out<br />Hello, world<br /><br />
</code>
<br />
Здесь специально не использована стандартная библиотека, а применён системный вызов <b>write</b>. Подробнее про него можно прочесть по команде <i>man 2 write</i>.<br />
<br />
<h3>
<a href="https://www.blogger.com/null" name="amd64"></a>2. amd64</h3>
<br />
В качестве процессора на данной архитектуре применяется Intel Core i5, операционная система - Gentoo GNU/Linux, синтаксис AT&T. По моей любимой привычке сначала напишем программу, а потом будем думать.<br />
<br />
<code>
.section .data<br /> hello_str: <br /> .string "Hello, world\n"<br /> .set hello_str_len, . - hello_str - 1<br />
<br />.section .text<br /> .globl _start<br /><br />_start:<br />
# Здесь подготавливаем и вызываем write<br /> mov $1, %rax<br /> mov $1, %rdi<br /> mov $hello_str, %rsi<br /> mov $hello_str_len, %rdx<br /> syscall<br />
<br /> # Здесь подготавливаем и вызываем exit<br />
mov $60, %rax<br /> mov $0, %rdi<br /> syscall<br />
</code>
<br />
<br />
Сборка и запуск:<br />
<br />
<code>
$ as tt.s -o tt.o && ld tt.o && ./a.out<br />Hello, world<br />
</code>
<br />
<br />
Теперь попытаемся понять что произошло.<br />
<br />
Краткое описание синтаксиса:<br />
На каждой строчке находятся команды (statement). Команда начинается с нуля и более меток, после которых находится ключевой символ, обозначающий тип команды. Всё что начинается с точки `.' является директивой ассемблера. Всё что начинается с буквы является инструкцией ассемблера и транслируется в машинный код. Комментарии бывают многострочными `/**/' и однострочными `#'.<br />
<br />
Директивы <code>.section</code> обозначают начало секций. Секция - это диапазон адресов без пробелов, содержащий в себе данные, предназначенные для одной цели <a href="http://alexanius-blog.blogspot.ru/2016/05/hello-world.html#as">[as]</a>. Объектный файл, сгененрированный <i>as</i> имеет как минимум три секции: <code>.text</code>, <code>.data</code>, <code>.bss</code>. Внутри объектного файла по адресу 0 располагается секция <code>.text</code>, за ней идёт секция <code>.data</code>, а за ней секция <code>.bss</code>. Все адреса <i>as</i> вычисляет как (адрес начала секции) + (смещение внутри секции). Итак, что же означают секции:<br />
<ul style="text-align: left;">
<li><b>.data</b> - в этой секции обычно хранятся константы</li>
<li><b>.text</b> - в этой секции обычно хранятся инструкции программы</li>
<li><b>.bss</b> - содержит обнулённые байты и применяется для хранения неинициализированной информации</li>
</ul>
В начале секции <code>.data</code> у нас стоит метка <code>hello_str</code>, которая указывает на начало строки.<br />
<br />
Далее идёт директива <code>.string</code>. Это псевдо операция, копирующая байты в объектник.<br />
<br />
Директива <code>.set</code> присваивает символу значение выражения. Т.о. мы говорим что символ <code>hello_str_len</code> равен выражению <code>. - hello_str - 1</code>. Символ `<code>.</code>' означает текущий адрес. Вычитая из него адрес метки <code>hello_str</code> получаем длину строки с завершающим нулём. Чтобы он не попал на печать вычитаем 1.<br />
<br />
Директива <code>.globl</code> говорит что данный символ должен быть виден <i>ld</i>. Т.е. теперь символ <code>_start</code> сможет быть слинкован. Это нужно, т.к. вход в программу осуществляется именно через этот символ.<br />
<br />
После метки <code>_start</code> начинаются непосредственно ассемблерные инструкции. И теперь опять вернёмся к теории.<br />
<br />
Данная программа написана под процессор Intel архитектуры amd64 (она же x86_64). Это 64-х битное расширение архитектуры IA-32. Описание самой архитектуры процессора находится в <a href="http://alexanius-blog.blogspot.ru/2016/05/hello-world.html#intel1">[intel1]</a>. Подробное описание команд процессора находится в <a href="http://alexanius-blog.blogspot.ru/2016/05/hello-world.html#intel1">[intel2]</a>.<br />
<br />
Итак, в данной программе мы оперируем регистрами - внутренней памятью процессора. Архитектура amd64 содержит очень мало регистров - всего 16 64-х разрядных регистров общего назначения: <b>RAX</b>, <b>RBX</b>, <b>RCX</b>, <b>RDX</b>, <b>RBP</b>, <b>RSI</b>, <b>RDI</b>, <b>RSP</b>, <b>R8D</b>-<b>R15D</b>.<br />
<br />
Операция <code>mov</code> предназначена для копирования первого операнда во второй (заметьте, что это особенность синтаксиса AT&T, и интеловский синтаксис имеет обратный порядок операндов). Мы можем скопировать константу, значение общего или сегментного регистра или значение из памяти. Копировать можно в общий или сегментный регистр или память. Для обозначения констант используется символ <code>$</code>, а для регистров - <code>%</code> Чуть позже станет понятно что куда и зачем мы копировали.<br />
<br />
Далее идёт операция <code>syscall</code>. Она делает системный вызов. Системный вызов - это функция из ядра ОС. Каждый системный вызов производится по номеру. Он должен находиться в регистре <code>rax</code>. Номера системных вызовов можно посмотреть в таблицах <a href="http://alexanius-blog.blogspot.ru/2016/05/hello-world.html#syscall1">[syscall1]</a><a href="http://alexanius-blog.blogspot.ru/2016/05/hello-world.html#syscall1">[syscall2]</a>. Но можно выяснить самому. Их конкретное местоположение зависит от дистрибутива. В моём случае они, например, находятся в файле /usr/include/asm/unistd_64.h. Вот выдержка из этого файла:<br />
<br />
<code>
... <br />
#define __NR_read 0<br />#define __NR_write 1<br />#define __NR_open 2<br />...<br />
#define __NR_execve 59<br />#define __NR_exit 60<br />#define __NR_wait4 61<br />...<br />
</code><br />
Понятно, что помимо номеров нам нужны ещё аргументы этих вызовов. Их можно найти следующим образом:<br />
<br />
<code>
$ cd /usr/src/linux/<br />
$ grep -rA3 'SYSCALL_DEFINE.\?(write,' *<br />fs/read_write.c:SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,<br />fs/read_write.c- size_t, count)<br />fs/read_write.c-{<br />fs/read_write.c- struct fd f = fdget_pos(fd);<br />
</code>
<br />
<br />
Но в целом таблицами, подготовленными хорошими людьми пользоваться удобнее.<br />
<br />
Итак, видно, что вызов <b>write</b> требует 3 аргумента. Первый - это дескриптор файла вывода. Он кладётся на регистр <b>rdi</b>. Мы на <b>rdi</b> кладём 1, что является дескриптором <b>stdout</b>. На регистр <b>rsi</b> кладётся указатель на адрес строки. И на регистр <b>rdx</b> кладётся длина строки. Всё, теперь, когда все регистры подготовлены, можно делать <b>syscall</b> и нам будет выведено сообщение.<br />
<br />
Далее нужно выйти из программы. Для этого используется системный вызов <b>exit</b>. Он имеет номер 60 и требует код возврата в качестве первого аргумента. Мы завершаемся с кодом 0, как и положено успешно выполненной программе.<br />
<br />
<h3>
<a href="https://www.blogger.com/null" name="sparcv9"></a>3. Sparc v9</h3>
<br />
Не устали? Теперь внезапно рассмотрим sparc. Меня эта платформа интересует, т.к. одна из линеек процессоров Эльбрус основана на этой архитектуре. Я тестировался на процессорах TI UltraSparc III+ (Cheetah+) с ОС Gentoo и процессорах Эльбрус R1000 c ОС Эльбрус. Итак, смотрим:<br />
<br />
<code>
.section .data<br />
hello_str:<br />
.ascii "Hello, world\n"<br />
.set hello_str_len, . - hello_str<br />
<br />
.global _start<br />
<br />
.section .text<br />
<br />
_start:<br />
! Подготавливаем и вызываем write <br />
mov 1, %o0<br />
set hello_str, %o1<br />
mov hello_str_len, %o2<br />
mov 4, %g1<br />
ta 0x10<br />
<br />
! Подготавливаем и вызываем exit<br />
mov 0, %o0<br />
mov 1, %g1<br />
ta 0x10<br />
</code>
<br />
Сборка и запуск:<br />
<code>
<br />
$ as -Av9 -64 t1.s -o t1.o && ld -Av9 -m elf64_sparc t1.o && ./a.out<br />
Hello, world<br />
</code>
<br />
<br />
Вроде как отличий немного. Синтаксис <i>as</i> был описан в блоке amd64, разве что здесь однострочные комментарии задаются символом <code>!</code>, поэтому его опускаем и переходим сразу к отличиям. Сразу скажу, что речь идёт о Sparc v9 если не оговорено другое. v9 является 64-х битным расширением архитектуры sparc v8. Начнём с регистров. Их здесь больше чем в amd64 - целых 32 общего назначения, доступных пользователю. Сами регистры называются <b>%r0</b> - <b>%r31</b>, но у них есть логическое разделение:<br />
<br />
<table border="1">
<caption>Регистры общего назначения</caption>
<tbody>
<tr><th>Название</th><th>Имя внутри окна</th><th>Имя r-регистра</th></tr>
<tr><td>Глобальные (global)</td><td>%g0 - %g7</td><td>%r0 - %r7</td></tr>
<tr><td>Выходные (out)</td><td>%o0 - %o7</td><td>%r8 - %r15</td></tr>
<tr><td>Локальные (local)</td><td>%l0 - %l7</td><td>%r16 - %r23</td></tr>
<tr><td>Входные (in)</td><td>%i0 - %i7</td><td>%r24 - %r31</td></tr>
</tbody></table>
<br />
Данные регистры называются <i>r регистрами</i> и используются для целочисленных вычислений. Плавающие регистры называются <i>f регистрами</i>, они расположены отдельно, и о них мы сегодня говорить не будем. Интересно отметить, что сама архитектура предполагает от 64 до 528 <i>r регистров</i>, но регистровое окно содержит только 24. Чтение <b>%g0</b> всегда возвращает 0, а запись в него не даёт эффекта. Вообще на спарке регистры сделаны очень круто, но их очень долго описывать, советую прочитать документацию <a href="http://alexanius-blog.blogspot.ru/2016/05/hello-world.html#sparcv9">[sparcv9]</a>.<br />
<br />
Переходим к инструкциям. Начнём с инструкции <code>mov</code>. От интела эта инструкция отличается тем, что её нет в Спарке. Sparc - это RISC архитектура с малым количеством команд, но для удобства программистов ассемблер поддерживает синтетические инструкции. В частности приведённый <code>mov</code> возможно будет оттранслирован следующим образом (есть несколько способов трансляции в зависимости от аргументов):<br />
<code>mov 1, %o1</code> -> <code>or %g0, 1, %o1</code><br />
Синтетические инструкции не являются частью стандарта, но входят в информационное приложение к нему, так что их можно смело использовать.<br />
Следующая инструкция <code>set</code>, являющаяся синонимом к инструкции <code>setuw</code>, которая тоже является синтетической инструкцией. Её раскрытие возможно выглядит следующим образом:<br />
<table>
<tbody>
<tr><td><code>set hello_str %o2</code></td> <td>-></td> <td><table><tbody>
<tr><td><code>sethi %hi(hello_str), %o2</code></td></tr>
<tr><td><code>or %o2, %lo(hello_str), %o2</code></td></tr>
</tbody></table>
</td></tr>
</tbody></table>
</div>
</div>
Инструкция <code>sethi</code> поместит старшие 22 бита <code>hello_str</code> (т.е. её адрес) на регистр <b>%o2</b>. Инструкция <code>or</code> поместит туда младший остаток. Обозначения <b>%hi</b> и <b>%lo</b> нужны для взятия старших и младших битов соответственно. Такие сложности возникают из-за того что инструкция кодируется 32 битами, и просто не может включать в себя 32-х битную константу.<br />
<br />
Далее мы кладём значение 4 на глобальный регистр <b>%g1</b>. Можно догадаться что это номер вызова <b>write</b>. Системный возов будет искать номер вызова именно там.<br />
<br />
Операция <code>ta</code> инициирует системное прерывание. Её аргументом является тип системного прерывания. Скажу честно - я не нашёл нормального описания системных вызовов для v9, а то что туда надо подавать 0x10 выяснил случайно из архивов какой-то переписки. Поэтому придётся просто это запомнить :)<br />
<br />
Далее производятся аналогичные действия для вызова <b>exit</b>, думаю их пояснять не нужно.<br />
<br />
<b>UPD</b>:<br />
<br />
Спасибо уважаемому Анониму за версию данной программы для SunOS 5.10:<br />
<br />
<code>
.section ".text"<br /> .global _start<br />_start:<br /> mov 4,%g1 ! 4 is SYS_write<br /> mov 1,%o0 ! 1 is stdout<br /> set .msg,%o1 ! pointer to buffer<br /> mov (.msgend-.msg),%o2 ! length<br /> ta 8<br /><br /> mov 1,%g1 ! 1 is SYS_exit<br /> clr %o0 ! return status is 0<br /> ta 8<br /><br />.msg:<br /> .ascii "Hello world!\n"<br />.msgend:</code><br />
<br />
Запуск:<br />
<code>$ as t1.s -o t1.o && ld t1.o && ./a.out<br />Hello world!</code></div>
</div>
<br />
<h3>
<a href="https://www.blogger.com/null" name="elbrus"></a>4. Эльбрус</h3>
<br />
Ну и, собственно, жемчужина коллекции - процессор Эльбрус. Работа проводилась на процессоре Эльбрус-4С, который имеет архитектуру команд v3 (наше внутреннее обозначение). Управляется машина ОС Эльбрус. Про сам Эльбрус можно почитать в <a href="http://alexanius-blog.blogspot.ru/2016/05/hello-world.html#elbrusbook">[elbrus]</a>, про какую-либо документацию, находящуюся в открытом доступе мне неизвестно.<br />
<br />
Как и Sparc, архитектура Эльбруса рассчитана в первую очередь на то что оптимальный код выдаст компилятор. Но в отличает от Sparc, ассемблер Эльбруса вообще не предназначен для людей. Итак, вот наш пример:<br />
<br />
<code>
.section ".data"<br />
<br />
$hello_msg:<br />
.ascii "Hello, world\n\000"<br />
<br />
.section ".text"<br />
.global _start<br />
<br />
_start:<br />
! Подготавливаем вызов write <br />
{<br />
sdisp %ctpr1, 0x3<br />
addd, 0 0x0, 13, %b[3]<br />
addd, 2 0x0, [ _f64, _lts1 $hello_msg ], %b[2]<br />
addd, 1 0x0, 0x1, %b[1]<br />
addd, 3 0x0, 0x4, %b[0]<br />
}<br />
<br />
! Вызываем write<br />
{<br />
call %ctpr1, wbs = 0x4<br />
}<br />
<br />
! Подготавливаем вызов exit<br />
{<br />
sdisp %ctpr2, 0x1<br />
addd, 0 0x0, 0x0, %b[1]<br />
addd, 1 0x0, 0x1, %b[0]<br />
}<br />
<br />
! Вызываем exit<br />
{<br />
call %ctpr2, wbs = 0x4<br />
}<br />
</code>
<br />
Сборка и запуск:<br />
<br />
<code>
$ las t.s -o t.o && ld t.o && ./a.out<br />
Hello, world<br />
</code>
<br />
Начнём с изменения синтаксиса.<br />
<br />
Мы видим что к синтаксису добавились фигурные скобки. Процессоры Эльбрус основаны на <a href="https://ru.wikipedia.org/wiki/VLIW">VLIW</a> архитектуре, а значит могут исполнять множество статически спланированных команд за такт. Набор таких команд называется широкой командой (ШК) и заключается в фигурные скобки. Остальной синтаксис более или менее идентичен.<br />
<br />
Если посмотреть
на команду сборки, то вместо as используется <i>las</i>. Это наш местный ассемблер, но сейчас идёт процесс перехода на <i>gas</i>, поэтому скоро он станет неактуален (отдел, занимающийся ассемблером уже сейчас ругается если я его использую, но в дистрибутиве пока именно он).
<br />
Чтобы процессор мог исполнять много команд за такт, ему нужно много регистров. Согласен, что их никогда не бывает много, но для программы на Эльбрусе регистровый файл содержит 256 регистров общего назначения размером 64 бита. Из них 224 предназначены для процедурного стека, а 32 являются глобальными регистрами. В Эльбрусе нет отдельных регистров для плавающих вычислений, все они выполняются на одном конвейере и хранятся в общих
регистрах. Именование регистров идёт следующим образом:<br />
<ul style="text-align: left;">
<li><b>%r<номер></b> - прямоадресуемые регистры текущего окна. <b><номер></b> является индексом относительно базы текущего окна</li>
<li><b>%b[<номер>]</b> - вращаемые регистры текущего окна. <b><номер></b> - индекс относительно текущей базы</li>
<li><b>%g<номер></b> - глобальные регистры. <b><номер></b> является индексом относительно базы текущей глобальной области</li>
</ul>
Иногда в ассемблере регистры имеют различные префиксы. Подобные названия не влияют ни на что и нужны только для наглядности. Префиксы бывают следующие:<br />
<div class="---western" style="text-indent: 0cm;">
<ul style="text-align: left;">
<li><b>s</b> одинарный формат регистра - 32 бита (Single) </li>
<li><b>d</b> двойной формат регистра - 64 бита (Double) </li>
<li><b>x</b> расширенный двойной регистра - 80 бит (Extended) </li>
<li><b>q</b> квадро формат регистра - 128 бит (Quadro)</li>
</ul>
</div>
Существует программное соглашение, согласно которому для передачи аргументов в вызываемую процедуру мы используем вращающиеся регистры.<br />
<br />
Итак теперь переходим к самой программе. Думаю первые несколько строк и так понятны, поэтому рассмотрим сразу первую ШК:<br />
<br />
<code>
_start:<br />
{<br />
sdisp %ctpr1, 0x3<br />
addd, 0 0x0, 13, %b[3]<br />
addd, 2 0x0, [ _f64, _lts1 $hello_msg ], %b[2]<br />
addd, 1 0x0, 0x1, %b[1]<br />
addd, 3 0x0, 0x4, %b[0]<br />
}<br />
</code>
<br />
Рассмотрим первую команду <code>sdisp %ctpr1, 0x3</code>. А чтобы понять что это такое и что оно делает нужно ещё немного рассказать про механизм работы переходов в Эльбрусе. В процессорах Эльбрус вызов функции является дорогим удовольствием, поэтому переходы следует готовить заранее. Для этого существует два типа команд - <i>ctp</i> (подготовка перехода) и <i>ct</i> - фактический переход. Нам доступно три регистра перехода: <b>%ctpr1</b>-<b>%ctpr3</b>, т.е. за раз мы можем подготовить три маршрута для прыжка. Существует несколько команд подготовки перехода, нас здесь интересует <code>sdisp</code>. Эта команда подготавливает переход для системного вызова. Первым аргументом идёт регистр перехода, по которому мы будем совершать прыжок. Вторым аргументом - точка входа в операционную систему, нам она нужна равной 3 (64-х битный вход в ОС).<br />
<br />
Далее рассмотрим команды <code>addd</code>. Как я уже говорил, ассемблер Эльбруса не предназначен для людей, и общепринятых мнемоников здесь пока нет. Так в ассемблере нет команды <code>MOV</code>. Чтобы положить значение на регистр применяется команда <code>add</code>. Она производит сложение регистров или констант и записывает их в регистр.<br />
<br />
Для Эльбруса одновременно доступно 6 арифметико-логических каналов (АЛК), т.е. за такт мы можем производить до 6 сложений. Итак, в первой операции мы кладём число 13 в регистр <b>%b[3]</b> - это длина нашей строки. (В версиях для других архитектур мы вычисляли это программно, и в Эльбрусе можно сделать также, но для <i>las</i> у меня это так и не получилось, хотя в <i>gas</i> всё заработало). Далее на регистр <b>%b[2]</b> мы кладём адрес начала нашего сообщения. Затем в <b>%b[1]</b> кладём идентификатор устройства вывода, и, наконец, в <b>%b[0]</b> кладём номер системного вызова. В целом аналогия с другими архитектурами прослеживается.<br />
<br />
Далее может возникнуть вопрос зачем в команде <code>addd</code> третья <b>d</b>. В мнемониках команд, реализованных для нескольких форматов операндов, последняя буква обозначает используемый формат. В данном случае мы работаем в double формате, т.е. с полноценным 64-х битным регистром.<br />
<br />
Отдельно рассмотрим команду <code>addd, 2 0x0, [ _f64, _lts1 $hello_msg ], %b[2]</code>, которая, как можно догадаться, кладёт в регистр <b>%b[2]</b> адрес печатаемого сообщения. Для того чтобы закодировать адрес в памяти используется аргумент <code>[ _f64, _lts1 $hello_msg ]</code>. Квадратные скобки означают взятие адреса. Внутри расположен <b>длинный литерал</b>. Его содержимое означает следующее:<br />
<ul style="text-align: left;">
<li>_f64 - формат литерала. В данном случае мы говорим что это литерал размера 64 (хотя он уместится и в 32 бита)</li>
<li>_lts1 - литеральный слог, кодирующий константное значение. Всего доступно 4 литеральных слога, так что в одной ШК мы не сможем поместить более 4 длинных литералов (в случае формата _f64 - не более 2).</li>
<li>$hello_msg - идентификатор, обозначающий нашу метку</li>
</ul>
<br />
Во второй ШК у нас производится операция <code>call %ctpr1, wbs = 0x4</code>, которая вызывает функцию, переход на которую подготовлен на регистре <b>%ctpr1</b>. т.е. вызывается наш <b>write</b>. Второй аргумент задаёт смещение для новой базы регистрового окна. Здесь я не буду объяснять что это значит, т.к. это займёт много времени, просто пока придётся запомнить что это должно быть так (на самом деле это очень частный случай и нужно понимать как его высчитывать)<br />
<br />
В третьей ШК мы аналогичным образом подготавливаем переходы для вызова <b>exit</b>, и в четвёртой ШК мы его вызываем.<br />
<br />
Всё, проще некуда.<br />
<br />
<h3>
<a href="https://www.blogger.com/null" name="epilogue"></a>Послесловие</h3>
<br />
Как я уже говорил в начале, данный материал появился потому что я не смог найти чего-то подобного в сети. На самом деле многое я взял из этого <a href="http://alexanius-blog.blogspot.ru/2016/05/hello-world.html#0xax">[0xax]</a> блога - описание примера на x86 и вообще саму идею. Для остальных архитектур пришлось изворачиваться :) Позже, во время работы над заметкой, я нашёл это <a href="http://alexanius-blog.blogspot.ru/2016/05/hello-world.html#mechasm">[mechasm]</a> неплохое описание, но оно уже было неактуально.<br />
<br />
Вообще я планировал написать эту заметку за неделю-две и перейти на следующий пример. Более того хотел ещё включить описание llvm IR. Но внезапно простенькая заметка про hello world заняла у меня несколько месяцев. Преимущественно из-за Эльбруса. Тут оказалось много нового и непонятного при почти полном отсутствии читабельной документации. И тут хотелось бы сказать <b>огромное спасибо</b> многим моим коллегам, которые терпеливо в течении долгого времени разъясняли мне простейшие вещи.<br />
<br />
В данной заметки могут быть неточности, ошибки и вообще фиг знает что, поэтому если что-то не так - пишите, я поправлю :)<br />
<br />
<h3>
<a href="https://www.blogger.com/null" name="lit"></a>Источники</h3>
<br />
<a href="http://download.intel.com/design/processor/manuals/253665.pdf">[intel1]</a> Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 1: Basic Architecture<br />
<a href="https://www.blogger.com/null" name="intel2"></a><a href="http://www.intel.ru/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-manual-325462.pdf">[intel2]</a> Intel® 64 and IA-32 Architectures Software Developer’s Manual Combined Volumes: 1, 2A, 2B, 2C, 3A, 3B, 3C and 3D<br />
<a href="https://www.blogger.com/null" name="syscall1"></a><a href="http://blog.rchapman.org/post/36801038863/linux-system-call-table-for-x86-64">[syscall1]</a> Таблица системных вызовов linux <br />
<a href="https://www.blogger.com/null" name="syscall2"></a><a href="https://filippo.io/linux-syscall-table/">[syscall2]</a> Другая таблица системных вызовов linux<br />
<a href="https://www.blogger.com/null" name="as"></a><a href="https://sourceware.org/binutils/docs/as/">[as]</a> Мануал по ассемблеру <br />
<a href="https://www.blogger.com/null" name="0xax"></a><a href="http://0xax.blogspot.ru/2014/08/say-hello-to-x64-assembly-part-1.html">[0xax]</a> Серия постов про написание hello world на ассемблере amd64. Во многом при написании заметки я смотрел именно в этот пост, там весьма подробное и доходчивое описание с замечаниями в комментах<br />
<a href="https://www.blogger.com/null" name="mechasm"></a><a href="http://mech.math.msu.su/~zubr/asm.html">[mechasm]</a>Аналогичный пост на русском, который я нашёл не сразу и не пользовался им. Но стиль изложения мне нравится<br />
<a href="https://www.blogger.com/null" name="sparcv9"></a><a href="https://cr.yp.to/2005-590/sparcv9.pdf">[sparcv9]</a>The SPARC Architecture Manual Version 9<br />
<a href="https://www.blogger.com/null" name="sparcv9asm"></a><a href="https://www.blogger.com/www.cse.scu.edu/~atkinson/teaching/sp05/259/sparc.pdf">[sparcv9asm]</a> SPARC Assembly Language Reference Manual<br />
<a href="https://www.blogger.com/null" name="oracle"></a><a href="https://docs.oracle.com/cd/E19120-01/open.solaris/816-1681/index.html">[oracle]</a> Актуальная документация от Oracle<br />
<a href="https://www.blogger.com/null" name="sparcasmbook"></a><a href="http://www.amazon.com/SPARC-Architecture-Assembly-Language-Programming/dp/0130255963">[sparcasmbook]</a>SPARC Architecture, Assembly Language Programming, and C. Очень хороший учебник по ассемблеру и по спарку<br />
<a href="https://www.blogger.com/null" name="elbrusbook"></a><a href="https://www.blogger.com/www.mcst.ru/doc/book_121130.pdf">[elbrus]</a> Микропроцессоры и вычислительные комплексы семейства «Эльбрус»
</div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com39tag:blogger.com,1999:blog-935622623924408531.post-14783523697718838242016-01-09T21:25:00.000+02:002019-05-06T13:37:40.223+02:00Собираем кросс-компилятор gcc для sparc<div dir="ltr" style="text-align: left;" trbidi="on">
Бывает, что перед разработчиком встаёт задача собрать проект, запускающийся на одной платформе, но при этом для разработки проекта используется другая. Для этих целей применяется кросс-компилятор - специальная сборка компилятора, работающая на host-платформе, и генерирующая код для target-платформы. Здесь я расскажу как собирать gcc с хостом на x86, генерирующий код под sparc.<br />
<a name='more'></a>Думаю вкратце понятно что такое кросс-компилятор, и для чего он нужен, поэтому немного о данной заметке. Во многом это будет вольный конспект <a href="http://preshing.com/20141119/how-to-build-a-gcc-cross-compiler/">данного</a> поста. Основное различие в том, что там автор собирал компилятор для arm, а я делаю это для sparc, что вносит свои коррективы. Ну и я попытался немного автоматизировать данный процесс.<br />
<br />
Итак, поехали. Создаём какую-нибудь директорию, и скачиваем туда всё необходимое:<br />
<br />
<code>
$ mkdir tmp && cd tmp<br />
$ svn checkout svn://gcc.gnu.org/svn/gcc/trunk gcc_trunk<br />
$ wget http://ftp.gnu.org/gnu/binutils/binutils-2.25.tar.gz<br />
$ wget https://cdn.kernel.org/pub/linux/kernel/v4.x/testing/linux-4.4-rc3.tar.xz<br />
$ git clone git://sourceware.org/git/glibc.git<br />
$ wget http://ftp.gnu.org/gnu/glibc/glibc-2.22.tar.xz<br />
$ wget http://www.mpfr.org/mpfr-current/mpfr-3.1.3.tar.xz<br />
$ wget https://gmplib.org/download/gmp/gmp-6.1.0.tar.xz<br />
$ wget ftp://ftp.gnu.org/gnu/mpc/mpc-1.0.2.tar.gz<br />
$ wget ftp://gcc.gnu.org/pub/gcc/infrastructure/isl-0.15.tar.bz2<br />
<br />
$ for i in *.tar*; do tar -xpvf $i; done</code><br />
<br />
Это набор необходимых пакетов. Здесь используется транковская версия gcc (можно и стабильную) и транковская версия glibc. Последнее связано с тем что >=gcc-5 не собирает glibc-2.22 из-за ошибок компиляции. Недавно вышел glibc-2.23, но я его не пробовал.<br />
<br />
gcc использует сторонние библиотеки, поэтому для сборки нужно их заранее подготовить:<br />
<br />
<code>
$ pushd gcc_trunk/<br />
$ ln -s ../mpfr-3.1.3 mpfr<br />
$ ln -s ../gmp-6.1.0 gmp<br />
$ ln -s ../mpc-1.0.2 mpc<br />
$ ln -s ../isl-0.15 isl<br />
$ popd<br />
</code>
<br />
<br />
Теперь создадим директорию куда будем устанавливать компилятор и библиотеки:<br />
<br />
<code>
$ mkdir cross<br />
$ export PREFIX="`pwd`/cross" # Директория установки<br />
$ export TARGET="sparc-sun-linux" # Целевая архитектура <br />
$ # export TARGET="sparc64-sun-linux" # Это если нам нужен 64-х битный sparc<br />
$ export PATH="$PREFIX"/bin:$PATH # Пути для бинарников<br />
</code>
<br />
<br />
Сначала соберём binutils. В нём содержатся кросс-ассемблер, кросс-линкер и прочие инструменты:<br />
<br />
<code>
$ export BINUTILS_OBJS="`pwd`/binutils_objs"<br />
$ export BINUTILS_SRC="`pwd`/binutils-2.25/"<br />
$ mkdir -p "$BINUTILS_OBJS"<br />
$ pushd "$BINUTILS_OBJS" > /dev/null<br />
$ "$BINUTILS_SRC"/configure --prefix="$PREFIX" --target=$TARGET --with-sysroot</code><br />
<code>$ make -j5<br />
$ make install<br />
$ popd > /dev/null<br />
</code>
<br />
<br />
Далее ставим некоторые файлы ядра чтобы приложения могли использовать спарковские системные вызовы<br />
<br />
<code>
$ export LINUX_SRC="`pwd`/linux-4.4-rc3"<br />
$ pushd "$LINUX_SRC" > /dev/null<br />
$ export LINUX_ARCH="sparc"<br />
$ # export LINUX_ARCH="sparc64" # Это для 64-х битной версии<br />
$ make ARCH=$LINUX_ARCH INSTALL_HDR_PATH="$PREFIX/$TARGET" headers_install<br />
$ popd > /dev/null<br />
</code>
<br />
<br />
Собираем только компиляторы без библиотек<br />
<br />
<code>
$ export GCC_OBJS="`pwd`/gcc_objs"<br />
$ export GCC_SRC="`pwd`/gcc_trunk"<br />
$ mkdir -p "$GCC_OBJS"<br />
$ pushd "$GCC_OBJS" > /dev/null<br />
$ "$GCC_SRC"/configure --prefix="$PREFIX" --target=$TARGET --enable-languages=c,c++,fortran --enable-gold=yes --enable-ld=yes --enable-lto CFLAGS="-O3" CXXFLAGS="-O3"<br />
$ make -j5 all-gcc<br />
$ make install-gcc<br />
$ popd > /dev/null<br />
</code>
<br />
<br />
Конфигурацию gcc следует подстраивать под себя, мне обычно нужно чтобы компилятор имел фронтенды только для c/c++/fortran (опция --enable-languages), а также чтобы он умел lto.<br />
<br />
Теперь ставим заголовочные файлы glibc. При этом создадим заглушку для libc.so. Тут стоит обратить внимание на опцию "-fno-stack-protector", без неё версия для sparc не соберётся.<br />
<br />
<code>
$ export GLIBC_OBJS="`pwd`/glibc_objs"<br />
$ export GLIBC_SRC="`pwd`/glibc"<br />
$ mkdir -p "$GLIBC_OBJS"<br />
$ pushd "$GLIBC_OBJS" > /dev/null<br />
$ "$GLIBC_SRC"/configure --prefix="$PREFIX"/$TARGET/ --build=$MACHTYPE --host=$TARGET --target=$TARGET --with-headers="$PREFIX"/$TARGET/include CFLAGS="-O2 -fno-stack-protector" CPPFLAGS="-O2 -fno-stack-protector"<br />
$ make install-bootstrap-headers=yes install-headers<br />
$ make -j5 csu/subdir_lib<br />
$ install csu/crt1.o csu/crti.o csu/crtn.o "$PREFIX"/$TARGET/lib<br />
$ "$PREFIX"/bin/$TARGET-gcc -nostdlib -nostartfiles -shared -x c /dev/null -o "$PREFIX"/$TARGET/lib/libc.so<br />
$ touch "$PREFIX"/$TARGET/include/gnu/stubs.h<br />
$ popd > /dev/null<br />
</code>
<br />
<br />
Теперь соберём библиотеки поддержки компилятора. В них, например, содержится обработка исключений c++.<br />
<br />
<code>
$ pushd "$GCC_OBJS" > /dev/null<br />
$ make -j5 all-target-libgcc<br />
$ make install-target-libgcc<br />
$ popd > /dev/null<br />
</code>
<br />
<br />
Непосредственно собираем и устанавливаем glibc<br />
<br />
<code>
$ pushd "$GLIBC_OBJS" > /dev/null<br />
$ make -j8<br />
$ make install<br />
$ popd > /dev/null<br />
</code>
<br />
<br />
И последнее - собираем и устанавливаем стандартную библиотеку c++:<br />
<br />
<code>
$ pushd "$GCC_OBJS" > /dev/null<br />
$ make -j8<br />
$ make install<br />
$ popd > /dev/null</code><br />
<br />
Собственно, всё, теперь можно использовать кросс-компилятор. Пока что мне не удалось собрать версию под 64 бита с multilib'ом - т.е. чтобы компилятор целевой платформой имел sparc64, но при этом умел генерить 32-х битный код, но в целом это не большая потеря. Можно тупо собрать две версии компилятора.<br />
<br />
Также у автора оригинального поста есть <a href="https://gist.github.com/preshing/41d5c7248dea16238b60">скрипт</a> для автоматической сборки, но я его не тестировал.</div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com0tag:blogger.com,1999:blog-935622623924408531.post-35725706673725787312015-12-15T20:45:00.000+02:002015-12-15T20:45:09.482+02:00Опасность вызова функций без объявленного прототипа в C<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
Ещё один пост про тонкости линковки. Предыдущий лежит <a href="http://alexanius-blog.blogspot.ru/2014/10/blog-post.html">здесь</a>. На этот раз речь пойдёт преимущественно о старых исходниках, переносе их в 64-х битный режим, ну и немного про режим сборки "вся программа". Пример основан на реальных <strike>событиях</strike> исходниках.<br />
<a name='more'></a>В языке C в большинстве случаев <a href="http://stackoverflow.com/questions/434763/are-prototypes-required-for-all-functions-in-c89-c90-or-c99">допустимо</a> делать вызов функции если в модуле не был объявлен прототип функции. Это очень плохое свойство языка, которое было оставлено для совместимости со старым софтом. Давайте для понимания сразу рассмотрим пример:<br />
<pre style="background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> $ cat t1.c
int main()
{
int * a;
a = (int *)foo();
*a = 10;
}
$cat t2.c
#include <stdlib.h>
int * foo(void)
{
int * a = malloc(sizeof(int));
*a = 100;
return a;
}
$ lcc t1.c t2.c -Wl,-Tdata=0x700000000
lcc: "1.c", line 5: warning: function "foo" declared implicitly
[-Wimplicit-function-declaration]
a = (int *)foo();
^
</code></pre>
</div>
</div>
Видно что в t1.c функция foo не имеет прототипа, и что именно мы вызываем становится понятно только после линковки. Поэтому и ругается компилятор.<br />
<br />
Сразу скажу, что используется компилятор Эльбруса, и на gcc я не смог это воспроизвести. И это вовсе не комплимент в сторону gcc (ну или моих рук). Опция <code>-Wl,-Tdata=0x700000000</code> нужна чтобы секция данных начиналась с больших адресов (допустимых только в 64-битном режиме). Теперь запустим пример и получим:<br />
<pre style="background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> $ ./a.out
Segmentation fault
</code></pre>
Казалось бы, что тут не так? Начнём рассмотрение со строчки <code>a = (int *)foo();</code>. На первый взгляд всё корректно. Но в реальности при сборке объектника из t1.c компилятор ничего не знает о функции foo, поэтому подставляет прототип по умолчанию, который возвращает <code>int</code>. Это приводит к генерации следующего кода:<br />
<pre style="background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> o7. CALL proc:foo () :4<sint32> // 't1.c' 4
o8. I2P o7:4<sint32> :8<sint32 *> // 't1.c' 4
o9. WRITE loc:a <- o8:8:(sint32 *) // 't1.c' 4
</code></pre>
Видно что мы берём возвращаемое из функции значение как <code>int</code> размера 4 байта, и приводим его к <code>(int *)</code> размера 8 байт. На 32-х битной системе это работает нормально (очевидно, что <code>(int *)</code> там тоже 4 байта). Проблемы возникают на больших адресах 64-х битного режима. Думаю теперь становится понятно зачем была нужна опция <code>-Wl,-Tdata=0x</code><code>700000000</code>. Она заставляет malloc выдавать указатели со значениями > 2^32. Соответственно в момент преобразования значения в int мы теряем значимые биты, что приводит к ошибке сегментирования.<br />
<br />
А теперь про режим сборки "вся программа", он же -fwhole, он же -flto. В данном режиме подобные ошибки становятся видны, т.к. оба модуля становятся видны, и мы можем подставить корректный вызов. Но возникает вопрос - а надо ли? Тут моё мнение разошлось с мнением более умных людей, которые считают что в режиме сборки "вся программа" нужно эмулировать ошибки обычного линкера,т.е. генерить некорректный код и ломаться тогда когда этого никто не ожидает.<br />
<br />
В общем мораль сего поста такова - всегда объявляйте прототип вызываемой функции.</div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com3tag:blogger.com,1999:blog-935622623924408531.post-1286371106486726492015-05-09T22:36:00.000+02:002016-06-04T12:44:49.674+02:00Лекции по оптимизирующим компиляторам<div dir="ltr" style="text-align: left;" trbidi="on">
В последние несколько месяцев занимался довольно новым для себя делом - читал лекции по курсу "Оптимизирующие компиляторы". Раньше мне не приходилось читать лекции (ну или почти не приходилось), да и предмет не самый распространённый. В общем о том что получилось, что хочется дополнить и исправить расскажу ниже.<br />
<a name='more'></a>Сам по себе курс длится два семестра, я читаю только первый. В нём предполагается рассказать в целом про оптимизирующие компиляторы и сами оптимизации. Курс был составлен на основе моих конспектов этого курса, прочитанного в МФТИ в 2013 году.<br />
Теперь про содержимое самого курса. Сейчас он состоит из 11 лекций:<br />
<ol style="text-align: left;">
<li>Введение в оптимизирующие компиляторы</li>
<li>Введение в теорию языков и автоматов. Лексический анализ.</li>
<li>Введение в теорию языков и автоматов. Синтаксический анализ.</li>
<li>Алгоритмы на графах, используемые в компиляторах</li>
<li>Структуры данных в оптимизирующих компиляторах</li>
<li>Оптимизации управления</li>
<li>Потоковые оптимизации</li>
<li>Цикловые оптимизации</li>
<li>Цикловые оптимизации (часть 2). Оптимизации памяти.</li>
<li>Анализ указателей</li>
<li>Планирование кода. Межпроцедурные оптимизации.</li>
</ol>
<div style="text-align: left;">
Я хочу дорабатывать данный курс. Пока мне видятся не лучшими решениями следующие вещи:</div>
<ul style="text-align: left;">
<li>Наличие второй и третьей лекции. Теорию автоматов нужно изучать отдельно, а информацию по фронтенду можно слить в одну лекцию. Но не думаю что это подойдёт для кафедры, на которой читается курс - отдельно теории автоматов у них нет.</li>
<li>В девятой лекции идёт остаток восьмой про циклы и ещё треть лекции про оптимизации памяти. По идее про оптимизации памяти нужна отдельная лекция, но у меня по данной теме очень мало информации.</li>
<li>Одиннадцатая лекция тоже содержит две разные темы, их неплохо бы читать отдельно.</li>
</ul>
Есть ещё некоторые замечания по каждой лекции в отдельности, но об этом я расскажу когда (если) выложу слайды.<br />
Ещё есть масса тем, про которые или не рассказано ничего, или сказано очень мало:<br />
<br />
<ul style="text-align: left;">
<li>ничего нет про распараллелвание</li>
<li>почти ничего нет про режим -fwhole/-flto</li>
<li>мало про профилирование</li>
<li>ничего нет про векторизацию</li>
<li>почти ничего про межпроцедурные анализы</li>
<li>ничего про оптимизации C++</li>
<li>ничего про средства отладки/разработки/workflow</li>
</ul>
В общем сейчас у меня появились небольшие наработки по данному курсу, есть что и куда пилить. Но хотелось бы узнать у сообщества: что ещё следует читать в курсе оптимизирующих компиляторов, возможно кто-то знает удачные примеры таких курсов?<br />
<br /></div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com4tag:blogger.com,1999:blog-935622623924408531.post-36773869897426984262015-03-29T22:31:00.000+02:002015-03-29T22:31:14.588+02:00Антикафе GeekTime (почти реклама)<div dir="ltr" style="text-align: left;" trbidi="on">
Расскажу про один интересный проект, к которому имею некоторое отношение. Это антикафе <a href="https://vk.com/geek.time">GeekTime</a> (не путать с сайтом, имеющим похожее название ;) ).<br />
<br />
<a name='more'></a>Для начала поясню что такое "антикафе". Это заведение, в которое люди приходят чтобы посидеть и провести время. От просто кафе отличается тем что здесь не подают еду, но можно принести что-нибудь своё или заказать. Но есть кофемашина, можно сделать чай, печеньки прилагаются. Оплата идёт только за проведённое время (максимум 500 рублей).<br /><br />
<br />
Как можно понять из названия это антикафе для гиков. Да, здесь есть игры, комиксы и т.п., но полагаю моих читателей больше интересуют вопросы, связанные с IT. Да, GeekTime именно антикафе для IT'шников самого разного плана. Здесь проводятся тематические мероприятия - лекции, семинары, мастер-классы. Самым крупным было проведение трёхдневного хакатона <a href="https://vk.com/ggj15">Global Game Jam</a>. Частенько здесь тусуются разные программисты, в т.ч. и я, так что можно придти поболтать :)<br />
<br />
Из интересного здесь есть 3d-принтер <a href="http://3dpr.ru/catalog/3d-printer-felix-20">Felix-2.0</a>, на котором можно заказать печать какой-нибудь модели. Также есть очки виртуальной реальности <a href="https://ru.wikipedia.org/wiki/Oculus_Rift">OculusRift</a> и контроллеры <a href="https://ru.wikipedia.org/wiki/Hydra_%28%D0%BA%D0%BE%D0%BD%D1%82%D1%80%D0%BE%D0%BB%D0%BB%D0%B5%D1%80%29">Razer Hydra</a>, в которых можно не только поиграть, но и отладить свою собственную игру (для целей разработки специальный дешёвый тариф). Присутствует также очень небольшая, но интересная техническая библиотека. Ну и конечно можно просто посидеть и поработать в своё удовольствие, если вы не любите работать в офисе.<br />
<br />
<br />
Ещё интересный момент состоит в том, что антикафе задумывалось как место тусовки IT'шников, и здесь рады предоставить место для встреч различным user-группам и прочим тематическим тусовочкам. Также очень круто если вы хотите прочитать какую-нибудь лекцию - антикафе предоставит помещение, проектор, интернет, рекламу. Администраторы (половина из которых сами IT'щники) будут рады просто поболтать.<br />
<br />
<br />В общем всех приглашаю в антикафе GeekTime, здесь круто, уютно и печеньки.</div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com0tag:blogger.com,1999:blog-935622623924408531.post-16050974990938724542014-12-03T14:50:00.000+02:002014-12-03T14:50:10.621+02:00Наведённые эффекты от оптимизаций<div dir="ltr" style="text-align: left;" trbidi="on">
При работе с оптимизациями могут возникать весьма забавные наведённые эффекты. Они не очевидным образом влияют на скорость исполнения программ. Возьмём, например, мой <a href="http://alexanius-blog.blogspot.ru/2014/11/strict-aliasing.html">недавний</a> strict-aliasing. Когда я его исследовал столкнулся со следующим явлением:<br />
<br />
<code>"-O3" :442.34:real:438.46:user:0.51:sys<br />"-O3 -fno-strict-aliasing" :376.43:real:374.28:user:0.39:sys</code><br />
<br />
В таблице приведены опции компиляции теста и замеры при его исполнении. Видно, что применение strict-aliasing просаживает тест на 15%. Во-первых это очень много, а во-вторых совершенно непонятно почему. Ведь strict-aliasing это даже не оптимизация, а анализ, который разрывает зависимости между LOAD/STORE. Как можно замедлить программу разорвав несколько лишних зависимостей? Оказывается легко.<br />
<br />
В Эльбрусах есть аппаратная поддержка технологии dam (memory access disambiguation). В двух словах она делает следующее. Если на этапе компиляции невозможно определить ни независимость, ни пересечение операций, а LOAD очень хочется закинуть за STORE, то это можно сделать, и ниже поставить проверку адресов, по которым работают эти операции. Если они не совпадают, то всё хорошо, если совпадают, то уходим на компенсирующий код и делаем всё по-старому.<br />
<br />
Так вот, теперь как это связано со strict-aliasing. Внезапно на одном тесте strict-aliasing определил независимость операций, с которыми ранее работал dam. Из-за этого dam'у пришлось применяться к другим операциям, которые по факту оказались зависимыми. Из-за этого много времени ушло на компенсирующий код, и исполнение деградировало. Теперь смотрим без dam:<br />
<br />
<code>"-O3 -fno-dam" :471.28:real:468.70:user:0.39:sys<br />"-O3 -fno-dam -fno-strict-aliasing" :473.76:real:470.96:user:0.36:sys</code><br />
<br />
Видно, что тест более не деградирует, однако исполняется заметно медленнее.<br />
<br />
А мораль отсюда такова: даже если в целом оптимизация ведёт себя хорошо - обязательно найдётся тест, который будет работать медленнее.</div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com0tag:blogger.com,1999:blog-935622623924408531.post-90326536362574855322014-11-29T13:28:00.001+02:002014-11-29T13:28:53.791+02:00Слайды с доклада про strict-aliasing<div dir="ltr" style="text-align: left;" trbidi="on">
Выступил на <a href="http://conf57.mipt.ru/ru/info/main/">57-ой научной конференции МФТИ</a> с докладом про мой любимый strict-aliasing. Слайды можно посмотреть здесь:<br />
<br />
<iframe allowfullscreen="" frameborder="0" height="355" marginheight="0" marginwidth="0" scrolling="no" src="//www.slideshare.net/slideshow/embed_code/42134968" style="border-width: 1px; border: 1px solid #CCC; margin-bottom: 5px; max-width: 100%;" width="425"> </iframe> <br />
<div style="margin-bottom: 5px;">
<b> <a href="https://www.slideshare.net/alexmarkin50/markin-astrict-aliasing" target="_blank" title="Правила перекрытия объектов в памяти">Правила перекрытия объектов в памяти</a> </b> from <b><a href="https://www.slideshare.net/alexmarkin50" target="_blank">Alex Markin</a></b> </div>
<br />
Наверное стоит добавить пару комментариев по теме и вообще.<br />
<br />
Такая тема взята потому как я являюсь автором strict-aliasing в компиляторе Эльбруса. Я когда-то уже <a href="http://alexanius-blog.blogspot.ru/2012/12/understanding-cc-strict-aliasing.html">переводил</a> статью по данной теме, но в реальности ещё не видел ни одной нормальной публикации. Хочу сделать её сам, но пока не получается.<br />
<br />
Сам strict-aliasing разрывает зависимости между load'ами и store'ами несовместимых типов. Что такое несовместимые типы долго объяснять, скажу только что совместимы только одни и те же типы с квалификаторами и типы, вложенные в агрегатные типы.<br />
<br />
В компиляторах реализация strict-aliasing обычно делится на две (минимум) части - оптимизационный анализ и анализ нарушений. Последний в gcc реализован на редкость плохо, мне удалось сделать анализ дающий очень мало false-positive. Я пока не замерял как gcc'шный анализ влияет на производительность, но на компилятоое Эльбруса удалось добиться прироста до 8%. При том, что в реализации есть серьёзные загрубления.</div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com0tag:blogger.com,1999:blog-935622623924408531.post-70688913608991611192014-11-21T13:24:00.002+02:002014-11-21T13:24:44.429+02:00Эльбрус-8С<div dir="ltr" style="text-align: left;" trbidi="on">
В МЦСТ постоянно идёт разработка новых процессоров, о которых предпочитается не заявлять публично. Но после получения хоть как-нибудь результатов разрешается что-нибудь рассказать. Относительно недавно появились инженерные образцы нового процессора <a href="http://www.mcst.ru/novyj-8yadernyj-mikroprocessor-elbrus-8c">Эльбрус-8С</a>, о котором пойдёт речь.<br />
<a name='more'></a><br />
Вот так выглядит наш новый красавчик:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaYIvy-PHGJxYyZtEQIP52HwzJkvJ4A2-a83iw1WV-ZVu9lPHrMPJEbMB30fFXG8_86KtYw5Yb5_M7LjilBVhFZVz_MLFGeA9BU5wMB6cVfsiVDb9e7GHsgavLY08ALX0DehG8Z08U52kW/s1600/%D0%BF%E2%95%9C%D0%BF%E2%95%A9%D1%8F%E2%96%84%D0%BF%E2%95%A0%D1%8F%E2%94%80%D1%8F%E2%94%90%D1%8F%E2%94%82+8%D0%BF%E2%95%91.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaYIvy-PHGJxYyZtEQIP52HwzJkvJ4A2-a83iw1WV-ZVu9lPHrMPJEbMB30fFXG8_86KtYw5Yb5_M7LjilBVhFZVz_MLFGeA9BU5wMB6cVfsiVDb9e7GHsgavLY08ALX0DehG8Z08U52kW/s1600/%D0%BF%E2%95%9C%D0%BF%E2%95%A9%D1%8F%E2%96%84%D0%BF%E2%95%A0%D1%8F%E2%94%80%D1%8F%E2%94%90%D1%8F%E2%94%82+8%D0%BF%E2%95%91.jpg" height="200" width="145" /></a></div>
<br />
А вот он же на плате:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicZKJpZxJ7ddTa97WgwCtc5BJ7gd2cZD47CbFJllXRcj5mHGkFbX1agzK-abh4a9Pw_zllG1MF_XLAiUAZ38N44hYAeuziGPiHGxpl4WD1iKWRQ2-f3wK-G3u6O4i0oPPXXtOHpc9e9cF3/s1600/DSC07581_2.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicZKJpZxJ7ddTa97WgwCtc5BJ7gd2cZD47CbFJllXRcj5mHGkFbX1agzK-abh4a9Pw_zllG1MF_XLAiUAZ38N44hYAeuziGPiHGxpl4WD1iKWRQ2-f3wK-G3u6O4i0oPPXXtOHpc9e9cF3/s1600/DSC07581_2.jpg" height="227" width="400" /> </a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Можно дополнить сравнительную таблицу характеристик из моего предыдущего <a href="http://alexanius-blog.blogspot.ru/2014/03/blog-post.html">поста</a>: </div>
<br />
<table>
<tbody>
<tr></tr>
</tbody></table>
<table><tbody>
<tr></tr>
</tbody></table>
<table><tbody>
<tr><th></th><th>Эльбрус-2С+</th><th>Эльбрус-4С</th><th>Эльбрус-8С</th></tr>
<tr><td>Тех. процесс, нм</td><td>90</td><td>65</td><td>28</td></tr>
<tr><td>Тактовая частота, МГц</td><td>500</td><td>800</td><td>1300</td></tr>
<tr><td>Число ядер CPU</td><td>2 + 4 DSP</td><td>4</td><td>8</td></tr>
<tr><td>Пиковая производительность, 64 разряда, Gflops</td><td>8</td><td>25</td><td>125</td></tr>
<tr><td>Пиковая производительность, 32 разряда, Gflops</td><td>16</td><td>50</td><td>250</td></tr>
<tr><td>Кэш 1 уровня (на ядро, данных + команд), КБ</td><td>64 + 64</td><td>64 + 128</td><td>64 + 128</td></tr>
<tr><td>Кэш 2 уровня, КБ</td><td>2 * 1024</td><td>4 * 2048</td><td>8 * 512 </td></tr>
<tr><td>Кэш 3 уровня, МБ</td><td>-</td><td>-</td><td>16 </td></tr>
<tr><td>Количество процессоров в системе, шт.</td><td>до 4</td><td>до 4</td><td>до 4</td></tr>
<tr><td>Пропускная способность канала межпроцессорного обмена, ГБ/с</td><td>3 * 4</td><td>3 * 16</td><td>3 * 16 </td></tr>
<tr><td>Скорость обмена с памятью, ГБ/с</td><td>12.8</td><td>38.4</td><td>51,2</td></tr>
<tr><td>Средняя рассеиваемая мощность, Вт</td><td>25</td><td>45</td><td>пока неизвестно</td></tr>
<tr><td>Количество транзисторов, шт.</td><td>368 млн.</td><td>986 млн.</td><td>2.7 млрд.</td></tr>
</tbody></table>
<br />
Видно, что произошёл значительный прирост производительности. Также помимо самого процессора к нему прилагается плата <a href="http://www.mcst.ru/kpi-dlya-perspektivnykh-vysokoproizvoditelnykh-mikroprocessorov">КПИ-2</a> (Контроллер переферийных устройств)<br />
<br />
Из фишечек нового процессора и платы стоит отметить:<br />
<ul style="text-align: left;">
<li>переход на тех. процесс 28 нм. (кто там ныл что мы безнадёжно отстали?)</li>
<li>появился кэш L3</li>
<li>увеличено количество вычислительных устройств с плавающей запятой с 4 до 6 </li>
<li>новая схема фильтрации снупирования</li>
<li>канальность памяти увеличилась до 4</li>
</ul>
В общем ждём промышленного производства и новых моделей.</div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com19tag:blogger.com,1999:blog-935622623924408531.post-7746398553286318072014-11-14T11:35:00.001+02:002014-11-14T11:35:27.469+02:00Новый блог о компиляторах: compileit.ru<div dir="ltr" style="text-align: left;" trbidi="on">
Решил попробовать запустить один проект - блог о компиляторах и языках программирования <a href="http://compileit.ru/">http://compileit.ru</a>. В данном блоге я публикую ссылки на различные статьи, публикации или просто интересные новости по данной тематике. Основной целью блога (помимо сбора интересующей меня информации, конечно) является организация вокруг него русскоязычного сообщества разработчиков компиляторов и людей, интересующихся языками программирования. Мне эта задача кажется важной в первую очередь потому что сейчас отсутствует нормальный обмен информацией между различными группами исследователей/разработчиков, и ни у кого нет общей картины направления движения индустрии. Более того может получаться ситуация, что исследование, проводимое в одной группе уже было проведено в другой, опубликовано где-то, где гугл не ищет, и получается, что часть работы была проделана зря. Я надеюсь, что если исследователи и разработчики будут обсуждать свои идеи в единой площадке, то это даст мощный толчок к развитию компиляторостроения и информатики в России.<br />
<br /></div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com0tag:blogger.com,1999:blog-935622623924408531.post-46691289402055393502014-10-20T20:03:00.000+03:002014-11-01T18:45:53.545+02:00Сложности линковки<div dir="ltr" style="text-align: left;" trbidi="on">
В очередной раз столкнулся с довольно забавным случаем из исходников. Что характерно это SPEC, и в нём обнаружилась ошибка (уже вторая с которой я столкнулся!). Причём для проявления ошибки должны были очень удачно сложиться звёзды.<br />
<i>/**</i><br />
<i> * Это вторая редакция поста с несколько расширенным разбором случая </i><br />
<i>*/</i><br />
Я не буду показывать весь SPEC, а рассмотрю только маленький примерчик.<br />
<a name='more'></a><code>//------ t1.cpp<br /><br />#include <stdlib.h><br />#include <stdio.h><br /><br />//namespace<br />//{<br />class A<br />{<br />public:<br /> A(){printf("1\n");a=1;}<br /> int a;<br />};<br />//}<br /><br />void foo(void * a)<br />{<br /> a = new A;<br />}<br /><br />//------ t1.h<br /><br />void foo(void * a);<br /><br />//------ t2.cpp<br /><br />#include <stdlib.h><br />#include <stdio.h><br /><br />//namespace<br />//{<br />class A<br />{<br />public:<br /> A(){printf("2\n");b=1;}<br /> int b;<br />};<br />//}<br /><br />void bar(void * a)<br />{<br /> a = new A;<br />}<br /><br />//------ t2.h<br /><br />void bar(void * a);<br /><br />//------ main.cpp<br /><br />#include "t1.h"<br />#include "t2.h"<br /><br />int main()<br />{<br /> void * a;<br /> foo(a);<br /> bar(a);<br />}</code>
<br />
<br />
Из исходника видно, что при работе `foo' вызывается конструктор объекта `A' из файла `t1.cpp', который выводит `1', а при работе `bar' вызывается конструктор объекта `A' из файла `t2.cpp', который выводит `2'. Смотрим что у нас получается по факту:<br />
<br />
<code>$ g++ *cpp<br />
$ ./a.out<br />
1<br />
1</code>
<br />
<br />
Если посмотреть дизассемблер, то чётко видно, что вызываются одинаковые<br />
функции:<br />
<br />
<code>080485dc <_Z3fooPv>:<br />
...<br />
80485f5: e8 1e 00 00 00 call 8048618 <_ZN1AC1Ev><br />
...<br />
<br />
08048638 <_Z3barPv>:<br />
...<br />
8048651: e8 c2 ff ff ff call 8048618 <_ZN1AC1Ev></code><br />
<br />
Теперь о том почему так получается. В стандарте есть понятие `linkage':<br />
<blockquote class="tr_bq">
3.5 Program and linkage<br />
<br />
2. A name is said to have linkage when it might denote the same object,<br />
reference, function, type, template, namespace or value as a name introduced by<br />
a declaration in another scope:<br />
<br />
— When a name has external linkage, the entity it denotes can be referred to by<br />
names from scopes of other translation units or from other scopes of the same<br />
translation unit.</blockquote>
<br />
Здесь говорится, что имя имеет "linkage" когда оно указывает на тот же объект по имени в другом scope'е. При этом если "linkage" идёт как external, то связывание производится из разных translation units, коими являются наши cpp файлы.<br />
<br />
Здесь стоит заметить, что хотя в разных модулях у нас разные объекты, имя у них идёт одинаковое, а связывание производится именно по имени.<br />
<br />
Далее смотрим<br />
<blockquote class="tr_bq">
1.4 Implementation compliance<br />
<br />
6. The templates, classes, functions, and objects in the library have external<br />
linkage (3.5)</blockquote>
<br />
Т.е. здесь чётко говорится, что наши классы имеют external linkage.<br />
<br />
Для обхода этой ошибки рекомендуется поместить оба класса в безымянные пространства имён (они закомментированы в примере). Тогда оба имени `A' будут находится в разных scope'ах, и не будут пересекаться:<br />
<br />
<code>080485f9 <_Z3fooPv>:<br />
...<br />
8048612: e8 c5 ff ff ff call 80485dc <_ZN12_GLOBAL__N_11AC1Ev><br />
...<br />
08048655 <_Z3barPv>:<br />
...<br />
804866e: e8 c5 ff ff ff call 8048638 <_ZN12_GLOBAL__N_11AC1Ev><br />
...</code><br />
<br />
Теперь то, что не вошло в первую редакцию поста.<br />
<br />
Попробуем скомпилировать с оптимизацией:<br />
<br />
<code>$ g++ *cpp -O1<br />
$ ./a.out<br />
1<br />
2</code><br />
<br />
На самом деле это чистое совпадение, связанное с тем, что с -O1 включается inline, и конструкторы тупо подставляются в тела вызывающих функций.<br />
<br />
Более того такое поведение компилятора совершенно законно. В стандарте есть "3.2 One definition rule", который для таких случаев гласит следующее:<br />
<br />
<blockquote class="tr_bq">
5. There can be more than one definition of a class type (Clause 9),<br />enumeration type (7.2), inline function with external linkage (7.1.2), class<br />template (Clause 14), non-static function template (14.5.6), static data member<br />of a class template (14.5.1.3), member function of a class template (14.5.1.1),<br />or template specialization for which some template parameters are not specified<br />(14.7, 14.5.5) in a program provided that each definition appears in a<br />different translation unit, and provided the definitions satisfy the following<br />requirements. Given such an entity named D defined in more than one translation<br />unit, then<br /><br />— each definition of D shall consist of the same sequence of tokens; and<br /><br />...<br />Тут ещё несколько сложных правил<br />...<br /><br />... If the definitions of D satisfy all these requirements, then the program<br />shall behave as if there were a single definition of D. If the definitions of D<br />do not satisfy these requirements, then the behavior is undefined.</blockquote>
Т.е. здесь говорится, что в <u>разных translation unit может быть несколько определений одного и того же класса</u> при определённых условиях. Меня эта формулировка очень удивила, но в принципе она довольно логична. В приведённом примере нарушаются условия, т.о. пример имеет UB и компилятор может делать с ним вообще всё что угодно. Т.е. разное поведение на -O0 и -O1 в данном случае совершенно допустимо.<br />
<br />
Кстати, с 5-ой версии gcc научится такие ошибки отлавливать в режиме -flto:<br />
<br />
g++ *cpp -Wall -flto && ./a.out <br />t1.cpp:4:7: warning: type ‘struct A’ violates one definition rule [-Wodr]<br /> class A<br /> ^<br />t2.cpp:6:7: note: a different type is defined in another translation unit<br /> class A<br /> ^<br />t1.cpp:8:9: note: the first difference of corresponding definitions is field ‘a’<br /> int a;<br /> ^<br />t2.cpp:10:9: note: a field with different name is defined in another translation unit<br /> int b;<br /> ^<br /><br />
Почему я так подробно написал об этой ошибке? Потому что столкнулся с ней я на примере, строка компиляции которого занимает 2 экрана, время компиляции пол часа, проявилось оно только в режиме компиляции всей программы, а свалилось оно на моей оптимизационной фазе из-за того что некорректно отработала предыдущая фаза, из-за того что к ней пришло некорректное представление. Далее пару вечеров обсуждали что именно произошло, кто виноват и что делать. В общем вроде бы мелочь, а столько веселья!<br />
</div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com0tag:blogger.com,1999:blog-935622623924408531.post-52140313223865481042014-08-04T09:00:00.000+03:002014-08-04T09:00:01.435+03:00Реализация таблиц виртуальных функций в C++<div dir="ltr" style="text-align: left;" trbidi="on">
Нашёл в архивах Сети хорошую <a href="http://web.archive.org/web/20120517021435/http://tinydrblog.appspot.com/?p=89001">статью</a> про реализацию таблиц виртуальных функций в C++, поэтому решил <a href="https://github.com/alexanius/translations/blob/master/VTable%20Notes%20on%20Multiple%20Inheritance%20in%20GCC%20C%2B%2B%20Compiler%20v4.0.1/vtable.markdown">перевести</a>. Узнал несколько крайне интересных деталей. Например, что бывает до двух реализаций одного (!) конструктора и до трёх реализаций одного (!) деструктора. Ну и ещё про то что такое VTT (virtual table table), с чего я собственно на эту статью и вышел.<br />
<br /></div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com0tag:blogger.com,1999:blog-935622623924408531.post-34135610587596939252014-08-03T18:04:00.000+03:002014-08-03T18:05:30.375+03:00llvm vs. gcc - 2014<div dir="ltr" style="text-align: left;" trbidi="on">
Я уже <a href="http://alexanius-blog.blogspot.ru/2013/10/llvm-vs-gcc.html">писал</a> про сравнение gcc и llvm в 2013 году, и вот недавно <a href="http://vmakarov.fedorapeople.org/spec/">вышло</a> сравнение свежих версий. Сравнение претерпело некоторые изменения, и разумеется, хотелось бы об этом написать.<br />
<a name='more'></a>Итак, в этом году сравниваем gcc-4.9 и llvm-3.4. При этом добавлены результаты для gcc-4.8 и llvm-3.3. Бенчмарк стандартный - SPEC2000. Интересно, что автор делал замеры только на x86_64 и ARM, т.е. x86 разрядности 32 считает не интересной (т.к. производительность программ для 64 битов выше). Для ARM замеры производились впервые. Не производились замеры fortran, и как следствие не тестировались плавающие вычисления т.к. у llvm нет фронтенда для фортрана, а использовать dragonegg слишком сложно.<br />
<br />
А теперь, собственно замеры.<br />
<br />
Скорость компиляции:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://vmakarov.fedorapeople.org/spec/2014/GLTime64.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://vmakarov.fedorapeople.org/spec/2014/GLTime64.png" height="284" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://vmakarov.fedorapeople.org/spec/2014/pcGLTime64.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="284" src="https://vmakarov.fedorapeople.org/spec/2014/pcGLTime64.png" width="640" /></a></div>
<br />
Размер сгенерённого кода пропущу, т.к. лично мне не особо интересно. Но он есть в оригинальном исследовании.<br />
<br />
Самое интересное. Производительность:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://vmakarov.fedorapeople.org/spec/2014/GLIScore64.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="508" src="https://vmakarov.fedorapeople.org/spec/2014/GLIScore64.png" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://vmakarov.fedorapeople.org/spec/2014/pcGLIScore64.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="510" src="https://vmakarov.fedorapeople.org/spec/2014/pcGLIScore64.png" width="640" /></a></div>
<br />
Исследования про ARM тоже публиковать не буду, их можно найти в оригинальном исследовании.<br />
<br />
Теперь к выводам, сделанным автором исследования:<br />
<ul style="text-align: left;">
<li>GCC из поколения в поколение показывает <b>устойчивое улучшение</b> производительности на x86-64. Производительность LLVM почти не поменялась.</li>
<li><b>LLVM-3.4</b> улучшает <b>скорость компиляции</b> в то время как<b> GCC-3.9 требуется больше времени</b> для лучшей кодогенерации при выключенном LTO. С другой стороны скорость компиляции в режиме <b>LTO была значительно увеличена в GCC-4.9</b>. И это важное достижение.</li>
<li>Разница между одними поколениями LLVM и GCC в целочисленных SPEC'ах на <b>x86-64</b> на данный момент составляет <b>6%</b> и <b>2%</b> соответственно без LTO и с ним (для точных цифр можно посмотреть в таблицы). Этот разрыв меньше чем в моём сравнении 2013 года, когда он составлял 8% и 3.5%. Я думаю, главной причиной является прогресс процессоров Intel. В 2013 году я использовал процессор, который был старше на 2 поколения (Sandy Bridge). Процессоры Intel стали лучше исполнять неоптимизированный код, другими словами они стали менее чувствительны к некоторым оптимизациям.</li>
<li>Для <b>ARM</b> GCC генерирует целочисленный код примерно на 10% лучше. Я верю, что GCC покажет себя лучше и на большинстве других, отличных от x86/x86-64 платформах. По крайней мере я видел схожие результаты на PPC.</li>
<li>Я думаю, что сообществу GCC следует уделять больше внимания улучшению качества кода для x86-64, т.к. производительность LLVM уже действительно близка к GCC.</li>
<li>Для улучшения производительности GCC нам нужны анализы, в которых другие компиляторы (LLVM или Intel ocmpiler) генерируют лучший код. К сожалению это работа на полную ставку для более чем одного человека, знакомого с основами компиляторостроения. Но если кому-нибудь интересно, я бы предложил проанализировать 186.crafty или 255.vortex в режиме LTO, где LLVM работает гораздо лучше чем GCC.</li>
<li>У меня хватило немного времени для анализа сгенрированного кода и поиска разницы в генерации. У меня сложилось впечатление, что у LLVM получше с разрывом зависимостей, с другой стороны GCC лучше справляется с удалением мёртвых записей. Другое отличае в том, что LLVM систематически использует регистры SSE для перемещения структур в памяти. GCC использует общие регистры для этого. Я затрудняюсь сказать какой способ лучше для современного процессора без дополнительных исследований, но код LLVM обычно получается меньше т.к. регистры SEE шире. Я проверил компиляторы Intel, он также использует регистры общего назначения для этих целей.</li>
</ul>
</div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com0tag:blogger.com,1999:blog-935622623924408531.post-36802202560647977512014-07-04T00:50:00.000+03:002014-07-04T00:50:03.124+03:00Не все перемещения на регистр одинаково полезны<div dir="ltr" style="text-align: left;" trbidi="on">
Словил забавную багу (а может и не багу) оптимизатора на казалось бы простеньком примере:<br />
<br />
<code>$ cat t.c<br />
#include <stdio.h><br />
<br />
typedef double t;<br />
<br />
t a = 0.5;<br />
t b = 0.23;<br />
t c = 6.0;<br />
<br />
int main (void)<br />
{<br />
t e, f;<br />
<br />
e = a - b;<br />
f = e * c;<br />
<br />
printf ("%.30f\n", f);<br />
return 0;<br />
}<br />
<br />
$ gcc t.c -O2 && ./a.out && gcc t.c && ./a.out <br />
1.619999999999999884536805438984<br />
1.620000000000000106581410364015<br />
</code><br />
<br />
Такой эффект наблюдается с совершенно бородатых времён (ещё по-моему gcc-2.x такое выдавал). И наблюдается он только на 32-битном x86.<br />
<br />
Сначала я думал, что это вина gcc, особенно с учётом того что llvm отрабатывает нормально. Но я эту багу <a href="https://gcc.gnu.org/bugzilla/show_bug.cgi?id=11040">нашёл</a> в багзилле, суть вот в чём. Без оптимизации компилятор хранит double переменные на стеке, там они занимают положенные 64 бита и всё хорошо. А при включённой оптимизации он перемещает значение на плавающий регистр, который имеет размер 80 бит.<br />
<br />
На вики есть <a href="http://en.wikipedia.org/wiki/Extended_precision#Need_for_the_80-bit_format">объяснение</a> почему именно 80 бит. Это связано с тем, что для удвоения точности экспоненту нужно увеличить на 1 бит и получить 12 бит, а мантиссу до 77 вместо старых 55 бит. Решение довольно спорное, т.к. оно не портируемое. Т.е. программе для того чтобы результат в разных режимах на разных машинах выдавал одинаковый результат нужно весьма извращаться. Иногда помогает ключ <span style="font-family: "Courier New",Courier,monospace;">-ffloat-store</span>, который запрещает хранить плавающие значения на регистрах.<br />
<br /></div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com2tag:blogger.com,1999:blog-935622623924408531.post-72677664422397152772014-05-23T23:19:00.000+03:002014-05-23T23:19:38.820+03:00Векторизация в компиляторе Эльбруса<div dir="ltr" style="text-align: left;" trbidi="on">
Наткнулся на старый <a href="http://thedeemon.livejournal.com/49226.html">пост</a> про автовекторизацию. Если в кратце, то автор жаловался на то, что компиляторы не могут ничего сделать даже на простейших примерах. Я просто не мог не проверить как те простейшие примеры отработают на Эльбрусе :)<br />
<a name='more'></a>Итак, у нас есть Эльбрус-2С+:<br />
<br />
<code>
$ cat /proc/cpuinfo<br />processor : 0<br />vendor_id : E2K MACHINE<br />cpu family : 4<br />model : 20255552<br />model name : Elbrus-e2k-e2c+<br />revision : 1<br />cpu MHz : 496.580<br />
L1 cache size : 64 KB<br />L1 cache line length : 32 bytes<br />L2 cache size : 1024 KB<br />L2 cache line length : 64 bytes<br />
...</code>
<br />
<br />
Есть компилятор Эльбруса:<br />
<br />
<code>$ gcc -v<br />lcc:1.18.07:Oct-19-2013:e2k-<wbr></wbr>2c+-linux</code>
<br />
<br />
Да, на Эльбрусах команда gcc вызывает нативный компилятор :) Сделано для упрощения портирования софта. Нет, у нас не используется gcc.<br />
<br />
Берём первый пример:<br />
<br />
<code>#include <stdio.h><br />int main(int argc, char* argv[])<br />{<br /> short a[256] __attribute__ ((aligned(16)));<br /> short b[256] __attribute__ ((aligned(16)));<br /> for(int i=0;i<256;i++) {<br /> a[i] = i & 1;<br /> b[i] = i & 3;<br /> }<br /> int mn = 100500;<br /> for(int n=0;n<10000000;n++) {<br /> int sum = 0, j=0;<br /> for(int y=0;y<16;y++)<br /> for(int x=0;x<16;x++) {<br /> short v = a[j] - b[j];<br /> sum += v*v;<br /> j++;<br /> }<br /> mn = mn < sum ? mn : sum;<br /> }<br /> printf("%d\n", mn);<br /> return 0;<br />}</code><br />
<br />
И сразу результаты:<br />
<br />
<code>$ gcc madd.cpp -o madd.out<br />
$ time ./madd.out<br />512<br /><br />real 1m22.307s<br />user 1m21.990s<br />sys 0m0.040s<br />
<br />
<br />
$ gcc madd.cpp -O3 -fno-vect -o madd_no_vect.out<br />
$ time ./madd_no_vect.out<br />512<br /><br />real 0m6.315s<br />user 0m6.280s<br />sys 0m0.010s<br />
<br />
$ gcc madd.cpp -O3 -o madd_vect.out<br />
$ time ./madd_vect.out<br />512<br />
<br />real 0m1.731s<br />
user 0m1.700s<br />sys 0m0.020s</code>
<br />
<br />
Разница между режимами компиляции впечатляющая. В моём g++-4.8.2 на машине Core i5 неоптимизированная версия исполняется 11.475 сек., оптимизированные 1.663 сек. и 1.662 сек. соответственно.<br />
<br />
Во-первых разница во времени показывает на сколько важен для Эльбрусов хороший компилятор. А как Вы уже догадались он чертовски хорош ;) Собственно, результаты говорят сами за себя. Векторизация отлично отработала и дала ускорение более чем в три раза (по сравнению с -O3 без векторизации).<br />
<br />
Второй пример:<br />
<br />
<code>#include <stdio.h><br />int main(int argc, char* argv[])<br />{<br /> short a[256] __attribute__ ((aligned(16))); <br /> short b[256] __attribute__ ((aligned(16)));<br /> for(int i=0;i<256;i++) {<br /> a[i] = i & 1;<br /> b[i] = i & 3;<br /> }<br /> int mn = 100500;<br /> for(int n=0;n<10000000;n++) {<br /> int sum = 0;//, j=0;<br /> for(int j=0;j<256;j++) {<br /> short v = a[j] - b[j];<br /> sum += v*v;<br /> }<br /> mn = mn < sum ? mn : sum;<br /> }<br /> printf("%d\n", mn);<br /> return 0;<br />}</code>
<br />
<br />
Выдал такие результаты:<br />
<br />
<code>$ gcc madd_oneloop.cpp -o madd_oneloop.out<br />-bash-4.2$ time ./madd_oneloop.out<br />512<br /><br />real 1m18.312s<br />user 1m18.080s<br />sys 0m0.020s<br />
<br />
$ gcc madd_oneloop.cpp -O3 -fno-vect -o madd_oneloop_no_vect.out<br />
$ time ./madd_oneloop_no_vect.out<br />
512<br /><br />real 0m1.001s<br />user 0m0.970s<br />sys 0m0.020s<br />
<br />
$ gcc madd_oneloop.cpp -O3 -o madd_oneloop_vect.out<br />
$ time ./madd_oneloop_vect.out<br />512<br /><br />real 0m1.000s<br />user 0m0.980s<br />sys 0m0.010s</code>
<br />
<br />
Что интересно, векторизация в нём реально применилась, но код и без неё неплохо соптимизировался.<br />
<br />
Даже не знаю какие выводы из всего этого можно сдлать. Просто для сведения - компилятор Эльбруса имеет крутую векторизацию.<br />
<br />
PS. На самом деле по таким синтетическим примерам судить о компиляторе нельзя, т.к. в реальной жизни всё гораздо сложнее. Чтобы проверить компилятор нужны как минимум специальные бенчмарки, и то это тоже не всегда показатель.<br />
<br /></div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com2tag:blogger.com,1999:blog-935622623924408531.post-17710239185254719852014-05-01T13:35:00.000+03:002014-05-01T20:34:15.794+03:00Про оптимизации, безопасность и "нормальные языки".<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
В конце 2013 года вышла довольно интересная статья "Towards Optimization-Safe Systems: Analyzing the Impact of Undefined Behavior" про то как компиляторы, применяя различные агрессивные оптимизации убирают проверки безопасности. Хочу поделиться своими мыслями на этот счёт.<br />
<a name='more'></a>Во-первых часть той статьи я <a href="https://github.com/alexanius/translations/blob/master/Towards%20Optimization-Safe%20Systems:%20Analyzing%20the%20Impact%20of%20Undefined%20Behavior/impact.markdown">перевёл</a> на русский (остаток переводить лень, но если кому-то нужно готов продолжить). Команда, написавшая эту статью весьма интересная - у них есть и более ранние работы, посвящённые неопределённому поведению и ошибкам, к которым оно приводит. Очень советую почитать, вот пара названий их статей: "Linux kernel vulnerabilities:<br />
State-of-the-art defenses and open problems", "Undefined Behavior: What Happened to My Code?", по ссылкам там можно и массу других любопытных статей найти.<br />
<br />
Если кому-то лень читать статью целиком, то в двух словах суть сводится к следующему: если Вы пишете код, который приводит в UB (undefined behaviour), то готовьтесь к неприятным последствиям. Например:<br />
<br />
<code>
char *buf = ...;<br />char *buf_end = ...;<br />unsigned int len = ...;<br />if (buf + len >= buf_end)<br /> return;<br /> /* len too large */<br />if (buf + len < buf)<br /> return;</code></div>
<div dir="ltr" style="text-align: left;" trbidi="on">
</div>
</div>
<br />
Здесь вторая проверка будет удалена, т.к. компилятор уверен, что <span style="font-family: "Courier New",Courier,monospace;">buf + len</span> не может быть меньше <span style="font-family: "Courier New",Courier,monospace;">buf</span>, ведь в противном случае было переполнение, приводящее к UB, а программист умный и ну никак не мог привести свою программу в такое состояние.<br />
<br />
Понятно, что таким образом написана гигантская часть кода и что большинство людей даже не подозревает о таком поведении компилятора. Очень забавно было читать <a href="http://gcc.gnu.org/bugzilla/show_bug.cgi?id=30475">истерику</a> одного программиста, который столкнулся с таким поведением.<br />
<br />
Как компиляторщик, который сам разрабатывает подобные оптимизации могу сказать, что <strike>программисты сами виноваты, надо было стандарт читать</strike> здесь довольно глубокая концептуальная проблема. Стандарт Си, например, содержит массу мест, где в двух строках довольно расплывчатым языком описывается какое-либо недопустимое действие. Об этой паре строчек знает полтора человека, которые потерев руки коммитят в компилятор оптимизацию, использующую данную возможность. А потом кто-то случайно обнаруживает разное поведение на первый взгляд корректного кода в режимах -O0 и -O3. Отлаживать такие штуки - адский ад. Поэтому возникает вопрос - а стоит ли делать оптимизации, которые ломают вообще весь софт ради пары процентов (это если повезёт) прироста производительности?<br />
<br />
Формально, конечно, правы компиляторщики. И более того, разработчики компилятора обычно делают warning'и, которые предупреждают об опасности. Более того в gcc-4.9 появился специальный санитайзер: <code>-fsanitize=undefined</code>, отлавливающий потенциально опасные конструкции. Да, понятно, что все опасные случаи, которыми воспользуется компилятор отловить невозможно, но к сожалению, не все компилят даже с ключом <span style="font-family: "Courier New",Courier,monospace;">-Wall</span><span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;">, про</span> -Werror</span> даже говорить не приходится, поэтому программисты в любом случае попадают в истории.<br />
<br />
Мне как-то высказывали <a href="http://eax.me/standards/#comment-1113595045">мнение</a>, что в "нормальных языках" такая ситуация невозможна в принципе. Я посмотрел в стандарт Haskell - в соответствии с ним компилятор имеет полное право убрать вторую проверку из нашего примера, хотя на практике мне этого добиться не удалось. Думаю, компилятор до этого ещё не дорос ;) Я также посмотрел в стандарт Erlang, но там вообще ничего про переполнение не сказано и хотел посмотреть в стандарт OCaml, но как бы это сказать... Нет у OCaml стандарта! Да, они в качестве стандарта используют книгу по OCaml, но понятно, что нельзя сделать что-либо серьёзное по документу который обновляют раз в несколько месяцев.<br />
<br />
Лично мне кажется, что такие оптимизации делать нужно в любом случае, ведь никогда не знаешь когда 2% ускорения превратятся в 20% из-за наложения эффекта нескольких оптимизаций. Вопрос безопасности... Ну для начала пусть программисты перестанут игнорировать предупреждения компилятора, а там уже о чём-то рассуждать можно будет. Если нужна сверх надёжность, либо -O0, либо качественный код и качественное тестирование. К сожалению, в российских гос. шарагах хорошие программисты - редкость и там проблемы возникают не из-за коварного компилятора, а из-за банального говнокода.</div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com0tag:blogger.com,1999:blog-935622623924408531.post-56338808080636537822014-03-28T21:06:00.000+03:002014-03-29T16:32:27.936+03:00Эльбрусы. Новые и не очень.<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
Недавно сгонял на выставку "Новая электроника - 2014", где меня целенаправленно интересовал стенд МЦСТ. Особенностью этого стенда было то, что на нём представлен новый процессор "Эльбрус-4С". Он нём и ещё о паре забавных железок расскажу под катом.<br />
<br />
<a name='more'></a><br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzI8mATp0NLWb1HuFP-2M5oXNSIDl03TfVbTLXPLkyfgqXdxiK60PiTjyxZK1MT1cAuVy7gWFFhjhBBuASxor004Kv3BVKyGX8D3MKDWT0LFAvzsrx_ENO0lmqYG8EXXHn0S-05Wapwuff/s1600/SAM_3263.JPG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzI8mATp0NLWb1HuFP-2M5oXNSIDl03TfVbTLXPLkyfgqXdxiK60PiTjyxZK1MT1cAuVy7gWFFhjhBBuASxor004Kv3BVKyGX8D3MKDWT0LFAvzsrx_ENO0lmqYG8EXXHn0S-05Wapwuff/s1600/SAM_3263.JPG" height="240" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Стенд МЦСТ</td></tr>
</tbody></table>
На стенде были уже вышедшие в 2013 году моноблок «КМ4-Эльбрус», настольный компьютер «Монокуб-РС» и защищённый ноутбук НТ-ЭльбрусS, и новинка - процессор Эльбрус-4С.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTKwZK5U3aw-0bD_3jjj8TkPlThBDMtnMgBSGc-1ZEzr0WXYCZR2k0p-Kk5F-s6rEzeVskYlIYt1agpQlspRXx8O4fvPKl9oAtHdkcCuUw1dapvJlOLs-onsTHEmuh5kgoVPKu6wxJXlBP/s1600/SAM_3247.JPG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTKwZK5U3aw-0bD_3jjj8TkPlThBDMtnMgBSGc-1ZEzr0WXYCZR2k0p-Kk5F-s6rEzeVskYlIYt1agpQlspRXx8O4fvPKl9oAtHdkcCuUw1dapvJlOLs-onsTHEmuh5kgoVPKu6wxJXlBP/s1600/SAM_3247.JPG" height="240" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Эльбрус-4С</td></tr>
</tbody></table>
На фото - сама новинка. Забавно, что её внутреннее название "Эльбрус-2S". Такая разница в названиях временами выносит мозг. Публично этот процессор представлен впервые, но пока что он находится только на
стадии испытаний. Я подготовил небольшое сравнение с Эльбрус-2С+, который
стоит в «Монокуб-РС»:</div>
<table>
<tbody>
<tr><th></th><th>Эльбрус-2С+</th><th>Эльбрус-4С</th></tr>
<tr><td>Тех. процесс, нм</td><td>90</td><td>65</td></tr>
<tr><td>Тактовая частота, МГц</td><td>500</td><td>800</td></tr>
<tr><td>Число ядер CPU</td><td>2 + 4 DSP</td><td>4</td></tr>
<tr><td>Пиковая производительность, 64 разряда, Gflops</td><td>8</td><td>25</td></tr>
<tr><td>Пиковая производительность, 32 разряда, Gflops</td><td>16</td><td>50</td></tr>
<tr><td>Кэш 1 уровня (на ядро, данных + команд), КБ</td><td>64 + 64</td><td>64 + 128</td></tr>
<tr><td>Кэш 2 уровня, КБ</td><td>2 * 1024</td><td>4 * 2048</td></tr>
<tr><td>Количество процессоров в системе, шт.</td><td>до 4</td><td>до 4</td></tr>
<tr><td>Пропускная способность канала межпроцессорного обмена, ГБ/с</td><td>3 * 4</td><td>3 * 16</td></tr>
<tr><td>Скорость обмена с памятью, ГБ/с</td><td>12.8</td><td>38.4</td></tr>
<tr><td>Средняя рассеиваемая мощность, Вт</td><td>25</td><td>45</td></tr>
<tr><td>Количество транзисторов, шт.</td><td>368 млн.</td><td>986 млн.</td></tr>
</tbody></table>
<br />
Тут стоит отметить, что Эльбрус-4С имеет ту же самую архитектуру, что и Эльбрус-2С+, относящуюся к классу VLIW архитектур. На базе нового процессора МЦСТ
планирует выпустить плату с форм-фактором microATX (с одним процессором)
и серверную материнскую плату с 4 процессорами.<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFc6czuxRZJHWCPWG7M97rTssmKEKVyGa2xV13QpxMyrK3qa1xf3S6G37I5DfYdihbsDQglOvpdC0GZUMuh9RTLGFHjHc7GS2voeTusfx_K8UrtGmXe_N0BBuCFydh5VjJuDo4IIOC66GL/s1600/SAM_3265.JPG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFc6czuxRZJHWCPWG7M97rTssmKEKVyGa2xV13QpxMyrK3qa1xf3S6G37I5DfYdihbsDQglOvpdC0GZUMuh9RTLGFHjHc7GS2voeTusfx_K8UrtGmXe_N0BBuCFydh5VjJuDo4IIOC66GL/s1600/SAM_3265.JPG" height="240" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Разные процессоры, выпускаемые МЦСТ</td><td class="tr-caption" style="text-align: center;"><br /></td><td class="tr-caption" style="text-align: center;"><br /></td><td class="tr-caption" style="text-align: center;"><br /></td></tr>
</tbody></table>
А теперь посмотрим на хит прошлого сезона - Монокубы и моноблоки. Это моноблоки на базе процессоров Эльбрус-2С+.<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0MOB3C2YxGKElwC6xyFz8kjHwkmzk03d2p_HQhGFy5Nv7WPrsNAy8_rWtJiljzOvJq3ZPONiclxhX_NdrvTxPRqkuuIFfhyphenhyphen6KfnB0Slq__YZilzbp2NQaYlB7QT7nSXRaOa304IKs1eY4/s1600/SAM_3237.JPG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0MOB3C2YxGKElwC6xyFz8kjHwkmzk03d2p_HQhGFy5Nv7WPrsNAy8_rWtJiljzOvJq3ZPONiclxhX_NdrvTxPRqkuuIFfhyphenhyphen6KfnB0Slq__YZilzbp2NQaYlB7QT7nSXRaOa304IKs1eY4/s1600/SAM_3237.JPG" height="240" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Моноблок «КМ4-Эльбрус»</td></tr>
</tbody></table>
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyQMugopgo7cao48oyuMCJ2T4QJX4QJ8cfJK1FnuhM113t9UkLaFQmd0LdOPLJV9zbq6bEV_MF0ZKc3J0-sq3XUNaghoIa-RuF-1CTPJenKt5qXZx1PZbFABrFX4XP9zxVtIDDVJoQrNa5/s1600/SAM_3238.JPG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyQMugopgo7cao48oyuMCJ2T4QJX4QJ8cfJK1FnuhM113t9UkLaFQmd0LdOPLJV9zbq6bEV_MF0ZKc3J0-sq3XUNaghoIa-RuF-1CTPJenKt5qXZx1PZbFABrFX4XP9zxVtIDDVJoQrNa5/s1600/SAM_3238.JPG" height="240" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Он же, поближе</td></tr>
</tbody></table>
На фото - моноблок. Это обычный компьютер с линуксом на борту. Подходит для офисной деятельности, говорят на нём даже OpenArena идёт. Сам не видел, но обещали показать.<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgcDDNMIAYcgWeJwUKq17I_U583SAC_ExbFEafFWRJbry-ro1FoW4XmGeYXbzaHF0JZb2-4QjJErNBDZFDs6wn7Fnc-KQszVEQa4DsKsELcQF5ZxjbG-q7cO-atJvd9w4xRlDPBNWy-2Bx/s1600/SAM_3241.JPG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgcDDNMIAYcgWeJwUKq17I_U583SAC_ExbFEafFWRJbry-ro1FoW4XmGeYXbzaHF0JZb2-4QjJErNBDZFDs6wn7Fnc-KQszVEQa4DsKsELcQF5ZxjbG-q7cO-atJvd9w4xRlDPBNWy-2Bx/s1600/SAM_3241.JPG" height="240" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Под капотом моноблока</td></tr>
</tbody></table>
А вот так выглядит монокуб (коробочка, на которой стоит описание):<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPP_9s2YnnAZHXK9h02HIX3T4ivHJzhl8Roj9UZLTCMU9H0HHc1MrqWX9s70iQintMrftwwUKxeMu7pf-2jNb_FT-YpNW1gSRQRCMzTMrl6DWnALSr7Oux7Toqlxd8klNh_FanNxnnonCE/s1600/SAM_3251.JPG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPP_9s2YnnAZHXK9h02HIX3T4ivHJzhl8Roj9UZLTCMU9H0HHc1MrqWX9s70iQintMrftwwUKxeMu7pf-2jNb_FT-YpNW1gSRQRCMzTMrl6DWnALSr7Oux7Toqlxd8klNh_FanNxnnonCE/s1600/SAM_3251.JPG" height="240" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Монокуб с... Windows о_О</td></tr>
</tbody></table>
Да, это действительно Windows. Страшно, не правда ли ;) Фишка в том, что в МЦСТ разрабатывается технология бинарной трансляции, при которой команды для x86 машин транслируются на команды Эльбруса. Причём это не совсем виртуальная машина, т.к. запуск производится на голом железе.<br />
<br />
Если говорить в целом про Монокуб/Моноблок, то можно отметить, что это линукс собственной сборки МЦСТ (очевидно, т.к. собственная архитектура). Имеет debian-based пакетный менеджер и собственный компилятор если в репозитории чего-то нет. А т.к. там нет почти ничего, компилятор вам понадобится, и вам останется молиться чтобы в пакете не было ассемблерных вставок :) <br />
<br />
Довольно часто звучит вопрос "Где это счастье можно приобрести?". Со слов официального представителя - почти ни где. Цена одной модели довольно высока (больше 100000 руб.), продаётся организациям под конкретные проекты. При партии от 100 машин можно получить скидку. Для простых смертных будет доступно когда "удастся снизить отпускную цену", чтобы это ни значило.<br />
<br />
Ещё одна шикарная железка - "Носимый терминал НТ-ЭльбрусS"<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEga4FmFhTc6RilG81CFAxz3c-GtvrUGpL5M0SnSlvHMyLQtay3n4IoD2c-jYfh_kSqHwQMzlOvEQnxaVw8iEE5Jzc_HQDo35m2qcc0GFoMaI-GwYw3_k_4VMvRq4889ExM-K6872i4xSzVg/s1600/SAM_3243.JPG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEga4FmFhTc6RilG81CFAxz3c-GtvrUGpL5M0SnSlvHMyLQtay3n4IoD2c-jYfh_kSqHwQMzlOvEQnxaVw8iEE5Jzc_HQDo35m2qcc0GFoMaI-GwYw3_k_4VMvRq4889ExM-K6872i4xSzVg/s1600/SAM_3243.JPG" height="320" width="240" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Носимый терминал НТ-ЭльбрусS</td></tr>
</tbody></table>
ЭТО представляет из себя металлический ящик весом до 10кг. с резиновыми насадками по углам. Его работа гарантируется даже если в 50 градусную жару подплыть к
противнику под водой и обезвредить его ударом этого ноутбука по голове.<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfzPK3EtjsMRZQ4zR-6OOR3g9iyPPSgk-col2hjV9zOlq2ry8twVNB1CPeAkKi0DGWk7kIH3nCgPXzE7cc8exFga13ImpEOfOAufF7TdlWKnpqeT2aumQdTeyM4i6kqf4dnmP-ADYP8Qgy/s1600/SAM_3260.JPG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfzPK3EtjsMRZQ4zR-6OOR3g9iyPPSgk-col2hjV9zOlq2ry8twVNB1CPeAkKi0DGWk7kIH3nCgPXzE7cc8exFga13ImpEOfOAufF7TdlWKnpqeT2aumQdTeyM4i6kqf4dnmP-ADYP8Qgy/s1600/SAM_3260.JPG" height="240" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Клавиатура НТ-ЭльбрусS</td></tr>
</tbody></table>
Забавная клавиатура - по сути просто резиновая настилка, чтобы вода не могла проникнуть внутрь.<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHgmf5Suy93JXvL4LBSr997mY4qHEfBAlCMyXrt9TQDlHtqRNGjAs4oNDSC5RIx_Zl4Ieu7rbuGIOSy6m0RzgiOzdicea1NwJhNtJyCiHhAdMDBRfd8pLsTI7bJFtPMn041M_BZq0tmS8i/s1600/SAM_3262.JPG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHgmf5Suy93JXvL4LBSr997mY4qHEfBAlCMyXrt9TQDlHtqRNGjAs4oNDSC5RIx_Zl4Ieu7rbuGIOSy6m0RzgiOzdicea1NwJhNtJyCiHhAdMDBRfd8pLsTI7bJFtPMn041M_BZq0tmS8i/s1600/SAM_3262.JPG" height="240" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">НТ-ЭльбрусS - вид сбоку</td></tr>
</tbody></table>
Интересно как они решают проблему попадания воды в разъёмы. Если посмотреть на правый верхний угол ноутбука, то резиновая накладка отсоединилась. У меня есть версия, что ноутбук работает в двух режимах - летальный и не летальный. Для второго обязательно наличие резиновых накладок тогда можно разгонять демонстрантов. При летальном режиме резиновые накладки снимаются и можно вести боевые действия.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFlq5Q9fTcARRXcDJexOzjLnpzZdi3wO7Jt8Z2c1WIA_QHUau3CY7vG4HUTZ6VooOVzWtYOqqGCk7LRrppGH4B5tVl-4jrDke8mLtcJ1j7frFMTBxKA4MexQjKSCshZcBwmY1OgAAiXPZI/s1600/SAM_3257.JPG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFlq5Q9fTcARRXcDJexOzjLnpzZdi3wO7Jt8Z2c1WIA_QHUau3CY7vG4HUTZ6VooOVzWtYOqqGCk7LRrppGH4B5tVl-4jrDke8mLtcJ1j7frFMTBxKA4MexQjKSCshZcBwmY1OgAAiXPZI/s1600/SAM_3257.JPG" height="240" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">НТ-ЭльбрусS рядом с HTC-Wildfire</td></tr>
</tbody></table>
Как-то так было на стенде. Это выставка не последняя, о следующих я постараюсь предупредить заранее, там тоже будет весело.<br />
<br />
PS. Чтобы избежать недопонимания - этот пост основан на базе поста для хабра, который модераторы не пропустили. Причина неизвестна.<br />
PPS. Хотя я и являюсь сотрудником МЦСТ, на выставке я был как частное лицо, и данный пост (как и все в этом блоге) является отражением моей личной позиции.<br />
<br /></div>
alexaniushttp://www.blogger.com/profile/09745266961115834144noreply@blogger.com32