2016-11-16 1 views
1

私はAndroidで使用するライブラリを持っていますが、問題はAndroid固有の問題ではないと確信しています。 このライブラリには、logcatに出力する一連のエラーコードが含まれており、すべてが定数文字列で構成されています。ライブラリの.soファイルの `const char *`ストレージの奇妙な動作

... 
if(...){ALOGE("Error in parameter XXXXXX");} 
if(...){ALOGE("Error in parameter YYYYYY");} 
if(...){ALOGE("Error in parameter ZZZZZZ");} 
... 

今日私は自分の.rodataセクション(約16kB)に大量のデータがあることに気付きました。だから私はstrings mylib.soを実行し、それらの文字列の束を得た。

Error in parameter XXXXXX 
Error in parameter YYYYYY 
Error in parameter ZZZZZZ 

Iしかし、私は2つの部分で文字列を分割場合(これらのコードはめったに使用されているので、問題ないはずですが)印刷の小さな余分なコストで、私はスペースで多くを救うことができること。その後、コンパイラはジョブを実行し、共通部分を1つの文字列にグループ化する必要があります。コンパイラには、文字列削除の最適化ステップ(CLANGとGCC)が重複しているためです。

私はそれをこのように行った:()私は定義(これは簡単なテストだった使用する必要があります知っている、私はこれらの多くを持っていますが、それらはすべて、このようなパターンを持っている)

... 
if(...){ALOGE("Error in parameter %s","XXXXXX");} 
if(...){ALOGE("Error in parameter %s","YYYYYY");} 
if(...){ALOGE("Error in parameter %s","ZZZZZZ");} 
... 

何私が見つけたのは:

  1. ライブラリーは正確に同じサイズです。 .rodataは現在かなり小さくなりますが、.textはほぼ同じ量だけ増加しています。 (数バイトの違いのみ)
  2. stringsコマンドは今度は"Error in parameter %s"文字列と分離された部分だけを出力します。したがって、文字列のマージは行われません。
  3. だから

..私は32ビット、64-ビットなどでコンパイルした場合には関係していないよう、ここで何が起こっているの?どうすれば修正できますか?どんな指導?コンパイラは何をしていますか? おかげ

エクストラデータ:

  • コンパイラCLANG 4.9(4.8は、同じ結果を行います)。
  • フラグ:-Os -fexceptions -std = C++ 11 -fvisivility =隠さ

EDIT:

私はGCCと同じ結果にOnline GCC

を使用してオンラインサンプルテストを作成

分割:

#include <stdio.h> 
int main() 
{ 
    int a = rand()%7; 
    switch(a){ 
     case 0: printf("Hello, %s!\n","Anna"); break; 
     case 1: printf("Hello, %s!\n","Bob"); break; 
     case 2: printf("Hello, %s!\n","Clark"); break; 
     case 3: printf("Hello, %s!\n","Danniel"); break; 
     case 4: printf("Hello, %s!\n","Edison"); break; 
     case 5: printf("Hello, %s!\n","Foo"); break; 
     case 6: printf("Hello, %s!\n","Garret"); break; 
    } 
    return 0; 
} 

NonSp点灯:

でコンパイル
#include <stdio.h> 
int main() 
{ 
    int a = rand()%7; 
    switch(a){ 
     case 0: printf("Hello, Anna!\n"); break; 
     case 1: printf("Hello, Bob!\n"); break; 
     case 2: printf("Hello, Clark!\n"); break; 
     case 3: printf("Hello, Danniel!\n"); break; 
     case 4: printf("Hello, Edison!\n"); break; 
     case 5: printf("Hello, Foo!\n"); break; 
     case 6: printf("Hello, Garret!\n"); break; 
    } 
    return 0; 
} 

gcc -Os -o main main.c 
gcc -Os -o main2 main2.c 

サイズ:

-rwxr-xr-x 1 20446 20446 8560 Nov 16 11:43 main      
-rw-r--r-- 1 20446 20446 478 Nov 16 11:41 main.c 
-rwxr-xr-x 1 20446 20446 8560 Nov 16 11:42 main2 
-rw-r--r-- 1 20446 20446 443 Nov 16 11:39 main2.c 

文字列:

strings main2 | grep "Hello"         
Hello, Anna!               
Hello, Bob!               
Hello, Clark!               
Hello, Danniel!              
Hello, Edison!              
Hello, Foo!               
Hello, Garret! 

    strings main | grep "Hello"         
Hello, %s!               
+1

コード「.text」がコード – Danh

+1

に使用されていますどのコンパイラを使用していますか?このCコードかC++コードですか?どのような最適化設定を使用していますか? –

+0

これはC++であり、フラグは本当に特別なものではありません。ただ-Oです。私が正しく覚えていればコンパイルは4.9です。(ndk r13b) – DarkZeros

答えて

2

