Как сказать компилятору НЕ оптимизировать определенный код? - PullRequest
3 голосов
/ 21 марта 2009

Есть ли способ сказать компилятору (g ++ в моем случае), чтобы не оптимизировал определенный код, даже если этот код недоступен? Я просто хочу эти символы в объектном файле.

Пример: Вот простая функция, и я хочу, чтобы эта функция была скомпилирована, даже если она никогда не вызывается.

void foo(){
  Foo<int> v;
}

Если нет официальной директивы компилятора, есть ли хитрость, заставляющая компилятор думать, что это важная функция? Или, по крайней мере, заставить его думать, что его нельзя игнорировать безопасно? Я пробовал что-то вроде этого:

extern bool bar;
void foo(){
  if(bar){
    Foo<int> v;
  }
}

но, похоже, этого не произошло.

(Если вы действительно хотите знать, почему я, черт возьми, этого хочу - это связано с этим вопросом, где вместо явного создания экземпляра шаблона с template class Foo<int> я просто хочу иметь возможность написать Foo<int> v, поскольку во многих случаях это проще, поскольку он неявно создает экземпляры всех необходимых функций и прекрасно работает в режиме отладки без оптимизации ...)

UPDATE:

Вот что я хочу сделать (в качестве скомпилированного мини-примера):

foo.h (такие файлы предоставляются мне без изменений)

template<class T>
struct Foo {
  T val_;
  Foo(T val) : val_(val) {
      // heavy code, long compile times
  }
};

Foo-instantiation.cpp

#include "foo.h"
void neverCalled() {
  Foo<int> f(1);
}

// The standard way to instantiate it is this:
// template class Foo<int>;
// but in reality it is often hard to find out 
// exactly what types I have to declare.
// Usage like Foo<int> f(1); will instantiate all
// dependent types if necessary.

foo-decl.h (интерфейс, который я извлек из foo.h)

template<class T>
struct Foo {
  T val_;
  Foo(T val); // no heavy code, can include anywhere and compile fast
};

main.cpp

#include <iostream>
#include "foo-decl.h"

int main(int argc, char** argv){
  Foo<int> foo(1);
  return 0;
}

Компиляция (без оптимизации)

g++ -c main.cpp
g++ -c foo-instantiation.cpp
g++ main.o foo-instantiation.oo

Компиляция (оптимизация)

g++ -O2 -c main.cpp
g++ -O2 -c foo-instantiation.cpp
g++ main.o foo-instantiation.oo
main.o(.text+0x13): In function `main':
: undefined reference to `Foo<int>::Foo(int)'
collect2: ld returned 1 exit status
  • Вместо этого я попробовал предварительно скомпилированные заголовки, но метод создания экземпляров шаблона значительно ускоряет компиляцию.
  • Компиляция foo-instantiation.cpp без оптимизации не так идеальна, потому что тогда код библиотеки (foo.h и другие) будет работать медленнее.

Ответы [ 7 ]

7 голосов
/ 21 марта 2009

Вы сталкиваетесь с правилом единого определения. В одном файле у вас есть определение:

template<class T>
struct Foo {
  T val_;
  Foo(T val) : val_(val) {
      // heavy code, long compile times
  }
};

и в другом другом определении:

template<class T>
struct Foo {
  T val_;
  Foo(T val); // no heavy code, can include anywhere and compile fast
};

Это явно не разрешено в C ++ (допускается только одно идентичное определение), и если вы нарушаете правило, ваш код может иногда казаться работающим, но на самом деле у вас есть страшное «неопределенное поведение» - все может случиться в зависимости от фаза луны (но, скорее, внутреннее состояние компилятора в определенные критические моменты).

По сути, вы не можете написать такой код - извините.

4 голосов
/ 21 марта 2009

Компилятор не может оптимизировать тело функции вне зависимости от того, объявляете ли вы это extern или нет, поскольку он не может знать, что функция не вызывается из другого модуля компиляции. Он может оптимизировать его, если вы объявите его static , но я не верю, что на самом деле это делают какие-либо компиляторы.

Компилятор может оптимизировать вызовы функций на месте:

while(false) {
  foo();
}

Выше можно опустить вызов foo ().

OTOH, компоновщик может удалить тела функций из конечного исключаемого числа, если они не были вызваны.

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

2 голосов
/ 21 марта 2009

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

void instantiation()
{
   Foo<int> f;
   f; // mark the variable as if it is used.
}

// or:
Foo<int>* instantiation()
{
   Foo<int> *p = new Foo<int>();
   return p; // The compiler cannot know if p will be used, it must compile
}

Лучшим решением было бы явное создание экземпляра шаблона, если вы хотите:

