- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
LXF106:Шаблоны в Sun Studio
Материал из Linuxformat.
Sun Studio |
---|
|
Содержание |
Выгода от ассемблера
- ЧАСТЬ 1 С точки зрения обывателя, все компиляторы одинаковы – они просто переводят программы в машинный код; специалист же с ходу назовет вам десяток отличий. Станислав Механошин расскажет об одном из них – встраиваемых шаблонах.
Но прежде, чем вдаваться в частности, имеет смысл узнать, что же такое Sun Studio. Сергей Пикалев, начальник отдела разработки ПО в Санкт-Петербургском офисе Sun Microsystems, любезно согласился ответить на все наши вопросы.
LXF: Название Sun Studio невольно наводит на мысли об интегрированной среде разработки: редактор с подсветкой синтаксиса, автодополнение, рефакторинг и тому подобные вещи, упрощающие жизнь программиста. А что это такое на самом деле?
СП: На мой взгляд, в Sun Studio можно выделить три основных части: компиляторы для языков C/C++ и Fortran, инструментарий (отладчик, средство поиска ошибок и прочее), и собственно интегрированную среду разработки (IDE). Речь не только об удобствах – это самостоятельный пакет, включающий все, что требуется для разработки приложений.
LXF: IDE, надо полагать, базируется на NetBeans?
СП: Конечно.
LXF: Вы имеете в виду связку из NetBeans и подключаемого модуля C/C++ Native Development (CND), или же есть какие-то отличия?
СП: Даже с установленным CND, NetBeans содержит большое количество компонентов для Java-разработки, идущих в комплекте с данной IDE. В Sun Studio их нет – мы удалили некоторые специфичные для Java вещи, поскольку для программистов на C/C++ они, очевидно, не нужны.
LXF: И что, если я – фанат Emacs или Vi, то могу установить только утилиты командной строки: компилятор, компоновщик, библиотеки и набирать код в привычном редакторе?
СП: Технически – да. Sun Studio имеет модульную структуру и вы можете выбирать, какие пакеты установить. Однако, лицензионное соглашение требует, чтобы данный продукт поставлялся единым пакетом, поэтому с сайта все равно придется загрузить один архив объемом около 350 МБ. Кстати, если говорить о компиляторах, то они помещаются примерно в 30 МБ. Мы хотели бы изменить то, как поставляется Sun Studio, и работа в этом направлении ведется, но как вы понимаете, дела, в которых замешаны юридические соглашения, быстро не делаются.
LXF: Конечно. Кстати, раз уж мы заговорили о лицензии: на каких условиях распространяется Sun Studio? Известно, что Intel C Compiler, например, можно получить бесплатно, но при этом нельзя использовать в «коммерческих целях», а под это определение подпадает решение студентом или аспирантом научных задач или проведение практических занятий преподавателем.
СП: 30 декабря 2005 года компания Sun Microsystems анонсировала проект Red October [Красный октябрь], в соответствии с которым ряд ее продуктов, в том числе, средства разработки, в одночасье стали доступными безо всякой платы: «Free for any use» [Бесплатно для любых целей]. В случае Sun Studio прибыль поступает от поддержки: если некоторая организация желает, чтобы мы реализовали конкретную функциональность или исправили определенную ошибку – она может заключить с нами контракт. В ином случае направление развития Sun Studio будет задаваться стратегией и приоритетами Sun Microsystems.
LXF: Да, это весьма похоже на модель, применяемую в мире Open Source. Но ведь Sun Studio – это не свободное ПО? «Free as in 'beer'», но не «Free as in 'speech'» ?
СП: Действительно, исходные тексты Sun Studio закрыты – пока. Но, как известно, наша компания имеет долгую историю публикации своих наработок и, могу вам сказать, в планах на будущее Sun Studio значится на одной из первых позиций.
LXF: Это не может не радовать – два полноценных свободно распространяемых набора компиляторов лучше, чем один. Кстати, насколько мне известно, Sun Studio заявляет совместимость с GCC. Это действительно так?
СП: Да, все верно – Sun Studio совместима с GCC.
LXF: И что, я могу взять, переопределить переменную $CC и собрать с его помощью ядро Linux?
СП: В принципе – да. Мы не выполняли всеобъемлющего тестирования, но ядро, собранное в наших лабораториях при помощи Sun Studio, неплохо загружалось и работало. Правда, на практике эта задача может оказаться не из легких: в исходных текстах Linux уйма директив условной компиляции, и многие из них требуется подправить определенным образом; после этого все компилируется. Иными словами, простого переопределения $CC пока не достаточно. Помимо того, GCC и Sun Studio по-разному реализуют различные специальные возможности, скажем, выравнивание (align). GCC трактует соответствующую директиву как обязательную, Sun Studio же стремится следовать данному указанию до тех пор, пока оно не входит в противоречие с оптимизатором кода. Если выясняется, что выравнивание мешает процедуре оптимизации – оно будет нарушено. В большинстве случаев это не создает проблем, однако, если программа жестко завязана на расположение байтов в памяти, могут быть трудности.
LXF: Компилятор – это, конечно, хорошо. А какие еще инструменты есть в Sun Studio? Вы упомянули отладчик – с ним все более менее ясно, но что такое «средство поиска ошибок»? И где профайлер?
СП: Профайлер, разумеется, есть – просто список инструментов, который я озвучил в начале беседы, никоим образом не является всеобъемлющим. А средство поиска ошибок, помимо прочего, помогает справиться с такими неприятностями, как взаимные блокировки и состояния гонки. Создатели распределенных приложений с ними хорошо знакомы.
LXF: Вы имеете в виду традиционные многопоточные приложения или что-то более серьезное: MPI, PVM, OpenMP?
СП: Смотря что понимать под традиционными многопоточными приложениями.
LXF: Ну, например, использующие pthreads...
СП: Да, pthreads поддерживается. Помимо них, поддерживаются Solaris Threads, OpenMP и MPI, причем Sun Studio – пожалуй, единственная среда, в которую интегрирована возможность отладки всех типов многопоточных приложений одновременно.
LXF: Занятно. А в чем еще силен Sun Studio?
СП: У нас очень хороший оптимизатор для процессоров AMD.
LXF: То есть Sun Microsystems пишет компиляторы для Advanced Micro Devices?
СП: Да, Intel создает средства разработки для своих процессоров самостоятельно, а AMD предпочитает передавать спецификации нам. Это своего рода аутсорсинг.
LXF: А насколько хорош оптимизатор Sun Studio? Наверное, существуют какие-нибудь тесты, сравнения...
СП: Да, существует такая организация как Standard Performance Evaluation Corporation, ее сайт расположен по адресу http://www.spec.org. Она предлагает наборы тестов для измерения производительности генерируемого кода. На этом же сайте регулярно публикуются результаты сравнений разных компиляторов. И Sun Studio выглядят там очень неплохо.
LXF: Ну и напоследок, хотелось бы узнать, кто использует Sun Studio. GCC — де-факто стандарт в мире свободного ПО, Intel гордится, что СУБД Oracle собирается именно с помощью ICC, а какой туз в рукаве припасли вы?
СП: СУБД Oracle собирается и нашим компилятором, только для ОС Solaris. Oracle вообще выбирает лучшее там, где оно действительно лучшее. Я не уверен, что могу открывать секреты наших партнеров, поэтому не стану называть других наших клиентов. А вот вещь, о которой я могу упомянуть, говорит сама за себя. Как вы знаете, 22 января корпорации Sun Microsystems и Intel заключили партнерское соглашение, в рамках которого Intel будет продвигать ОС Solaris как критически важную операционную систему для серверов на базе процессоров Intel Xeon. Так вот, в рамках этого соглашения, оптимизирующие компиляторы для связки ОС Solaris – Xeon будем делать именно мы, путем добавления оптимизаций и для данного семейства процессоров.
Что ж, вооружившись этими сведениями, мы можем перейти к основной теме нашего материала – использованию встраиваемых шаблонов Sun Studio для повышения производительности
приложений.
Что это такое?
Многие компиляторы языков C/C++ предоставляют разработчику средства для использования встраиваемого ассемблера. В большинстве случаев это делается с использованием директив asm, _asm или __asm, включаемых в тело программы. Есть такая возможность и в Sun Studio 12. Однако, в данной статье мы поговорим о другом средстве использования встраиваемого ассемблера, предлагаемого компилятором Sun Studio – встраиваемых шаблонах (inline templates).
Идея встраиваемых шаблонов очень проста: вы описываете и вызываете функцию, а также создаете файл с ее ассемблером. При компиляции необходимо дополнительно передать компилятору имя данного файла, в результате чего ассемблерный код будет встроен по месту вызова. При этом требуется, чтобы код удовлетворял ABI [Application Binary Interface, соглашение о низкоуровневом интерфейсе между программой и ОС, библиотеками и различными компонентами программы] вашей платформы в части соблюдения конвенции вызова функций.
У встраиваемых шаблонов имеется ряд преимуществ перед встраиваемым ассемблером, что очерчивает разумную область их применимости. Перечислим некоторые из достоинств:
- Использование встраиваемых шаблонов, как правило, не требует изменения исходного кода программы, что в ряде случаев невозможно, а иногда – нежелательно;
- Там, где в исходном коде пришлось бы написать несколько препроцессорных условных выражений, с использованием встраиваемых шаблонов можно обойтись одним вызовом функции и несколькими разными файлами с ассемблером, специфичными для платформы;
- Шаблонами можно прозрачно заменить вызов почти любой функции (почему «почти», будет сказано ниже);
- Их использование не ограничено языками C и C++;
- Единожды написанный шаблон можно использовать повторно по имени, не копируя код напрямую или средствами препроцессора.
Очевидно, что должны иметься и недостатки. За три года использования данного средства я обнаружил два:
- Встраиваемые шаблоны менее стандартны, что с лихвой окупается возможностью написания функции, выполняющей тот же функционал для компиляторов, не поддерживающих такой возможности;
- При ABI, предусматривающем передачу аргументов через стек, параметры вызова будут переданы соответствующим образом, то есть с копированием в память. При использовании встраиваемого ассемблера этого можно избежать. Данное обстоятельство делает использование встраиваемых шаблонов наиболее эффективным на платформах с ABI, предусматривающих передачу параметров через регистры (например, x64).
Как ими пользоваться?
Небольшое замечание по использованию идентификаторов: имя функции, указанное в шаблоне, должно быть таким, каким его ожидает компилятор, то есть со всеми декорациями, присущими языку программирования. Для выяснения, как именно должно выглядеть имя функции, можно собрать проект без шаблона и посмотреть на ошибки компоновки, содержащие имена ненайденных символов, либо транслировать исходный файл в ассемблер при помощи опции -S и взглянуть на реальное имя вызываемой функции. В случае C++, для упрощения декорирования и совместного использования с модулями, написанными на языке C, можно использовать декларации вида:
#ifdef __cplusplus extern "C" { #endif extern void foo(); #ifdef __cplusplus } #endif
Предположим, вам необходимо встроить ассемблер вместо такого выражения:
(v << 8) | (v >> 24)
Напишем простой тест:
int test_rotate_byte_left(int v) { return (v << 8) | (v >> 24); }
Скомпилируем его:
cc -fast -m64 -S test-rotate-expr.c
И посмотрим, что получилось:
movl %edi,%eax ; загрузка аргумента shll $0x8,%eax ; сдвиг влево sarl $0x18,%edi ; сдвиг вправо orl %edi,%eax ; побитное ИЛИ ret
Предположим, вы хотите использовать единственную инструкцию ROL [оборот влево, при этом биты, вышедшие за границу операнда, появляются в младших позициях, – прим.ред.]. Что надо сделать? Во-первых, описать функцию и вызывать ее:
extern int rotate_byte_left(int v); int test_rotate_byte_left(int v) { return rotate_byte_left(v); }
Во-вторых, создать в отдельном файле с расширением .il ее шаблон с соблюдением требований ABI (мы пользуемся ABI x64, где первый параметр передается через регистр rdi, а целочисленное возвращаемое значение ожидается в регистре rax или, в случае 32-битного целого, как у нас – в eax):
.inline rotate_byte_left,0 roll $8,%edi movl %edi,%eax .end
В-третьих, вызвать компилятор, передав ему дополнительно имя шаблона:
cc -fast -m64 -S test-rotate.c rotate.il
Вот что получится в итоге:
movl %edi,%eax roll $010,%eax ret
Обратите внимание, что я не писал ret в конце шаблона – этого не требуется. Однако, если вы предполагаете у функции возвращаемое значение, то его следует поместить туда, где оно должно быть согласно ABI, в данном случае – в регистр eax. Если бы это было значение с плавающей точкой, то оно бы возвращалось в xmm0 или на вершине стека x87 в более старой конвенции вызова.
Последний параметр после имени функции (,0) – это размер ее аргументов в байтах; в настоящее время он не используется, хотя должен быть указан для обратной совместимости с компилятором Sun Workshop 5.0.
Также надо отметить, что хотя в данном примере в файле rotate.il определена лишь одна функция, их может быть несколько. Достаточно интересные примеры можно найти в библиотеке libm.il, поставляемой с компилятором Sun Studio для различных платформ (см. файлы, расположенные в каталогах prod/lib/* компилятора).
Замена определения функции
В случае с заменой определения функции возникает вопрос: будет ли ее конкретное использование действительно заменено шаблоном, или же будет вызвана сама функция? Очевидно, что со стопроцентной уверенностью на него может ответить дизассемблер, тем не менее, я приведу правила, по которым происходит выбор кода.
Встраивание шаблона выполняется компилятором на этапе трансляции модуля, то есть до компоновки всего приложения. Таким образом, если функция должна быть вызвана, но для нее определен шаблон – будет использован шаблон.
Исключением являются встроенные функции, известные компилятору. Например, если вы хотите переопределить код функции strcmp(), то это может не получиться. Компилятор «знает» ее, и если был использован ключ -xbuiltin=%all (либо явно, либо в результате применения макроопции -fast), требующий замены встроенных функций шаблонами, определенными самим компилятором, то будет выбран внутренний шаблон. Здесь можно использовать опцию -xbuiltin=%none для отмены действия, требуемого -fast (это нужно делать после собственно ключа -fast), либо отказаться от -xbuiltin=%all. К сожалению, синтаксис этой опции на сегодня не подразумевает выбор индивидуальных функций, и можно либо включить встраивание их всех, либо отключить его для указанной единицы трансляции.
В случае использования языка Fortran переопределение встроенных функций (таких, например, как ABS) становится невозможным в принципе. Здесь остается лишь использовать функцию с другим, нестандартным, именем.
То же самое происходит, если данный шаблон уже определен в стандартной библиотеке libm.il, а вы используете опцию -xlibmil либо явно, либо как часть -fast. Определение из libm.il имеет приоритет. В данном случае стоит скопировать требуемый libm.il и поправить определение функции, передав -xnolibmil и имя копии файла с шаблонами в качестве параметров компилятора. Альтернативным решением является отказ от -xlibmil и указание компилятору полного пути к соответствующей библиотеке libm.il, а также имени файла с собственным шаблоном после нее. В этом случае будет использовано последнее встретившееся определение.
Ограничения
Встраиваемые шаблоны по своей сути предназначены для (пере-) определения кода. Не все конструкции, допустимые в ассемблере, понимаются транслятором шаблонов. Некоторые директивы, такие как .align, допустимы к использованию с ограничениями на оптимизацию. Некоторые, однако, не могут быть применены вовсе. На самом деле, это относится к большинству псевдоопераций ассемблера.
В частности, встраиваемые шаблоны не могут использоваться для определения данных. Все соответствующие директивы, такие как .byte, .double и аналогичные не могут встречаться во встраиваемых шаблонах даже в том случае, если их использование допустимо в секции кода.
Это накладывает ограничение на использование шаблонов, которым требуются константы, хранящиеся в памяти. При наличии необходимости в таких константах приходится применять зачастую менее эффективную загрузку регистров из непосредственных операндов. Рассмотрим, например, определенную в prob/lib/amd64/libm.il функцию fabs():
.inline fabs,0 movq $0x7fffffffffffffff,%rax movdq %rax,%xmm1 andpd %xmm1,%xmm0 .end
Можно видеть, что первые две инструкции, включая крайне малоэффективную пересылку между регистром общего назначения rax и регистром xmm1, не потребовались бы, если бы было возможно определить константу в памяти.
Для обхода данного ограничения можно определить константу в самой единице трансляции как глобальную переменную. В этом случае шаблон выглядел бы так:
.inline fabs,0 andpd mask_sign,%xmm0 .end
где константа mask_sign должна быть определена в том же (и в каждом) исходном файле, из которого вызывается шаблон. Например:
#include <stdio.h> #include <math.h> #pragma align 16 (mask_sign) static const long mask_sign[2]={0x7fffffffffffffffl,0l}; void main() { printf(“%f\n”, fabs(-1.0)); printf(“%f\n”, fabs(-2.0)); }
Устранение этого ограничения в будущем может существенно расширить применимость встраиваемых шаблонов в Sun Studio.
И в заключение
Использование встраиваемых шаблонов Sun Studio – мощный инструмент, пригодный для оптимизации узких мест в программе, прозрачного переопределения действий и кода существующих функций, а также для быстрого испытания экспериментальных блоков кода. С его помощью можно также использовать инструкции, для которых отсутствуют как встроенные функции языка, так и возможность получить их в результате кодогенерации (такие как rtdsc, rol, либо prefetch в каком-либо конкретном месте программы).
Будучи менее стандартным, чем встраиваемый ассемблер, данный механизм, тем не менее, позволяет достичь оптимизации целевого кода с большей прозрачностью, а во многих случаях, и совместимостью, чем более стандартные решения. Использование ассемблерного кода, оформленного практически так же, как обычная функция на языке ассемблера, делает данный подход более унифицированным, позволяет меньше задумываться о взаимодействии написанного вручную ассемблера со сгенерированным автоматически. Однократное определение шаблона без использования препроцессора делает вероятность ошибки меньше, а отладку и сопровождение приложения – проще.
Творческий подход к использованию встроенных шаблонов создает практически неисчерпаемые возможности их применения теми разработчиками, которых не пугает перспектива написать несколько строк на ассемблере.