2016-11-27 4 views
0

ここでは簡単な健全性チェックの質問があります。基本的な要件は、パフォーマンス上の理由から2つの柔軟な配列メンバーを構造体に入れてmallocへの呼び出し回数を減らすことです。voidの上に構造体タイプを実装する*

structインスタンスが定数オフセットで複数のフィールドを含む整列されたメモリブロックであると仮定すると、オフセット計算とキャストを記述することによって、構造体と意味的に同等な機能を実装できますか?アライメント/計算が一貫したオフセットを維持

void f() 
{ 
    typedef struct 
    { 
    double x; 
    char y; 
    int32_t foo; 
    double z; 
    } equivalent; 
    equivalent * e = malloc(sizeof(equivalent)); 
    free(e); 

    static_assert(sizeof(equivalent) == 24,""); 
    char* memory = malloc(24); 
    double* x = (double*) (0 + memory); 
    char* y  = (char *) (8 + memory); 
    int32_t* foo = (int32_t*) (12 + memory); 
    double* z = (double*) (16 + memory); 
    free(memory); 
} 

は面倒ですが、種類と仮定すると、クライアントコードは、そのいずれかを確認する必要はありませんとにかく不透明です。同様に構文上のオーバーヘッドは隠されています。

私はエイリアシングルールをC11(「効果的なタイプ」の部分)で明確にして読んだことがあり、そこにはっきりとしていると思います。

この公正ゲームですか?私は非常に鈍いコードをたくさん書く前に第二の意見を求めると思った。

乾杯

編集:ジョナサン・レフラーに対する応答として、これは私がメモリの単一のブロックにランタイム所定の長さの配列のカップルを置くつもり方法の簡単な&汚いスケッチです。

私は、配列の位置を計算するために使用される整数を格納することをお勧めします。これは、構造体のコピーをより簡単にするためです。適切に初期化されたポインタを格納し、それらをコピーに再配置する方がおそらく高速です。退屈なものは何もありません

void* g(uint64_t N_first, uint64_t N_second) 
{ 
    // desired representation:                                      
    // uint64_t N_first;                                       
    // int32_t first[N_first];                                      
    // uint64_t N_second;                                       
    // double second[N_second];                                      
    // this function doesn't populate the arrays, only                                
    // allocates storage and sets up the length fields                                

    uint64_t bytes_for_lengths = 16; 

    char* bytes = malloc(bytes_for_lengths + bytes_for_first(N_first) + 
         bytes_for_second(N_second)); 

    uint64_t* ptr_N_first = get_N_first(bytes); 
    *ptr_N_first = N_first; 

    uint64_t* ptr_N_second = get_N_second(bytes); 
    *ptr_N_second = N_second; 

    return (void*)bytes; 
} 

// I haven't decided how best to factor out the field access 
// and associated functions yet, so this is not optimal 

uint64_t* get_N_first(void* vdata) 
{ 
    char* data = (char*)vdata; 
    return (uint64_t*)(data + 0); 
} 
int32_t* get_first(void* vdata) 
{ 
    char * data = (char*)vdata; 
    return (int32_t*)(data + 8); 
} 
uint64_t bytes_for_first(uint64_t N_first) 
{ 
    // first is an int32_t                                       
    // the next field needs to be 8 byte aligned                                 
    uint64_t bytes = 4 * N_first; 
    if (bytes % 8 != 0) 
    { 
     bytes += 4; 
    } 
    return bytes; 
} 

uint64_t* get_N_second(void* vdata) 
{ 
    uint64_t n_first = *get_N_first(vdata); 
    uint64_t first_bytes = bytes_for_first(n_first); 
    char* data = (char*)vdata; 
    return (uint64_t*)(data + 8 + first_bytes); 
} 
double* get_second(void* vdata) 
{ 
    char * data = (char*)vdata; 
    uint64_t n_first = *get_N_first(vdata); 
    uint64_t first_bytes = bytes_for_first(n_first); 
    return (double*)(data + 8 + first_bytes + 8); 
} 
uint64_t bytes_for_second(uint64_t N_second) 
{ 
    // second is a double                                       
    return 8 * N_second; 
} 
+0

ここまでは何も問題ありません。不明な点は、これを「構造体内の2つの柔軟な配列メンバー」を実現するためにどのように適応させるかです。あなたはそれを直接行うことはできません。構造体に2つのポインタを使用し、メモリを連続して割り当てることによって、結果を慎重にほぼ達成できますが、注意する必要があります。 –

+0

@JonathanLefflerありがとうございました。私はこれをどのように適応させるつもりのスケッチを加えました。明らかにエラーが起こりやすいのは明らかですが、Cがうまくやってくれれば解決できます。 –

+0

コードをコンパイルした後、これはどのように有効ですか?私は半分まともなコンパイラは、多かれ少なかれ同じコードを生成するか、またはsteuctメンバーにアクセスするために、または手動オフセットを行うための少なくとも速いコードとして少なくともと仮定します。しかし、私はちょうどここにポイントを見て失敗するかもしれない – adam10603

答えて

0

「ダブルVLA」構造タイプを達成するためにストレートフォワード構造を使用する方がクリーンであると感じることはできません。多かれ少なかれこのような:

// desired representation: 
// uint64_t N_first; 
// int32_t first[N_first]; 
// uint64_t N_second; 
// double second[N_second]; 

#include <assert.h> 
#include <inttypes.h> 
#include <stdalign.h> 
#include <stdio.h> 
#include <stdlib.h> 

