C - статьи

Ab initio

Сначала - вкратце о причинах, мотивациях, побуждениях и, разумеется, проблемах, приведших к появлению этого языка. Короче говоря: почему и зачем сдалось обычно крайне занятым западным буржуазным умам (читай - программистам) тратить свое драгоценное время, измеряемое в человеко-годах (man-year), на разработку очередного мало кому известного языка программирования электронно-вычислительных машин с приблизительным порядковым номером в "несколько_(десятков) тысяч_с_чем-то".
Ab initio

Для проектировщика/разработчика компилятора получение сносного машинного кода для различных аппаратных платформ представляется весьма сложной задачей. Так, одни пробуют использовать многоцелевой генератор кода из пакета компиляционных программных средств сторонних производителей. Но генераторы кода обладают чрезмерной (сравнительно) сложностью и достаточно непросты (опять же, относительно) в эксплуатации и, к тому же, они существенно сужают/ограничивают выбор языков реализации, что ставит их "вне закона" - или, как говорят в просторечии, делает их негодными для написания транслятора.
Другие "творцы" пробуют использовать язык С в роли портабельного ассемблера. К сожалению, С никогда не претендовал на такую роль - он язык программирования, а не язык ассемблера. Разница есть - и довольно основательная. C ограничивает реализацию в конкретном соглашении вызовов, делает невозможными вычисления целей переходов (targets of jumps), не обеспечивает поддержку сборки мусора (garbage collection) и владеет весьма скудной поддержкой исключений (exception handling) или отладки. (Приведенные утверждения не стоит понимать как критику C - просто этот язык разрабатывался для несколько других целей.)
Ab initio
Слова эти не были пустым звуком для гарвардской команды исследователей: Джона Диаса (John Dias), Рубена Олински (Reuben Olinsky), Кристиана Лайндига (Christian Lindig), Кэвина Ридуайна (Kevin Redwine), Нормана Рэмзи (Norman Ramsey), работавших в сотрудничестве с Саймоном Пэйтоном Джонсом (Simon Peyton Jones) из Microsoft Research в Кембридже,- которые успешно запустили в свет концепцию совершенно нового портабельного ассемблера.

Ad examplar:

/* program1.c-- */ import f, la; export g;
g (bits8x) { x = f (la+4); return (x); }
/* program2.c-- */ export f, la, lb; data {la: bits32{0}; lb: bits8{'\n'}; lc: bits8; } import g; f (bits8 x) {return (x);}
Здесь: компиляционная единица program1.c-- импортирует из program2.c-- имя процедуры f и имя метки la - оба используются в операторе вызова. Импорт успешен, поскольку оба f и la были экспортированы из program2.c--. Стоит отметить, что program1.c-- не сможет импортировать метку lc из program2.c-- - вследствие того что имя g импортировано program2.c--, но не используется, а lb экспортировано program2.c--, но не импортировано program1.c--.
Гибрид дает возможность ассоциировать высокоуровневый компилятор с ипостасью проинициализированных данных и диапазоном исходных областей C-- и определять альтернативные продолжения (alternate continuations) для вызовов подпрограмм, которые могут допускать исключения. Взятая на вооружение поддержка run-time представляет собой интерфейс (который определен в C), способный использовать сборщик мусора, механизм исключений и отладчик - для получения доступа как к высоко-, так и к низкоуровневой информации. Как результат: программа на C-- приостанавливается в безопасной точке. Высоко- и низкоуровневая информация согласовывается со значениями диапазонов C-- и общей нумерацией переменных. Наконец, оптимизатор C-- действует над ограничениями, так что, пока исполнение приостановлено, отладчик или сборщик мусора могут изменять значения локальных переменных, и процедурный вызов с альтернативными продолжениями может возвращаться более чем в одну позицию. Трехуровневое решение проблемы также обеспечивает требуемую поддержку для параллелизма.

Ad rem