すべてのあなたの期待はかなり正しいですが、テストケースではその効果を実証するには十分ではありません。まず、バイナリ実行可能ファイルには、 "セグメント/セクションアラインメント"(または、このようなもの)という概念があります。簡単に言えば、異なるセクションの最初のバイトは、ある値の倍数であるファイルオフセット(例えば、十進数512)にのみ置くことができます。セクション間の未使用スペースは、この要件を満たすためにゼロで埋められます。テストケースで提供されたすべてのデータは、そのパディングを使い果たしていないため、実際の違いを感じることはできません。次に、効果をより明確に比較したい場合は、スタートアップコードとリンクしないでください。つまり、通常の実行可能ファイルではなく、最小数の参照で動的ライブラリを構築する必要があります。

次に、私のテストプログラムです。それはあなたのものと少し異なります。しかしそれほど概念的ではない。その後

#include <stdio.h> 

#if defined(_SPLIT) 
#define LOG(str) printf("Very very very loooo-o-o-o-o-o-o-ooooong prefix %s", str) 
#elif defined(_NO_SPLIT) 
#define LOG(str) printf("Very very very loooo-o-o-o-o-o-o-ooooong prefix " str) 
#else 
#error "Don't know what you want." 
#endif 

int foo(void) { 
    LOG("aaaaaaaa"); 
    LOG("bbbbbbbb"); 
    LOG("cccccccc"); 
    LOG("dddddddd"); 
    LOG("eeeeeeee"); 
    LOG("ffffffff"); 
    LOG("gggggggg"); 
    LOG("hhhhhhhh"); 
    LOG("iiiiiiii"); 
    LOG("jjjjjjjj"); 
    LOG("kkkkkkkk"); 
    LOG("llllllll"); 
    LOG("mmmmmmmm"); 
    LOG("nnnnnnnn"); 
    LOG("oooooooo"); 
    LOG("pppppppp"); 
    LOG("qqqqqqqq"); 
    LOG("rrrrrrrr"); 
    LOG("ssssssss"); 
    LOG("tttttttt"); 
    LOG("uuuuuuuu"); 
    LOG("vvvvvvvv"); 
    LOG("wwwwwwww"); 
    LOG("xxxxxxxx"); 
    LOG("yyyyyyyy"); 
    LOG("zzzzzzzz"); 
    return 0; 
} 

、ダイナミック・ライブラリーを作成することができます:

$ gcc --shared -fPIC -o t_no_split.so -D_NO_SPLIT test.c 
$ gcc --shared -fPIC -o t_split.so -D_SPLIT test.c 

とサイズを比較します

IMO
-rwxr-xr-x 1 sysuser sysuser 12098 Nov 16 14:19 t_no_split.so 
-rwxr-xr-x 1 sysuser sysuser 8002 Nov 16 14:19 t_split.so 

を、実際に顕著な違いがあります。正直言って、私はセクションごとのサイズをチェックしていませんが、とにかく自分でそれを行うことができます。

もちろん、分割されていない文字列は分割された文字列よりも12098 - 8002バイト多く使用されるわけではありません。コンパイラ/リンカはt_split.soの場合よりも多くのスペースをt_no_split.soに使用する義務があることを意味します。そして、この膨れは間違いなく文字列サイズの違いによるものです。もう1つ興味深いのは、2番目の引数をprintf()に渡すことによって引き起こされるマシンコードの小さな膨張を中和します。

P.S.私のマシンはx64 Linux、GCC 4.8.4です。

+0

ありがとう、ありがとう、私は理解する!確かに、魔法の4Kサイズが爆発する(正確には4096)ので、おそらくそれが理由です。コードサイズをフルページに減らすことができない場合、コンパイラは、ページに1バイトしか保持していなくても、0に設定された '.rodata'にページを割り当てます。だから私はさらに弦を減らすか、何もしないでください。明日は適切なコードを調べて.rodataのページ数を確認します。 – DarkZeros

+0

実際、私は自分の '.rodata'に0x66f8を使っていました。私の最適化では、0x60f0。しかし、ページのサイズが0x1000なので、サイズの低下は全く見られませんでした。しかし、これらの最適化が可能であることを知っておくとよいでしょう! – DarkZeros

+0

@DarkZerosそれにもかかわらず、それはうまくいくでしょうし、将来、いくつかのトリックや最適化と共に役立つでしょう。 – Sergio

1

あなたが唯一の文字列ごとに19のバイトを保存するが、コストでいます追加の議論を何に渡すかvarargs関数のように見えます。最低でも、ロードアドレスとプッシュです。

私が推測させてください、ALOGEは実際にマクロですか?

void BadParameter(const char * paramName) 
{ 
    ALOGE("Error in parameter %s", paramName); 
} 

...と、それにすべてのコールを置き換える:あなたのような(ないインライン)機能を必要とする -

は、私はあなたがDEFINE必要はないと思います。

+0

私は理解しますが、私の実際のコードは長い文字列を持っています。私は文字列の約1kBを節約しなければならないという計算をしましたが、奇妙なことはサイズがまったく同じことです。私は同じ結果でオンラインの例を出しました。 – DarkZeros

+1

実行可能ファイルのサイズは、ラウンド数のページである可能性が高いことに注意してください。私はこの文脈でどれほど大きな "ページ"があるのか​​分かりませんが、4kは可能です。 –