Chez ouam

/home/jmfrouin

View on GitHub
3 February 2013

C++ et l'embarqué sous GNU/Linux

by Jean-Michel Frouin

Retirer les patrons STL

Introduction

L'utilisation de la STL dans des projets embarqués pose un problème majeur, celui de la duplication de code dans les objets binaires. En effet les patrons (Qui sont des morceaux de code génériques) sont spécialisés durant la précompilation. On se retrouve vite avec plusieurs kilos de code pour rien. Pour contourner ce problème (Dû aux contraintes du monde embarqué.) il suffit d'encapsuler l'ensemble des patrons utilisés dans une classe à part. Ainsi le code des patrons de la STL sera centralisé dans un binaire, celui de la classe qui encapsule les patrons.
Imaginons qu'un binaire utilise abondamment les patrons STL. Tout d'abord il faut identifier les patrons STL utilisés par le binaire. Il est possible de récupérer la liste en utilisant : nm -C binaire.o.

Etude de cas

Prenons l'exemple, d'un binaire qui utilise les patrons STL des std::string :

U __gxx_personality_v0
00000b56 T main
U _Unwind_Resume
00000000 T f1()
000005a8 T f2()
U std::string::compare(char const*) const
U std::allocator::allocator()
U std::allocator::~allocator()
U std::string::append(char const*)
U std::string::operator=(std::string const&)
U std::basic_string<char, std::char_traits, std::allocator >::basic_string(char const*, std::allocator const&)
U std::basic_string<char, std::char_traits, std::allocator >::basic_string(std::string const&)
U std::basic_string<char, std::char_traits, std::allocator >::~basic_string()
U std::string::operator+=(char const*)
00000000 W bool std::operator!=<char, std::char_traits, std::allocator >(std::basic_string<char, std::char_traits, std::allocator > const&, char const*)
00000000 W std::basic_string<char, std::char_traits, std::allocator > std::operator+<char, std::char_traits, std::allocator >(std::basic_string<char, std::char_traits, std::allocator > const&, char const*)

On voit, clairement, que ce binaire utilise abondamment le patron STL des strings. Pour peu qu'un projet utilise les strings et soit constitué de plusieurs binaires, il y a aura une duplication du code STL dans chacun des binaires. Si le but est de produire des binaires de taille minimum, il semble évident qu'un travail est à faire pour récupérer la taille occupée par le code STL.

Pour ce faire, il suffit d'encapsuler tout le code utilisé dans une classe pour centraliser l'utilisation des patrons. Le code de l'objet CString encapsule les méthodes utilisées par ce binaire :

Définition la classe CString

#ifndef __CSTRING_H__
#define __CSTRING_H__

#include <string>
#include <iostream>

class CString : protected std::string
{
  friend std::ostream& operator<<(std::ostream& _os, const CString& _string);

  public:
    static const size_t npos = std::string::npos;

  public:
    CString();
    CString(const char* _name);
    CString(const CString& _name);
    ~CString();

    //From std::string
    const CString operator+(const char* _string);
    const CString operator+(const CString& _string);
    CString& operator+=(const char* _string);
    CString& operator+=(const CString& _string);
    CString& operator=(const CString& _string);
    bool operator==(const char* _string) const;
    bool operator==(const CString& _string) const;
    bool operator!=(const char* _string) const;
    bool operator<(const char* _string) const;
    bool operator<(const CString& _string) const;
    bool operator>(const char* _string) const;
    bool operator>(const CString& _string) const;

    const char* c_str() const;
    size_t find(char _char) const;
    size_t find_last_of(char _char) const;
    CString substr(size_t _pos, size_t _npos)const;
    CString substr(int _nb)const;
    CString& append(const CString& _string);
    CString& assign(const CString& _string);
    size_t length() const;
};
#endif //__CSTRING_H__

Et l'implémentation :

#include "cstring.h"

CString::CString()
{
}

CString::CString(const char* _name)
:std::string(_name)
{
}

CString::CString(const CString& _name)
{
  const std::string* l_tmp;
  l_tmp = &_name;
  std::string::assign(*l_tmp);
}

CString::~CString()
{
}

const CString CString::operator+(const char* _string)
{
  std::string l_tmp((*this).c_str());
  l_tmp += _string;
  CString l_ret(l_tmp.c_str());
  return l_ret;
}

const CString CString::operator+(const CString& _string)
{
  return (*this) + _string.c_str();
}

CString& CString::operator+=(const char* _string)
{
  std::string::operator+=(_string);
  return *this;
}

CString& CString::operator+=(const CString& _string)
{
  const std::string* l_tmp;
  l_tmp = &_string;
  std::string::operator+=(*l_tmp);
  return *this;
}

CString& CString::operator=(const CString& _string)
{
  const std::string* l_tmp;
  l_tmp = &_string;
  std::string::operator=(*l_tmp);
  return *this;
}

bool CString::operator==(const char* _string) const
{
  bool l_ret;
  std::string l_this((*this).c_str());
  std::string l_tmp(_string);
  l_ret = l_this == l_tmp;
  return l_ret;
}