// .h
template <typename T>
class Foo
{
public:
   Foo( T const & value );
   void set( T const & ); // whatever else
private:
   T value_;
};

// template implementation another file, not included from .h
// instantiation.cpp??
template <typename T>
Foo<T>::Foo<T>( T const & value ) : value_(value) {}

template <typename T>
void Foo<T>::set( T const & v )
{
   value_ = value;
}

// explicit instantiation
template class Foo<int>;
template class Foo<double>;

// test.cpp
#include "header.h"
int main()
{
    Foo<int> f(5);
    f.set( 7 );

    Foo<char> f2; // linker error Foo<char>() not defined
}

Код пользователя будет видеть только заголовок и знать, какие методы существуют, но не реальную реализацию. Реализация будет скомпилирована в одном модуле компиляции, где происходит явная реализация шаблона.

Обратите внимание, что если вы забудете явно создать экземпляр одного типа, это будет ошибка компоновщика, а не ошибка компиляции.

Правило единого определения

Одно правило определения в c ++ гласит, что может быть только одно определение для каждого символа или класса. Наличие нескольких определений может быть легко обнаружено для обычных символов (если вы определите два void f() { }, компоновщик обнаружит дублированный символ), но это немного сложнее с шаблонами. С шаблонами это сложнее, так как они обычно объявляются и определяются в заголовочных файлах. Компилятор генерирует используемые символы в каждой единице компиляции [1], и компоновщик обычно находит более одного эквивалентного символа (std :: vector :: push_back () компилируется в каждую единицу компиляции, которая имеет std :: vector и вызывает push_back)

Компилятор помечает шаблонный код как «слабый» символ, означающий, что, хотя этот символ определен здесь, он также может быть определен в другом модуле компиляции, и компоновщик может отказаться от символа, не приводя к ошибке ссылки. Это необходимо, если вы хотите связать разные модули компиляции, которые используют одни и те же средства STL, например, с одинаковыми типами.

До gcc 4.2, gcc linux linker отбрасывает все, кроме одного из слабых символов, без дальнейшей проверки. Некоторые линкеры (gcc linker в linux будет в ближайшем будущем, а не как 4.2, не знают ни 4.3, ни 4.4, это может быть там), проверяют, что разные «слабые» символы на самом деле одинаковы и выдают ошибку / предупреждение пользователю.

Ваш код нарушает ODR в том смысле, что вы переопределяете шаблон в другом месте. Вы должны объявить шаблон один раз и реализовать методы извне, как указано выше. В любом случае, если оба определения совместимы (как и в опубликованном вами фрагменте): все методы и атрибуты-члены в точности совпадают и имеют одинаковые квалификаторы (virtual / const-ness ...), компилятор должен принять их, так как только одно определение (увы, повторяется) шаблона.

[1] Будут скомпилированы только те методы, которые фактически вызываются в коде:

template <typename T>
struct Test
{
   void f() { std::cout << "f()" << std::endl; }
   void g() { std::cout << "g()" << std::endl; }
};
int main()
{
   Test<int> t;
   t.f(); // compiler generates Test<int>::f, but not Test<int>::g
}
2 голосов
/ 21 марта 2009

поиск в документации по теме # pragma . Это определение является своего рода аварийным штриховкой, которая позволяет указывать все виды свойств. gcc поддерживает, поэтому есть хорошая ставка, что и g ++ тоже. Имейте в виду, что они, вероятно, не являются переносимыми, что может или не может иметь значение для вашего проекта.

1 голос
/ 21 марта 2009

Обычно это делается с помощью директивы компилятора. В C это будет #pragma, а в Delphi Pascal это {$ O-}, {$ O +} вокруг рассматриваемого кода. Точный синтаксис и метод зависят от конкретной реализации, поэтому необходимо проверить документацию для любой используемой вами системы.

Непростая оптимизация функции довольно проста, но один или два раза я видел случаи, когда необходимо было сказать компилятору не оптимизировать конкретный код. Это крайне редко и не то, с чем я сталкивался очень давно, но это может иногда случаться. В случаях, когда это происходит, обычно происходит компиляция с использованием какого-то старого унаследованного кода, который был создан до более поздней разработки в технологии CPU, - гипертрединг является классическим примером.

0 голосов
/ 17 июня 2009

Объявите вашу переменную как volatile:

volatile Foo<int> v;

Обычно это предотвращает любые оптимизации. Я проверил это с помощью компилятора Intel C ++ и Microsoft Visual Studio 2008.

0 голосов
/ 21 марта 2009

С руки я не уверен. Возможно, Предварительно скомпилированные заголовки решат эту проблему, хотя?

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...