2016-11-29 2 views
16

Я заметил, что иногда даже если я не использую iostream и связанные библиотеки ввода-вывода, мои двоичные файлы, созданные Mingw, все еще были необоснованно большими.gcc: Strip unused functions

Например, я написал код для использования только vector и cstdio и скомпилировал его с помощью -O2 -flto, моя программа может достигать 2 МБ! Я запустил nm main.exe > e.txt и был потрясен, увидев все связанные с ним функции iostream.

После некоторого поиска в Google, я научился использовать -ffunction-sections -Wl,-gc-sections, что уменьшает размер программы от 2 МБ до ~ 300 КБ (если с -s, 100 + КБ). Отлично!

Для дальнейшей проверки эффекта -ffunction-sections -Wl,-gc-sections, вот еще один код:

#include <cstdio> 
#include <vector> 
#include <tuple> 
#include <algorithm> 
#include <chrono> 
#include <windows.h> 

#undef min 

struct Point { 
    int x, y; 
}; 

constexpr int length = 5; 
constexpr int half_length() { 
    return length & 1 ? length : length - 1; 
} 

template<class F> 
int func_template(F&& f) { 
#ifdef _MSC_VER 
    puts(__FUNCSIG__); 
#else 
    puts(__PRETTY_FUNCTION__); 
#endif 
    printf("\n"); 
    return f(); 
} 

struct fake_func { 
    int operator()() const { return 59; }; 
}; 

template<class F, class... Args> 
int pass_args(F&& f, Args&&... args) { 
#ifdef _MSC_VER 
    puts(__FUNCSIG__); 
#else 
    puts(__PRETTY_FUNCTION__); 
#endif 
    printf("\n"); 
    return f(std::forward<Args>(args)...); 
} 

template<class T> 
T min(T x) { 
    return x; 
} 

template<class T, class... Args> 
T min(T x, Args... args) { 
    T y = min(args...); 
    return x < y ? x : y; 
} 

void type_verifier(int x) { 
    printf("%dd ", x); 
} 

void type_verifier(char x) { 
    printf("'%c' ", x); 
} 

void type_verifier(double x) { 
    printf("%lff ", x); 
} 

template<class T> 
void type_verifier(T x) { 
    printf("unknown "); 
} 

template<class T, class... Args> 
void type_verifier(T x, Args... args) { 
    type_verifier(x); 
    type_verifier(args...); 
} 

int bufLen; 
char buf[100]; 

template<class... Args> 
inline int send(Args... args) { 
    bufLen = sprintf(buf, std::forward<Args>(args)...); 
    return bufLen; 
} 

namespace std { 

inline namespace v1 { 
    void func() { 
     printf("I am v1\n"); 
    } 
} 

namespace v2 { 
    void func() { 
     printf("I am v2\n"); 
    } 
} 

} 

int main() { 
    std::vector<int> v {1, 2, 3, 4, 5}; 
    for (auto &i : v) printf("%d ", i); 
    printf("\n"); 

    Point p {1, 2}; 
    printf("%d %d\n", p.x, p.y); 

    auto t = std::make_tuple("Hello World", 12); 
    printf("%s %d\n", std::get<0>(t), std::get<1>(t)); 
    int a, b; 
    auto f = []() { return std::make_tuple(1, 2); }; 
    std::tie(a, b) = f(); 
    printf("%d %d\n", a, b); 

    //int test_constexpr[half_length() + 4]; 

    int ft = func_template([]{ return 42; }); 
    printf("func_template: %d\n", ft); 
    ft = func_template(fake_func {}); 
    printf("func_template: %d\n", ft); 
    ft = pass_args([](int x, int y) { return x + y; }, 152, 58); 
    printf("pass_args: %d\n", ft); 
    ft = pass_args([](int n, const char *m) { 
     for (int i = 0; i < n; i++) printf("%c ", m[i]); 
     printf("\n"); 
     return 0; 
    }, 5, "Hello"); 

    printf("min: %d\n", min(3, 4, 2, 1, 5)); 
    type_verifier(12, 'A', 0.5, "Hello"); 
    printf("\n"); 

/* send("Hello World"); 
    send("%d", 1); 
    send("%d", "1234"); 
    sprintf(buf, "%d", "123");*/ 

    std::func(); 
    std::v1::func(); 
    std::v2::func(); 

    std::rotate(v.begin(), v.begin() + 2, v.end()); 
    for (auto &i : v) printf("%d ", i); 
    printf("\n"); 

    auto start = std::chrono::steady_clock::now(); 

    std::vector<int> x {2, 4, 2, 0, 5, 10, 7, 3, 7, 1}; 
    printf("insertion sort: "); 
    for (auto &i: x) printf("%d ", i); 
    printf("\n"); 
    // insertion sort 
    for (auto i = x.begin(); i != x.end(); ++i) { 
     std::rotate(std::upper_bound(x.begin(), i, *i), i, i+1); 
     for (auto &j: x) printf("%d ", j); 
     printf("\n"); 
    } 

    std::vector<int> heap {7, 5, 3, 4, 2}; 
    std::make_heap(heap.begin(), heap.end()); 
    std::pop_heap(heap.begin(), heap.end()); 
    printf("Pop heap (%d)\n", heap.back()); 
    heap.pop_back(); 
    heap.push_back(1); 
    std::push_heap(heap.begin(), heap.end()); 
    std::sort_heap(heap.begin(), heap.end()); 
    for (auto &i: heap) printf("%d ", i); 
    printf("\n"); 

    auto end = std::chrono::steady_clock::now(); 
    auto diff = end - start; 
    printf("time: %I64d ms\n", 
     std::chrono::duration_cast<std::chrono::milliseconds>(diff).count()); 

    { 
     auto u = v; 
     std::move_backward(u.begin(), u.begin() + u.size() - 1, u.begin() + u.size()); 
     for (auto &i : u) printf("%d ", i); 
     printf("\n"); 
    } 

    { 
     auto u = v; 
     std::move(u.begin() + 1, u.begin() + u.size(), u.begin()); 
     for (auto &i : u) printf("%d ", i); 
     printf("\n"); 
    } 

    start = std::chrono::steady_clock::now(); 
    Sleep(2000); 
    end = std::chrono::steady_clock::now(); 
    diff = end - start; 
    printf("time: %I64d ms\n", 
     std::chrono::duration_cast<std::chrono::milliseconds>(diff).count()); 

    std::chrono::steady_clock::time_point before; 
    before = std::chrono::steady_clock::now(); 
    Sleep(2000); 
    auto after = std::chrono::steady_clock::now(); 
    printf("%f seconds\n", std::chrono::duration<double>(after - before).count()); 

    return 0; 
} 