C-- является абстракцией аппаратного обеспечения, вследствие чего "разговор" здесь ведется сугубо на низком уровне, а никак не на высоком. Железо обеспечивает вычисления, управление потоками, памятью и регистрами - C--, со своей стороны, "обещает" предоставить соответствующие абстракции.
Выражения и присваивания C-- служат абстракциями вычислений. Язык оснащен богатым комплексом вычислительных операторов, но работают они лишь с типами данных машинного уровня: байтами, словами ит.п. Абстракция выражения скрывает конкретную комбинацию машинных команд, требуемых для вычисления значений, а также машинные регистры, которые необходимы для хранения промежуточных результатов.
Управляющие конструкции goto (по отношению к C-- стоит пересмотреть свои убеждения, продиктованные чистой верой в непогрешимость Великого Эдсгера В. Дэйкстры (Edsger W. Dijkstra) насчет вреда данного оператора) и if - абстракции потока управления. Последняя скрывает машинные "коды условий"; расширенные условия представляют собой произвольные булевы (логические) выражения.
Обращение к памяти в языке-гибриде подобно таковому на машинном уровне - за исключением того лишь, что адреса, используемые в программах C--, могут быть произвольными выражениями. Эта абстракция скрывает ограничения режимов машинной адресации.
Переменные C-- выступают в роли абстракций регистров. C-- пытается, насколько это возможно, хранить все переменные в регистрах CPU, если же они туда не помещаются (ввиду превышения лимита дозволенного объема), то идут в основную память. Абстракция скрывает количество и метод использования стандартных регистров вычислительной системы.
Дополнительно в C-- присутствует также процедурная абстракция - характеристика эта выглядит как аппаратный примитив. Тем не менее, много процессорных архитектур представляют прямую поддержку для процедур, хотя сама природа этой поддержки широко варьируется (процедурный вызов или многочисленные команды сохранения данных в регистрах, регистровые окна, регистры связи, расширенные предсказания для команд возврата и т.п.). По причине такого разнообразия соглашения о вызовах и управлении стек-активизацией являются сильно архитектуро-зависимыми и трудно определяемыми.
Ad rem

Клиент C-- состоит из двух частей: компилятора с языка высокого уровня - front-end, и перенастраиваемого (retargetable) генератора кода - back-end. Предполагается, что исполняемая программа разделена на три составляющие, каждая из которых может размещаться в объектных файлах, библиотеках или их комбинациях.
Компилятор front-end транслирует высокоуровневый исходный текст программы в один или более модулей C--, которые после сепаратно транслируются компилятором C-- в генерируемый объектный код. Front-end входит в front-end run-time систему, включающую сборщик мусора, обработчик исключений и другие средства, которые необходимы исходному языку. Сама программа пишется на языке программирования, предназначенном для людей, а не на C--.
С каждой реализацией C-- поставляется run-time система; основная цель системы - поддержка и обеспечение доступа к информации, которая может быть известна только генератору кода. Она делает доступной эту информацию для front-end run-time системы посредством C-- run-time интерфейса. Различные front-ends могут взаимодействовать с одной C-- run-time системой. Для формирования исполняемой программы сгенерированный объектный код связывается с обеими run-time системами.
В общих чертах, C-- может поддерживать высокоуровневые run-time сервисы, к примеру сборки мусора, следующим образом. Сначала управление передается front-end run-time системе. Сборщик мусора проходит стек C--, вызывая программы доступа (процедуры) из run-time системы языка; в каждой активизационной записи стека он, используя дальнейшие процедуры (представляемые C-- run-time), находит размещение каждой переменной. (Хотя следует отметить, что C-- run-time не известно, в какой из переменных хранится указатель.)
Front-end компилятор формирует статически распределяемый блок данных, идентифицирующий переменные указателей, и использует директиву span, чтобы ассоциировать этот блок данных с соответствующей областью процедуры программных счетчиков. Сборщик мусора совмещает эти два источника информации для выбора процедурной переменной как корня.
Используя run-time интерфейс, front-end run-time система может проверять и модифицировать состояние приостановленных вычислений. Вместо определения представительств приостановленных вычислений или активизационных записей, они скрыты за простыми абстракциями. Эти абстракции присутствуют в front-end run-time системе в виде набора C-процедур (подпрограмм, написанных на языке C).
Камнем преткновения для разработчиков языка стала легкость в перенастройке front-ends, а не в возможности запуска всех программ на C-- везде. Несмотря на то, что любая C-- программа имеет, независимо от машины, отчетливо выраженную семантику, высокоуровневому компилятору, транслирующему исходную программу для двух целевых архитектур, необходимо сгенерировать две разные C-- программы. Например, программа, сгенерированная для машины с командами с плавающей запятой, будет отличаться от программы, сгенерированной для машины без плавающей запятой.

Ad usum

Сегодня наиболее активные работы ведутся над компилятором и интерпретатором Quick C--. Компилятор написан на языке функционального программирования и инструментальном средстве так называемого грамотного (literate) программирования . Документация Quick C-- представлена в формате системы допечатной подготовки LaTeX. Процесс сборки Quick C-- управляется портом программы mk (аналог make в операционных системах Plan 9 и Inferno).
Для сборки компилятора из исходных текстов (ибо лишь в таком виде он распространяется) требуется наличие таких пакетов, как: Objective Caml 3.04 или выше, Noweb 2.9 или выше, LaTeX и POD-утилиты Perl (точнее - pod2man).
Дабы получить интерпретатор, реализованный на C, вам дополнительно потребуются компилятор ANSI C (стандарт de facto - GCC - подходит) и пакет языка программирования , включая библиотеку и заголовочные файлы.
доступен как в виде tarball-файлов, так и в виде исходного дерева посредством анонимного Rsync-сервера.
Кроме Quick C--, есть еще несколько проектов компиляторов:
  • Фермина Рэйга (Fermin Reig), написанный на языке Standard ML;
  • Trampoline C-- Compiler Сергея Егорова (Sergei Egorov);
  • - первый прототип реализации C--.

  • К сожалению, все три проекта на данный момент не поддерживаются.

    Differentia specifica

    Чтобы более детально разобраться с некоторыми отличительными особенностями C--, рассмотрим пример трех процедур (sp1, sp2, sp3), вычисляющих сумму и произведение целых чисел от 1 до n. /* Обычная рекурсия */ sp1(word4 n) { word4 s, p; if n == 1 { return (1, 1); } else { s, p = sp1(n-1); return (s+n, p*n); } }
    /* Tail-рекурсия */ sp2(word4 n) { jump sp2_help (n, 1, 1); }
    sp2_help (word4 n, word4 s, word4 p) { if n==1 { return (s, p); } else { jump sp2_help (n-1, s+n, p*n) } }
    /* Циклы */ sp3(word4 n) { word4 s, p; s = 1; p = 1;
    loop: if n==1 { return (s, p); } else { s = s+n; p = p*n; n = n-1; goto loop; } }

    (лат.) - отличительный признак; характерная особенность.

    Dixi

    (лат.) - я сказал, я высказался.
    document.write('');
    Dixi
    Dixi

    Dixi
    Dixi
    Новости мира IT:
  • 02.08 -
  • 02.08 -
  • 02.08 -
  • 02.08 -
  • 02.08 -
  • 01.08 -
  • 01.08 -
  • 01.08 -
  • 01.08 -
  • 01.08 -
  • 01.08 -
  • 01.08 -
  • 01.08 -
  • 01.08 -
  • 01.08 -
  • 31.07 -
  • 31.07 -
  • 31.07 -
  • 31.07 -
  • 31.07 -

  • Архив новостей
    Dixi
    Dixi
    Dixi
    Последние комментарии:
    (66)

    2 Август, 17:53
    (19)

    2 Август, 17:51
    (34)

    2 Август, 15:40
    (42)

    2 Август, 15:35
    (1)

    2 Август, 14:54
    (3)

    2 Август, 14:34
    (3)

    2 Август, 14:15
    (2)

    2 Август, 13:34
    (7)

    2 Август, 13:04
    (3)

    2 Август, 12:28
    Dixi
    Dixi
    Dixi

    BrainBoard.ru

    Море работы для программистов, сисадминов, вебмастеров.

    Иди и выбирай!

    Dixi
    Dixi
    Dixi
    Loading
    google.load('search', '1', {language : 'ru'}); google.setOnLoadCallback(function() { var customSearchControl = new google.search.CustomSearchControl('018117224161927867877:xbac02ystjy'); customSearchControl.setResultSetSize(google.search.Search.FILTERED_CSE_RESULTSET); customSearchControl.draw('cse'); }, true);
    Dixi
    Dixi

    Dixi
    Dixi


    IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware


    PR-акции, размещение рекламы — ,
    тел. +7 495 6608306, ICQ 232284597
    Пресс-релизы —

    Dixi
    Dixi
    Dixi

    Dixi
    This Web server launched on February 24, 1997

    Copyright © 1997-2000 CIT, © 2001-2009

    Dixi
    Dixi

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


    Обратите внимание, в нашей компании можно также приобрести в кредит, звоните, наш менеджер подробнее расскажет Вам о наших предложениях.



    Импорт/экспорт

    . В общем, любая процедура или метка, объявленная в компиляционной единице C--, может вызывать или ссылаться на, соответственно, прочие единицы или подпрограммы других языков программирования. Все имена, используемые за пределами компиляционной единицы C--, должны явно экспортироваться. Подобно этому, имена, используемые, но не объявленные в компиляционной единице, должны явно импортироваться. За импорт и экспорт имен отвечают объявления import и export соответственно.

    In abstracto

    Гибрид (от лат. hibrida - помесь) - это организм, полученный в результате скрещивания генетически различающихся родительских форм (видов, пород, линий и др.). Процесс создания гибридов, гибридизация,- скрещивание разнородных в наследственном отношении организмов; один из важнейших факторов эволюции биологических форм в природе. Применяют его для получения хозяйственно ценных (Sic!) форм животных и растений. Скрещивание особей одного и того же вида называется внутривидовой гибридизацией, а различных видов или родов - отдаленной гибридизацией.
    Что ж, выполнив виртуальную функцию ассоциации и тем самым преобразовав популярное биологическое понятие к теме нашего сегодняшнего разговора о новой, уникальной и по-своему революционной разработке, попробуем просеять через "сито объективности и краткости" доступную документацию и оставить лишь как можно более яркие концептуальные особенности нашего гибрида.
    Отдаленная гибридизация двух различных технологий программирования произвела на этот информационный свет C-- - новый язык программирования, специально спроектированный как портабельный ассемблер, квинтэссенция обычного "сборщика" (первоначальное перевод слова assembler) иС.

    Локальные переменные

    . C-- допускает объявление произвольного количества локальных переменных (именуемых также виртуальными регистрами). Объявления очень схожи на таковые в C: список однотипных имен переменных предваряется типом.
    Предполагается, что, если локальных переменных не слишком много, то все они отображаются на регистры. В sp3, например, локальные переменные s и p - определенно регистро-размещаемые, и для них никогда не резервируются стековые слоты.

    Метки

    . C-- работает с локальными метками и операторами goto (см. sp3). Метка находится не в любом месте области, а лишь в теле собственной процедуры, и она не является значением первого класса. Метка может использоваться только как объект оператора goto, и только метка может быть аргументом goto. Для всех других переходов используются tail-вызовы.

    Начальный вызов

    . Процедура main (), которая первой вызывается при исполнении кода в C, здесь отсутствует (NB). Взамен: каждая компиляционная единица C-- (файл, содержащий полную C-- программу, которая может быть скомпилирована и пригодна для компоновки) рассматривается как набор процедур и меток. Процедуры, описанные внутри компиляционной единицы, не имеют иерархии и могут вызываться за ее пределами при условии, что они экспортированы и что соглашение о вызове известно. Тогда способ ввода кода C-- состоит в вызове процедур C-- из программ, написанных на других языках. Метки допускают доступ к распределяемым данным другим программам.

    Panem et circenses

    Вот и еще одна серебряная пуля нашла свое место в барабане шестизарядного револьвера Одинокого Странника. Время покажет, суждено ли этой пуле вылететь из ствола оружия и покончить с оборотнем - или же она будет заменена другой, более, так сказать, "серебристой".
    Dixi.

    Процедуры

    . Несмотря на то, что подпрограммы C-- могут возвращать значения, для них используется термин "процедуры", а не "функции". Термин "функция" более соответствует чистым математическим функциям, т. е. соответствию y=f (x) между переменными величинами, в силу которого каждому рассматриваемому значению некоторой величины x - аргумента (или независимой переменной) соответствует определенное значение другой величины y - функции (или зависимой переменной).
    Процедуры C-- имеют много общего с функциями C: аналогичный синтаксис определений, передача параметров по значениям, запрещенная вложенность процедур в процедуры ит.п. Но есть также и существенные отличия: процедурные переходы (следовательно, эффективные tail-вызовы), способность к возвращению множественных результатов, неопределенный тип результата, процедурный вызов - как оператор (не как выражение), фиксированное количество аргументов, отсутствие вложенности локальных областей и прочее.
    Пример возвращения множественных результатов: все sp-процедуры примера возвращают два результата: сумму - s+n и произведение - p (n. Оператор return получает ноль или более параметров, просто как процедурный вызов; и вызов может передавать множественные результаты, как видно в рекурсивном вызове sp1. Такая способность множественных результатов довольно полезна и, к тому же, легка в реализации.

    Tail-вызовы

    . C-- обеспечивает оптимизацию tail-вызовов - даже между отдельно скомпилированными модулями. В процедуре sp2_help, к примеру, tail-вызовы реализуют простой цикл без стекового прироста. Процедура sp2 вызывает sp2_help, которая возвращается непосредственно к вызывающему оператору sp2. Tail-вызов может рассматриваться как "параметры отправки jump-операторов".

    Типы

    . По сравнению со стандартными ассемблерами, C-- выделяется очень слабой и по-своему ненадежной системой типизации, исключительная цель которой состоит в сообщении генератору кода того, насколько велико каждое значение и какой класс операций может над ним выполняться. C-- поддерживает лишь минимум типов данных: семейство word-типов (word8, word16, word32, word64) и семейство типов с плавающей запятой (или, как их чаще величают по-математически, "действительными": float32, float64, float80). Эти типы кодируют только размер (в битах) и тип регистра (общего назначения или с плавающей запятой), необходимых для размещения/хранения данных. Значения с плавающей запятой могут размещаться в различных банках регистров. Не все типы данных доступны для каждой машины. Word-типы используются для символов, битовых векторов, целых чисел и указателей. На каждой машине один из word-типов (типично word32или word64) определяется native-размером слов машины. Второй тип данных также определяется native-указателем типов. Экспортируемые и импортируемые имена должны иметь native-указатель на тип.

    Условности

    . В C-- есть поддержка выражений условий, но, в отличие от C, здесь нет булевого (логического) типа (в C как булев тип используется int). Взамен, условности синтаксически включают операции сравнения, вроде ==,!=, <, <=, >, >= ит.п. (они названы операциями а не операторами - потому что им дозволено появляться лишь в условных тестах if, а не внутри выражений - как операторам C - - ).
    C--, подобно C, также поддерживает оператор выбора switch. Отличие заключается в том, что C-- позволяет программисту определять, что рассматриваемое значение несомненно примет одно из указанных значений,- вследствие этого опускаются проверки диапазонов.

    Выражения

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

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

    Как проверить, насколько эффективный код генерирует компилятор? Очень просто: нужно выбрать несколько наиболее часто употребляемых конструкций языка и алгоритмов - и измерить время их выполнения после компиляции различными компиляторами. Для более точного определения времени необходимо набрать статистику и выполнить каждую конструкцию некоторое количество раз.
    Вроде все просто - но тут начинают возникать определенные проблемы. Провести тестирование некоторых конструкций (например, обращение к полю объекта) не удастся из-за оптимизации на уровне компилятора: строки типа for (unsigned i=0;i<10000000;i++) dummy = obj->dummyField; все компиляторы просто выбросили из конечного бинарного кода.
    Вторым неприятным моментом является то, что в результаты всех тестов неявно вошло время выполнения самого цикла "for", в котором происходит набор статистики. В некоторых реализациях оно может быть очень даже существенным (например, два такта на одну итерацию пустого for для gcc). Измерить "чистое" время выполнения пустого цикла удалось не для всех компиляторов - VC++ и Intel Compiler выполняют достаточно хорошую "раскрутку" кода и исключают из конечного кода все пустые циклы, inline-вызовы пустых методов и т.д. Даже конструкцию вида for (unsigned i=0;i<16;i++) dummy++; VC++ реализовал как dummy += 16;.
    Наличие такой нетривиальной низкоуровневой оптимизации наводит на мысль о необходимости анализа сгенерированного кода на уровне ассемблера. Во-первых, это позволит убедиться в том, что мы действительно измерили то, что хотели измерить (а не оптимизированный компилятором пустой цикл, из которого он выбросил все "лишние" вызовы). Во-вторых, это позволит более точно определить, чей код наиболее оптимален, что существенно дополнит картину тестирования.
    Кроме того, для полноты картины было проведено тестирование времени компиляции работающего исходника с целью определить, у какого же из компиляторов время компиляции наименьшее.
    Для измерения времени выполнения тестов использовался счетчик машинных тактов, доступный по команде процессора RDTSC, что позволило не только сравнить время выполнения большого количества однотипных операций, но и получить приближенное время выполнения операции в тактах (вторая величина является более показательной и удобной для сравнения). Все тесты проводились на Pentium III (700 МГц), параметры компиляции были установлены в "-O2 -6" (оптимизация по скорости + оптимизация под набор команд Pentium Pro). Кроме того, для Borland Builder была добавлена опция --fast-call - передача параметров через регистры (Intel Compiler, MSVC++ и gcc автоматически используют передачу параметров через регистры при использовании оптимизации по скорости).
    Тестирование было разделено на несколько независимых частей. Первая - тестирование скорости работы основных конструкций языка (виртуальные вызовы, прямые вызовы и т.д.). Вторая - тестирование скорости работы STL. Третья - тестирование менеджера памяти, поставляемого вместе с компилятором. Четвертая - разбор ассемблерного кода таких базовых операций, как вызов функции и построения цикла. Пятая - сравнение времени компиляции и размера выполняемого файла.

    Разбор ассемблерного кода неких базовых операций

    Для анализа использовался достаточно простой код на С++:
    void dummyFn1(unsigned);
    void dummyFn2(unsigned aa) {
    for (unsigned i=0;i<16;i++) dummyFn1(aa);
    }

    А теперь посмотрим, во что этот кусок кода компилирует MSVC++ (приводится только текст необходимой функции):
    ?dummyFn2@@YAXI@Z PROC NEAR
    push esi
    push edi
    mov edi, DWORD PTR _aa$[esp+4]
    mov esi, 16
    $L271:
    push edi
    call?dummyFn1@@YAXI@Z

    add esp, 4
    dec esi
    jne SHORT $L271
    pop edi
    pop esi
    ret 0
    ?dummyFn@@YAXI@Z ENDP

    Как видно, MSVC++ инвертировал цикл и for (unsigned i=0;i<16;i++) у него превратился в unsigned i=16;while (i--);, что очень правильно с точки зрения оптимизации - мы экономим на одной операции сравнения (см. следующий листинг), которая занимает, как минимум, 5 байт, и нарушает выравнивание. Конечно, компилятор по своему усмотрению поменял порядок изменения переменной i, но в данном примере мы ее используем просто как счетчик цикла, поэтому такая замена вполне допустима.
    А вот что выдал Intel Compiler (вообще-то, он сначала вообще полностью развернул цикл, но после увеличения количества итераций на порядок прекратил заниматься такой самодеятельностью):
    ?dummyFn2@@YAXI@Z PROC NEAR
    $B1$1:
    push ebp
    push ebx
    mov ebp, DWORD PTR [esp+12]
    sub esp, 20
    xor ebx, ebx
    $B1$2:
    mov DWORD PTR [esp], ebp
    call?dummyFn1@@YAXI@Z
    $B1$3:
    inc ebx
    cmp ebx, 16
    jb $B1$2
    $B1$4:
    add esp, 20
    pop ebx
    pop ebp
    ret
    ?dummyFn2@@YAXI@Z ENDP

    Во-первых, используется прямой порядок цикла for, поэтому появилась дополнительная команда сравнения "cmp ebx, 16". А вот и очень интересный момент -перед началом цикла мы выделили на стеке необходимое количество памяти плюс некий запас ("sub esp, 20"), а потом вместо пары push reg;..;add esp, 4;, как это делает MSVC++, использовали одну команду копирования. Кроме того, использование регистра общего назначения ebx для счетчика цикла вместо индексного esi, как в MSVC++, дополнительно уменьшает время выполнения и размер кода.
    Borland Builder сгенерировал следующую конструкцию:
    @@dummyFn2$qui proc near
    ?live16385@0:
    @1:
    push ebp
    mov ebp,esp
    push ebx
    push esi
    mov esi,dword ptr [ebp+8]
    ?live16385@16:
    @2:
    xor ebx,ebx
    @3:
    push esi
    call @@dummyFn1$qui
    pop ecx
    @5:
    inc ebx
    cmp ebx,16
    jb short @3
    ?live16385@32:
    @7:
    pop esi
    pop ebx
    pop ebp
    ret
    @@dummyFn2$qui endp

    Если не считать большего количества подготовительных операций, то блок вызова собственно функции является чем-то средним между MSVC++ и Intel Compiler: цикл используется прямой и передача параметров осуществляется с помощью push reg;. Правда, есть интересный момент: вместо add esp, 4 используется pop ecx; что экономит, как минимум, 4 байта,- правда, из-за дополнительного обращения к памяти команда "pop" может работать медленнее, чем сложение.
    Ну и, наконец, gcc (обратите внимание, gcc для ассемблера использует синтаксис AT&T):
    __Z7dummy2Fnj:
    LFB1:
    pushl %ebp
    LCFI0:
    movl %esp, %ebp
    LCFI1:
    pushl %esi
    LCFI2:
    pushl %ebx
    LCFI3:
    xorl %ebx, %ebx
    movl 8(%ebp), %esi
    .p2align 4,,7
    L6:
    subl $12, %esp
    incl %ebx
    pushl %esi
    LCFI4:
    call __Z2dummyFn1j
    addl $16, %esp
    cmpl $15, %ebx
    jbe L6
    leal -8(%ebp), %esp
    popl %ebx
    popl %esi
    popl %ebp
    ret

    Данный код является самым плохим из всех приведенных выше - gcc использует прямой цикл плюс пару push esi;..;add esp, 4 (это происходит неявно в команде "addl $16, %esp") для передачи параметров; кроме того, резервирует место на стеке прямо в цикле, а не вне его, как это делает Intel Compiler. Кроме того, совершенно непонятно, зачем резервировать место на стеке, а потом использовать команду push reg;. Единственный приятный момент - это явное выравнивание начала цикла по границе, чего не делают остальные компиляторы - поскольку линейка кэша сегмента кода достигает 32-х байт, то метки начала циклов должны быть выровнены по границе 16 байт. На каждый байт, выходящий за пределы кэша, процессор семейства P2 тратит 9-12 тактов.

    Результаты

    Казалось бы, вывод о самом эффективном компиляторе напрашивается сам собой - это Borland Builder C++. Но не стоит спешить. Многие разработчики указывают на ошибки при формировании кода у Borland Builder (в частности, при использовании ссылок его поведение становится непредсказуемым). Кроме того, Borland Builder C++ явно наследует многое от Delphi (один модификатор вызова метода DYNAMIC чего стоит), в результате чего при компилировании абсолютно правильного С++ кода могут возникать ошибки (например, отсутствие множественного наследования для VCL-классов; а все потомки от TObject являются VCL-классами).
    С другой стороны, самым стабильным и "вылизанным" компилятором можно назвать gcc. Но скорость выполнения откомпилированного кода на нем будет не слишком высокой. Причиной тому, вероятно, существование gcc на многих платформах и, как следствие, необходимость компилирования под эти платформы.
    MSVC++ или Intel Compiler не имеют явно выраженных недостатков, так что их позиции примерно равны.
    В общем, однозначно ответить, "какой компилятор наилучший", невозможно. Но пусть результаты данных тестов помогут вам сделать "правильный" выбор.

    Сравнение времени компиляции и размера выполняемого файла

    Для выполнения этого теста использовался все тот же исходный код, из которого были удалены все compiler-specific тесты. Тестирование выполнялось отдельно для компиляции релиза и для отладочной версии, размер бинарного файла указан только для релиза (см. табл. 4). Чтобы исключить влияние файлового кэша, проводились две одинаковые компиляции подряд - время измерялось по второй с помощью команды "date" (исключение составил только Builder - он сам измеряет время компиляции).
    Таблица 4. Результаты сравнения времени компиляции и размера выполняемого файла
    VC++ Intel Compiler Bulder C++ MinGW (gcc)
    release build time,sec 3 5 2.35 6
    release size, Kb 56 72 77 214
    debug build time, sec 3 5 3 7

    Первое место поделили Borland Builder и MSVC++, а вот gcc - опять на последнем месте, как по скорости компиляции, так и по размеру бинарного файла. Интересным моментом является тот факт, что время компиляции отладочной версии у gcc и Builder'а выше времени компиляции релиза. Объясняется это тем, что при компиляции отладочной версии компилятору необходимо добавить отладочную информацию, что существенно увеличивает размер объектного файла - и, как следствие, время работы линковщика.

    Сравнительный анализ компиляторов С++

    Игорь Тимошенко,
    К сожалению, выбор компилятора часто обусловлен, опять-таки, идеологией и соображениями вроде "его все используют". Конечно, среда разработки Microsoft Visual C++ несколько более удобна, чем у портированного gcc - но это ведь вовсе не значит, что релиз своего продукта вы должны компилировать с использованием MSVC++. Используйте оболочку, компилируйте промежуточные версии на MSVC++ (кстати, время компиляции у него гораздо меньше, чем у gcc), но релиз можно собрать с использованием другого компилятора, например от Intel. И, в зависимости от компилятора, можно получить прирост в производительности на 10% просто так, на ровном месте. Но какой "правильный" компилятор выбрать, чтобы он сгенерировал максимально быстрый код? К сожалению, однозначного ответа на этот вопрос нет - одни компиляторы лучше оптимизируют виртуальные вызовы, другие - лучше работают с памятью.
    Попробуем определить, кто в чем силен среди компиляторов для платформы Wintel (x86-процессор + Win32 ОС). В забеге принимают участие компиляторы Microsoft Visual C++ 6.0, Intel C++ Compiler 4.5, Borland Builder 6.0, MinGW (портированный gcc) 3.2.

    Тестирование менеджера памяти

    Как известно, при выделении памяти malloc редко обращается напрямую к системе - и использует вместо этого свою внутреннюю структуру для динамического выделения памяти и изменения размера уже выделенного блока. Скорость работы этого внутреннего менеджера может весьма существенно влиять на скорость работы всего приложения. Тестирование менеджера памяти было разбито на две части: в первой измерялась скорость работы пары malloc/free, а во второй - malloc/realloc, причем realloc должен был выделить вдвое больший объем памяти, чем malloc.
    Таблица 3. Результаты тестирования менеджера памяти
    VC++ Intel Compiler Bulder C++ MinGW (gcc)
    malloc 905 (6336) 902 (6317) 24 (174) 882 (6178)
    realloc 30 (718) 30 (716) 12 (295) 30 (719)

    И снова быстрее всех был Borland Builder C++. Благодаря такой быстрой реализации malloc'а он находится на первом месте и по скорости создания/удаления объектов - да и на тестах STL, связанных с изменением размера блока памяти, бегает достаточно быстро.

    Тестирование скорости работы основных конструкций языка

    Первый тест очень даже прост, он заключается в измерении скорости прямого вызова (member call), виртуального вызова (virtual call), вызова статик-метода (данная операция полностью аналогична вызову обыкновенной функции), создания объекта и удаления объекта с виртуальным деструктором (create object), создания/удаления объекта с inline-конструктором и деструктором (create inline object), создание template'ного объекта (create template object). Результаты теста приведены в таблице 1.
    Таблица 1. Результаты тестирования скорости работы основных конструкций языка
    VC++ Intel Compiler Bulder C++ MinGW (gcc)
    virtual call 140 (9) 134 (9) 139 (9) 183 (12)
    member call 124 (8) !34 (9) 103 (7) 154 (10)
    static call 121 (8) 113 (7) 109 (7) 118 (8)
    create object 606 (424) 663 (443) 459 (321) 619 (433)
    create inline object 579 (405) 600 (420) 343 (240) 590 (413)
    create temlate object 580 (405) 599 (419) 349 (244) 579 (405)

    Первая цифра - это полное время, затраченное на тест (в миллисекундах); цифра в скобках - количество тактов на одну команду.
    Результаты получились очень даже интересными: первое место занял Borland Builder, а вот gcc на вызове методов, особенно виртуальных, показал существенное отставание. По всей видимости - из-за бурного развития COM'а, где все вызовы виртуальные, разработчикам "родных" компиляторов под Win32 пришлось максимально оптимизировать эти типы вызовов. Другим интересным фактом является то, что хорошо оптимизировать создание объекта с inline-конструктором и деструктором смог, опять-таки, только Builder.
    Конечно, у MSVC++ также наблюдается небольшой прирост производительности, но объясняется это тем, что MSVC++ очень хорошо "раскручивает" код и все заглушки просто выбрасывает. То есть в тесте с inline-вызовами MSVC++ определил, что вызываемый метод является пустым, и исключил его вызов. После исключения вызова пустого метода у него остался пустой цикл, который компилятор также выбросил.
    Borland же в случае использования inline-конструктора делает inline не только вызов метода "Конструктор", но и выделение памяти под объект. То же самое делает Builder относительно деструктора. Любопытно отметить, что с шаблонами Builder работает точно так же, как с inline-методами, чего совершенно не скажешь о других компиляторах.

    Тестирование STL

    STL, как известно, входит в ISO стандарт C++ и содержит очень много полезного и превосходно реализованного кода, использование которого существенно облегчает жизнь программистам. Конечно, MCVC++, gcc и Builder используют различные реализации STL - и результаты тестирования будут сильно зависеть от эффективности реализации тех или иных алгоритмов, а не от качества самого компилятора. Но, так как STL входит в ISO-стандарт, тестирование этой библиотеки просто неотделимо от тестирования самого компилятора.
    Проводилось тестирование только наиболее часто используемых классов STL: string, vector, map, sort. При тестировании string'а измерялась скорость конкатенации; для vector'а же - время добавления элемента (удаление не тестировалось, так как это просто тестирование realloc'а, которое будет проведено ниже); для map'а измерялось время добавления элемента и скорость поиска необходимого ключа; для sort'а - время сортировки. Так как Microsoft не рекомендует использовать STL в VC++, для сравнения было добавлено тестирование конкатенации строк на основе родного класса VC++ для работы со строками CString и, чтобы уж совсем никого не обидеть, то и родного класса Builder'а - AnsiString. Результаты, опять же, оказались очень даже интересными (см. табл. 2)
    Таблица 2. Результаты тестирования STL
    VC++ Intel Compiler Bulder C++ MinGW (gcc)
    string add 8 (572) 11 (837) 3 (244) 2 (199)
    AnsiString - - 11 (832) -
    Cstring 106 (7476) 104 (7331) - -
    sort 157 (10994) 156 (10943) 387 (27132) 226 (15848)
    vector insert 110 (77) 96 (67) 63 (44) 56 (39)
    map insert 1311 (1836) 1455 (2037) 848 (1148) 448 (627)
    map find 181 (127) 4 (3) 418 (293) 199 (139)

    Согласно результатам, не рекомендованный STL string работает в 12 раз быстрее, чем родной CString Microsoft! Как тут в очередной раз не задуматься о практичности рекомендаций Microsoft... А вот просто потрясающий результат на поиске от Intel Compiler это результат оптимизации "ничего не делающего кода" - поиск как таковой он просто выбросил из конечного бинарного кода. Не менее интересен результат gcc - во всех тестах, связанных с выделением памяти, gcc оказался на первом месте.

    

        Биржевая торговля: Механические торговые системы - Создание - Программирование