struct DoubleVLA 
{ 
    uint64_t N_first; 
    int32_t *first; 
    uint64_t N_second; 
    double *second; 
    //double align_32[];  // Ensures alignment on 32-bit 
}; 

extern struct DoubleVLA *alloc_DoubleVLA(uint64_t n1, uint64_t n2); 

struct DoubleVLA *alloc_DoubleVLA(uint64_t n1, uint64_t n2) 
{ 
    struct DoubleVLA *dv = malloc(sizeof(*dv) + n1 * sizeof(dv->first) + n2 * sizeof(dv->second)); 
    if (dv != 0) 
    { 
     dv->N_first = n1; 
     dv->N_second = n2; 
     if (alignof(dv->second) >= alignof(dv->first)) 
     { 
      dv->second = (double *)((char *)dv + sizeof(*dv)); 
      dv->first = (int32_t *)((char *)dv + sizeof(*dv) + n2 * sizeof(dv->second)); 
     } 
     else 
     { 
      dv->first = (int32_t *)((char *)dv + sizeof(*dv)); 
      dv->second = (double *)((char *)dv + sizeof(*dv) + n1 * sizeof(dv->first)); 
     } 
    } 
    return dv; 
} 

int main(void) 
{ 
    struct DoubleVLA *dv = alloc_DoubleVLA(UINT64_C(11), UINT64_C(32)); 
    for (uint64_t i = 0; i < dv->N_first; i++) 
     dv->first[i] = i * 100 + rand() % 100; 
    for (uint64_t j = 0; j < dv->N_second; j++) 
     dv->second[j] = j * 1000.0 + (rand() % 100000)/100.0; 
    for (uint64_t i = 0; i < dv->N_first; i++) 
     printf("%.2" PRIu64 " = %12" PRId32 "\n", i, dv->first[i]); 
    for (uint64_t j = 0; j < dv->N_second; j++) 
     printf("%.2" PRIu64 " = %12.2f\n", j, dv->second[j]); 
    free(dv); 
    return 0; 
} 

あっても32ビットプラットフォーム上で、それが適切直後doubleのアレイのために整列されることを、その大きさは、このような作るために構造の端部に十分にパディングがあるはずその後の構造と配列int32_t。しかし、最初に2つのサイズを入れ、最後に2つのポインタを構造体に入れておくことで回避できる不必要な埋め込みがあります。これは64ビットプラットフォームでは問題ありません。オプションのalign_32 VLAは、アラインメント要件がint32_tで、アラインメント要件がdoubleより大きくないと仮定しています。奇妙な配置制限や要件があったとしても、構造体が正しく埋められていることを保証します。制約が満たされているという静的アサーションを提供することは可能です。

alignofの材料はC11由来です。それはあなたが異なるアライメント要件を持つ2つのタイプを扱うことを可能にし、自動的により良いレイアウトを選択します。

この組織では、構造のセクションへの機能的なインターフェイスは必要ありません。直接アクセスは簡単で簡単に理解できます。

+0

私はこれが好きです、それを書いてくれてありがとう。私はポインタのアドレスルックアップをキャッシュする価値があるかどうかは分かりませんが、構造体に固定フィールドを置くと、構造体+配列のための十分なスペースを確保することで多くの定型文が削除されます。ニース。 –

2

...

size_t offset_of_x = offsetof(equivalent, x); 
size_t offset_of_y = offsetof(equivalent, y); 
size_t offset_of_foo = offsetof(equivalent, foo); 
size_t offset_of_z = offsetof(equivalent, z); 

char* memory = malloc(sizeof(equivalent)); 
double* x = offset_of_x + memory; 
char* y  = offset_of_y + memory; 
int32_t* foo = offset_of_foo + memory; 
double* z = offset_of_z + memory; 
free(memory); 

そして、はい、これは完全に合法です。

(あなたの編集後)

/編集:

代わりにこの表現を使う:

struct fake_your_version { 
    uint64_t N_first; 
    int32_t first[N_first]; 
    uint64_t N_second; 
    double second[N_second]; 
}; 

あなたはどちらか、この表現を使用して検討する必要があります。

struct fake_alternative_1 { 
    uint64_t size; // max over all num[i] 
    uint64_t num[2]; // num[0] being for first, num[1] being for second 
    struct { 
     int32_t first; 
     double second; 
    } entry[num]; 
}; 

またはこの表現:

struct fake_alternative_2 { 
    uint64_t num[2]; 
    void * data[2]; // separate malloc(num[i] * sizeof(whatever)); 
}; 

あなたのアプローチでは、最後の配列以外のサイズ変更でデータの移動を強制するためです。

fake_alternative_1また、mallocを保存します(パディングバイトとアレイのサイズが異なる必要がある場合はメモリを犠牲にして)。

あなたもこれを行うことを検討する前mallocが本当にその遅いですwheather、あなたは本当にあなたがそれを避けなければならないこと、youselfに依頼する必要があります。おそらく、あなたが何をしているにしても、malloc以外の何かがあなたの速度を落としています(おそらく、mallocを保存しようとすると、より高速ではなくさらに遅くなります)。

fake_alternative_2は、それぞれの配列が自分のmallocということを受け入れるだけですが、私はあなたにこの代替手段を与えることで何も新しいことを言いません)。

+1

ちょうど 'offsetof()'を使うことができます:https://en.wikipedia.org/wiki/Offsetof –

+1

ああ、私はそれが公式であることは知らなかったが、実際にはC89以来そこにいる... –

関連する問題