К моему разочарованию, окончательная программа еще раз> 2MB.

Интересно, cl.exe вдумчиво удалить все функции, связанные с iostream последовательно, даже если я не использовал /O2 или любые другие флаги, только cl.exe main.cpp. (Для кода выше cl.exe производит 100 + KB двоичный код).

Я пропустил какие-либо другие полезные флаги gcc для этого?

Спецификация:

  • Mingw-w64 GCC 6.1.0
  • Mingw-w64 GCC 6.2.0
  • Visual Studio 2017 RC
  • Все двоичные файлы связаны статически

Сравнение с Linux

Я сравнил двоичные файлы, произведенные gcc 4.9.2 (Linux) и gcc 4.9.3 (mingw-w64) для вышеуказанного кода (кроме windows.h и Sleep).

Compile флаг

g++ -o c++11 c++11.cpp -std=c++11 -static-libgcc -static-libstdc++ -ffunction-sections -Wl,-gc-sections -O2

Linux НКУ действительно успешно стирает iostream и функцию без необходимости -flto в то время как Mingw-w64 НКА просто не может сделать это правильно.

Windows поддерживает только формат PE, в то время как Linux поддерживает формат ELF, позволяя Linux использовать Gold-линкер. Может быть, это объяснение?

Update

я в конце концов подал ошибку в https://sourceforge.net/p/mingw-w64/bugs/578/. Будем надеяться, что это привлечет внимание!

+0

Это может помочь: [Как удалить неиспользуемые символы C/C++ с помощью GCC и ld?] (Https: // stackoverflow.com/questions/6687630/how-to-remove-unused-cc-symbols-with-gcc-and-ld) – benbuck

+0

Пробовал все: -O (уменьшить 2KB), -fwhole-program (без изменений), -fomit-frame -поинтер (без изменений). -why_live недоступен. –

+0

'длина & 1? length: length - 1' можно изменить на 'length + (length & 1) - 1' –

ответ

2

Попробуйте удалить информацию об отладке и символе из статического libstdC++ через -Wl,--strip-all. Это уменьшило мой исполняемый файл с 9M до 670K на Cygwin (13x) и с 6M до 80K на Ubuntu (80x).

+0

' -Wl, - strip-all' совпадает с '-s', о котором я упоминал выше. Отключение информации об отладочной информации и таблицы символов не снимает неиспользуемые функции, связанные с iostream, что MSVC делает без объяснения (никаких специальных флагов не требуется)! –

+0

Хорошо, я вижу. FYI a 4.8.2 x86_64-w64-mingw32-g ++ (Ubuntu 14.04) с '-std = C++ 11 -O2 -ffunction-sections -Wl, -gc-sections -static-libstdC++ -s' генерирует исполняемый файл 100K, поэтому проблема размера может быть специфичной для конкретной версии инструментальной цепочки (и, следовательно, сложнее исследовать удаленно). Вы пробовали анализировать карту компоновщика ('-Wl, - print-map')? – yugr

+0

Фактически 200K (искал неправильный файл). – yugr

Смежные вопросы