bool CString::operator==(const CString& _string) const
{
  bool l_ret;
  l_ret = (*this) == _string.c_str();
  return l_ret;
}

bool CString::operator!=(const char* _string) const
{
  bool l_ret;
  std::string l_this((*this).c_str());
  std::string l_tmp(_string);
  l_ret = l_this != l_tmp;
  return l_ret;
}

bool CString::operator<(const char* _string) const
{
  bool l_ret;
  std::string l_this((*this).c_str());
  std::string l_tmp(_string);
  l_ret = l_this < l_tmp;
  return l_ret;
}

bool CString::operator<(const CString& _string) const
{
  bool l_ret;
  l_ret = (*this) < _string.c_str();
  return l_ret;
}

bool CString::operator>(const char* _string) const
{
  return ((*this).c_str() > _string);
}

bool CString::operator>(const CString& _string) const
{
  const std::string* l_tmp;
  l_tmp = &_string;
  return ((*this).c_str() > (*l_tmp));
}

const char* CString::c_str() const
{
  const std::string* l_tmp;
  l_tmp = this;
  return (*l_tmp).c_str();
}

size_t CString::find(char _char) const
{
  return std::string::find(_char);
}

size_t CString::find_last_of(char _char) const
{
  return std::string::find_last_of(_char);
}

CString CString::substr(size_t _pos, size_t _npos)const
{
  std::string l_tmp = std::string::substr(_pos, _npos);
  return CString(l_tmp.c_str());
}

CString CString::substr(int _nb) const
{
  std::string l_tmp = std::string::substr(_nb);
  return CString(l_tmp.c_str());
}

CString& CString::append(const CString& _string)
{
  const std::string* l_tmp;
  l_tmp = &_string;
  std::string::append(*l_tmp);
  return *this;
}

CString& CString::assign(const CString& _string)
{
  const std::string* l_tmp;
  l_tmp = &_string;
  std::string::assign(*l_tmp);
  return *this;
}

size_t CString::length() const
{
  return std::string::length();
}

std::ostream& operator<<(std::ostream& _os,const CString& _string)
{
  const std::string* l_tmp;
  l_tmp = &_string;
  _os << (*l_tmp);
  return _os;
}

Ainsi le code de la STL sera recopié, durand la précompilation, dans le fichier cstring.cpp et sera donc exclusivement contenu dans le binaire cstring.o.

Enfin, il suffit de vérifier que tout fonctionne correctement en recherchant à nouveau les patrons STL dans le nouveau binaire :

U __gxx_personality_v0
00000892 T main
U _Unwind_Resume
00000000 T f1()
00000446 T f2()
U CString::operator=(CString const&)
U CString::CString(char const*)
U CString::~CString()
U CString::operator+(char const*)
U CString::operator+=(char const*)
U CString::operator!=(char const*) const

Voilà, nos patrons STL des strings ne sont plus dans le binaire.

Tailles des objets binaires

Le binaire utilisant les patrons STL (code/analyse_templates/test_template.cpp) : 33 Ko (Normal), 4.7Ko (Strippée)

Le binaire utilisant le binaire encapsulant les patrons STL (code/analyse_templates/test_template2.cpp) : 31Ko (Normal), 3.4Ko (Strippée)

Binaire encapsulant les patrons STL (code/analyse_templates/cstring.cpp) : 45Ko (Normal), 5.9Ko (Strippée)

Conclusion

On constate que l'on a réussi à gagner 1.3Ko sur l'objet binaire, mais nous avons un nouvel objet binaire de 5.9Ko, donc au final on se retrouve avec beaucoup de code en plus. Il est bien évident que cela est efficace si beaucoup de binaires utilisent les mêmes patrons. TODO : Compléter l'exemple pour que l'on se retrouve au final avec un gain de place.

Supprimer les symboles

Pour supprimer complètement les symboles d'un objet il est possible de faire appel à l'utilitaire strip dont c'est le rôle.

Optimisation des if avec enum

Lorsque l'on veut vérifier qu'une variable (de type enum) :

if((Type == CBaseComponent::eLabel) || (Type == CBaseComponent::eLink) || (Type == CBaseComponent::eCheckBox))

Dont voici le code assembleur généré :

0044E70A cmp dword ptr [Type],8
0044E70E je Interface::CPanel::RebuildComponents+190h (44E720h)
0044E710 cmp dword ptr [Type],9
0044E714 je Interface::CPanel::RebuildComponents+190h (44E720h)
0044E716 cmp dword ptr [Type],7
0044E71A jne 0044EA15

On remarque 3 tests de comparaisons!

On peut en faire uniquement un :

 if((1<<Type) & ((1<<CBaseComponent::eLabel) | (1<<CBaseComponent::eLink) | (1<<CBaseComponent::eCheckBox)))

Dont le code assembleur est :

0044E70A mov eax,1
0044E70F mov ecx,dword ptr [Type]
0044E712 shl eax,cl
0044E714 and eax,380h
0044E719 je 0044EA14
tags: