По поводу x86 и суперскалярности. Все верно: x86 != суперскалярность. Но сейчас все представители семейства суперскалярны (даже Atom с недавних пор обрел это свойство), так что я не стал здесь вдаваться в детали.
Частота P4 не была «отвоевана». Она была завоевана. P4 из-за постоянного реплэя выделял мощность в соответствии со своей частотой, а IPC демонстрировал низкий. Эффективно он работал как процессор с более низкой частотой.
Тоже немного по нему ностальгирую. Технически он еще жив, т.к. все еще есть клиенты, которые его покупают. Но уже не развивается.
Itanium отлично показал себя на некоторых классах задач, но на некоторых оказался средненьким. В общем, не потянул на general purpose архитектуру.
Однако на нем были обкатаны некоторые интересные фишки (двоичная трансляция, например). Многие из этих фишек, подозреваю, получат новые воплощения, но уже с учетом полученных уроков.
Не стоит напрямую связывать обратную совместимость и возможность работать над длиной стадии. Однако действительно: отказ от совместимости позволяет работать над проблемой более эффективно.
Идеальная машина — это та, в которой единственные связи между инструкциями — это связи по данным (т.е. одни инструкции используют результаты работы других). Задача разработчиков архитектуры — постоянно приближаться к этому идеалу, устраняя все другие (кажущиеся искусственными) зависимости. Не думаю, что ARM сейчас ближе к этому идеалу, чем x86.
Зачем любители ARM хотят «похоронить» x86 (а они действительно хотят?), я не знаю. Я за разнообразие архитектур. Без конкуренции ничего хорошего не получается :)
И еще я бы не сказал, что единственная причина, по которой x86 жива, — это большое количество написанных программ. Все-таки, на сегодняшний день, это самая эффективная архитектура, которая все еще продолжает развиваться.
Софт со временем созреет. Сейчас многое для этого делается: синтаксические расширения языков программирования, библиотеки хорошие, обучение студентов в ключе «параллельного мышления».
Основная проблема как раз в этом и есть: нынешние поколения программистов не привыкли мыслить параллельно. Как говорит один мой знакомый, параллельно умеют мыслить только домохозяйки. У них в одном углу ребенок растет, в другом кастрюля кипит, а в третьем белье гладится. И ведь все синхронизовано! :)
Кстати, над автоматическим распараллеливанием программ работы тоже ведутся. Но здесь все как-то запредельно сложно получается пока что…
Вы, наверное, имеете в виду спецификатор «inline»?
Такой спецификатор является не «указанием», а скорее, пожеланием. Компилятор может отказаться выполнять подстановку функции, отмеченной этим спецификатором.
Помимо этого компилятор может выполнять подстановки функций, не отмеченных словом «inline» (если ему позволяет заданный опциями режим работы).
Так что «инлайнинг» — это действительно оптимизация, и довольно непростая во многих случаях.
Все верно: в общем случае такая оптимизация не пройдет. Но в общем случае вообще никакая оптимизация не пройдет. Задача компилятора во многом состоит в том, чтобы отыскивать возможности для оптимизации.
Приведенный пример служит лишь иллюстрацией (одной из самых простых). Но даже такие примеры на практике встречаются довольно часто. Программист редко работает лишь со внешними объектами. Как только он определяет что-то свое, у компилятора сразу возникает простор для творчества.
Попробую пояснить.
Я не хотел слишком подробно разбирать этот пример, что привело, видимо, к слишком сумбурному тексту.
Дело в том, что мы с вами вкладываем разный смысл в слово «поддерживает».
Конечно же, любой компилятор для C++ поддерживает полиморфизм в том смысле, что строго выполняет требования стандарта языка C++. Но я имел в виду немного другое.
Продолжим разбор примера.
Что происходит в динамике (т.е. во время выполнения программы), когда необходимо выполнить инструкцию «int value = ptr->func();»
А происходит примерно следующее:
1) из участка памяти, соответствующего экземпляру класса, на который указывает «ptr», берется указатель на таблицу виртуальных функций
2) из таблицы виртуальных функций достается указатель на функцию «func»
3) таким образом, мы получили указатель именно на ту версию виртуальной функции «func», которая соответствует конкретному типу класса. Лишь на этом этапе мы можем, наконец, вызвать «func».
Мы видим, что реализованный напрямую механизм виртуальных функций приводит к дополнительным косвенностям: в коде, вместо вызова конкретной функции, стоит вызов функции через указатель. Мало того, что это замедляет сам вызов, это не дает компилятору возможности выполнить подстановку вызова (inline) просто напросто потому, что мы не знаем, какой вызов подставлять (т.к. у нас есть несколько версий функции «func»).
Задача оптимизирующего компилятора состоит в том, чтобы как можно больше узнать о типе класса, для которого вызывается виртуальная функция. А это довольно нетривиальная (в общем случае) задача.
В рассмотренном выше примере компилятор мог действовать примерно так:
1) мы видим, что «ptr» может указывать только на класс типа «A»
2) значит, вызываться может только версия «func», определенная для класса «A»
3) заменяем вызов через таблицу виртуальных функций непосредственно на вызов нужной версии
4) теперь, когда у нас есть конкретный вызов, мы можем его попросту подставить.
Таким образом, у нас вообще не остается вызова. Остается просто присваивание «value = 5».
Данная запись является вводной для цикла постов о принципах работы оптимизирующих компиляторов, о сложностях их использования и о бонусах, которые можно получить от использования хорошего компилятора.
Если вам интересны какие-то определенные темы, оставляйте запросы. Попробуем рассмотреть то, что интересно именно вам.
Частота P4 не была «отвоевана». Она была завоевана. P4 из-за постоянного реплэя выделял мощность в соответствии со своей частотой, а IPC демонстрировал низкий. Эффективно он работал как процессор с более низкой частотой.
Itanium отлично показал себя на некоторых классах задач, но на некоторых оказался средненьким. В общем, не потянул на general purpose архитектуру.
Однако на нем были обкатаны некоторые интересные фишки (двоичная трансляция, например). Многие из этих фишек, подозреваю, получат новые воплощения, но уже с учетом полученных уроков.
Идеальная машина — это та, в которой единственные связи между инструкциями — это связи по данным (т.е. одни инструкции используют результаты работы других). Задача разработчиков архитектуры — постоянно приближаться к этому идеалу, устраняя все другие (кажущиеся искусственными) зависимости. Не думаю, что ARM сейчас ближе к этому идеалу, чем x86.
Зачем любители ARM хотят «похоронить» x86 (а они действительно хотят?), я не знаю. Я за разнообразие архитектур. Без конкуренции ничего хорошего не получается :)
И еще я бы не сказал, что единственная причина, по которой x86 жива, — это большое количество написанных программ. Все-таки, на сегодняшний день, это самая эффективная архитектура, которая все еще продолжает развиваться.
Основная проблема как раз в этом и есть: нынешние поколения программистов не привыкли мыслить параллельно. Как говорит один мой знакомый, параллельно умеют мыслить только домохозяйки. У них в одном углу ребенок растет, в другом кастрюля кипит, а в третьем белье гладится. И ведь все синхронизовано! :)
Кстати, над автоматическим распараллеливанием программ работы тоже ведутся. Но здесь все как-то запредельно сложно получается пока что…
Такой спецификатор является не «указанием», а скорее, пожеланием. Компилятор может отказаться выполнять подстановку функции, отмеченной этим спецификатором.
Помимо этого компилятор может выполнять подстановки функций, не отмеченных словом «inline» (если ему позволяет заданный опциями режим работы).
Так что «инлайнинг» — это действительно оптимизация, и довольно непростая во многих случаях.
Приведенный пример служит лишь иллюстрацией (одной из самых простых). Но даже такие примеры на практике встречаются довольно часто. Программист редко работает лишь со внешними объектами. Как только он определяет что-то свое, у компилятора сразу возникает простор для творчества.
Я не хотел слишком подробно разбирать этот пример, что привело, видимо, к слишком сумбурному тексту.
Дело в том, что мы с вами вкладываем разный смысл в слово «поддерживает».
Конечно же, любой компилятор для C++ поддерживает полиморфизм в том смысле, что строго выполняет требования стандарта языка C++. Но я имел в виду немного другое.
Продолжим разбор примера.
Что происходит в динамике (т.е. во время выполнения программы), когда необходимо выполнить инструкцию «int value = ptr->func();»
А происходит примерно следующее:
1) из участка памяти, соответствующего экземпляру класса, на который указывает «ptr», берется указатель на таблицу виртуальных функций
2) из таблицы виртуальных функций достается указатель на функцию «func»
3) таким образом, мы получили указатель именно на ту версию виртуальной функции «func», которая соответствует конкретному типу класса. Лишь на этом этапе мы можем, наконец, вызвать «func».
Мы видим, что реализованный напрямую механизм виртуальных функций приводит к дополнительным косвенностям: в коде, вместо вызова конкретной функции, стоит вызов функции через указатель. Мало того, что это замедляет сам вызов, это не дает компилятору возможности выполнить подстановку вызова (inline) просто напросто потому, что мы не знаем, какой вызов подставлять (т.к. у нас есть несколько версий функции «func»).
Задача оптимизирующего компилятора состоит в том, чтобы как можно больше узнать о типе класса, для которого вызывается виртуальная функция. А это довольно нетривиальная (в общем случае) задача.
В рассмотренном выше примере компилятор мог действовать примерно так:
1) мы видим, что «ptr» может указывать только на класс типа «A»
2) значит, вызываться может только версия «func», определенная для класса «A»
3) заменяем вызов через таблицу виртуальных функций непосредственно на вызов нужной версии
4) теперь, когда у нас есть конкретный вызов, мы можем его попросту подставить.
Таким образом, у нас вообще не остается вызова. Остается просто присваивание «value = 5».
Если вам интересны какие-то определенные темы, оставляйте запросы. Попробуем рассмотреть то, что интересно именно вам.