アクセサ関数を使用しない理由はありますか?
基本的なケースには、構造化データとプレーンデータの2つがあります。構造化データにはさまざまなデータ型があり、プレーンデータはリストしたものの1つの形式のみです。使用するタイプごとにプロトタイプ値(個別のバイトコンポーネントを含む)を格納できる場合は、透過的なエンディアン補正をサポートすることもできます(リストしたすべてのタイプで合計28バイトまたは30バイト-複合タイプは単なるペアであり、同じです基本コンポーネントとしてのバイトオーダー)。私はこのアプローチを使用して、分子動力学シミュレーションからの原子データを保存およびアクセスしました。実際には非常に効率的です。これは、私がテストした中で最速のポータブルなものです。
構造を使用して「ファイル」(バッキングファイル、メモリマップ、エンディアン、プレーンデータの場合はデータ形式)を記述します。
struct file {
int descriptor;
size_t size;
unsigned char *data;
unsigned int endian; /* Relative to current architecture */
unsigned int format; /* endian | format, for unstructured files */
};
#define ENDIAN_I16_MASK 0x0001
#define ENDIAN_I16_12 0x0000
#define ENDIAN_I16_21 0x0001
#define ENDIAN_I32_MASK 0x0006
#define ENDIAN_I32_1234 0x0000
#define ENDIAN_I32_4321 0x0002
#define ENDIAN_I32_2143 0x0004
#define ENDIAN_I32_3412 0x0006
#define ENDIAN_I64_MASK 0x0018
#define ENDIAN_I64_1234 0x0000
#define ENDIAN_I64_4321 0x0008
#define ENDIAN_I64_2143 0x0010
#define ENDIAN_I64_3412 0x0018
#define ENDIAN_F16_MASK 0x0020
#define ENDIAN_F16_12 0x0000
#define ENDIAN_F16_21 0x0020
#define ENDIAN_F32_MASK 0x00C0
#define ENDIAN_F32_1234 0x0000
#define ENDIAN_F32_4321 0x0040
#define ENDIAN_F32_2143 0x0080
#define ENDIAN_F32_3412 0x00C0
#define ENDIAN_F64_MASK 0x0300
#define ENDIAN_F64_1234 0x0000
#define ENDIAN_F64_4321 0x0100
#define ENDIAN_F64_2143 0x0200
#define ENDIAN_F64_3412 0x0300
#define FORMAT_MASK 0xF000
#define FORMAT_I8 0x1000
#define FORMAT_I16 0x2000
#define FORMAT_I32 0x3000
#define FORMAT_I64 0x4000
#define FORMAT_P8 0x5000 /* I8 pair ("complex I8") */
#define FORMAT_P16 0x6000 /* I16 pair ("complex I16") */
#define FORMAT_P32 0x7000 /* I32 pair ("complex I32") */
#define FORMAT_P64 0x8000 /* I64 pair ("complex I64") */
#define FORMAT_R16 0x9000 /* BINARY16 IEEE-754 floating-point */
#define FORMAT_R32 0xA000 /* BINARY32 IEEE-754 floating-point */
#define FORMAT_R64 0xB000 /* BINARY64 IEEE-754 floating-point */
#define FORMAT_C16 0xC000 /* BINARY16 IEEE-754 complex */
#define FORMAT_C32 0xD000 /* BINARY32 IEEE-754 complex */
#define FORMAT_C64 0xE000 /* BINARY64 IEEE-754 complex */
アクセサ機能は、さまざまな方法で実装できます。Linuxでは、マークされた関数static inline
もマクロと同じくらい高速です。
このdouble
型は64ビット整数型を完全にはカバーしていないため(仮数には52ビットしかないため)、数値構造を定義します。
#include <stdint.h>
struct number {
int64_t ireal;
int64_t iimag;
double freal;
double fimag;
};
アクセサ関数が常に4つのフィールドに入力するようにします。GCCを使用すると、自動型検出を使用して構造体番号を定義するマクロを作成することもできます。
#define Number(x) \
( __builtin_types_compatible_p(__typeof__ (x), double) ? number_double(x) : \
__builtin_types_compatible_p(__typeof__ (x), _Complex double) ? number_complex_double(x) : \
__builtin_types_compatible_p(__typeof__ (x), _Complex long) ? number_complex_long(x) : \
number_int64(x) )
static inline struct number number_int64(const int64_t x)
{
return (struct number){ .ireal = (int64_t)x,
.iimag = 0,
.freal = (double)x,
.fimag = 0.0 };
}
static inline struct number number_double(const double x)
{
return (struct number){ .ireal = (int64_t)x,
.iimag = 0,
.freal = x,
.fimag = 0.0 };
}
static inline struct number number_complex_long(const _Complex long x)
{
return (struct number){ .ireal = (int64_t)(__real__ (x)),
.iimag = (int64_t)(__imag__ (x)),
.freal = (double)(__real__ (x)),
.fimag = (double)(__imag__ (x)) };
}
static inline struct number number_complex_double(const _Complex double x)
{
return (struct number){ .ireal = (int64_t)(__real__ (x)),
.iimag = (int64_t)(__imag__ (x)),
.freal = __real__ (x),
.fimag = __imag__ (x) };
}
これは、整数型または浮動小数点の実数型または複素数型である限りNumber(value)
、正しいものを作成することを意味します。struct number
value
型変換で許可されている限り、整数コンポーネントと浮動小数点コンポーネントが同じ値に設定されていることに注意してください。(大きさが非常に大きい整数の場合、浮動小数点値は概算になります。(int64_t)round(...)
整数コンポーネントを設定するときに、浮動小数点パラメーターを切り捨てる代わりに丸めることもできます。
4つのアクセサ関数が必要です。2つは構造化データ用、2つは非構造化データ用です。非構造化(プレーン)データの場合:
static inline struct number file_get_number(const struct file *const file,
const size_t offset)
{
...
}
static inline void file_set_number(const struct file *const file,
const size_t offset,
const struct number number)
{
...
}
offset
上記はバイトオフセットではなく、数値のインデックスであることに注意してください。構造化ファイルの場合、バイトオフセットを使用し、ファイルで使用される数値形式を指定するパラメーターを追加する必要があります。
static inline struct number file_get(const struct file *const file,
const size_t byteoffset,
const unsigned int format)
{
...
}
static inline void file_set(const struct file *const file,
const size_t byteoffset,
const unsigned int format,
const struct number number)
{
...
}
私が省略した関数本体(...)で必要な変換は非常に簡単です。最適化のためにできるトリックもいくつかあります。たとえば、エンディアン定数を調整して、下位ビットが常にバイトスワップ(ab-> ba、abcd-> badc、abcdefgh-> badcfehg)になり、上位ビットが短いスワップ(abcd-> cdab)になるようにします。 、abcdefgh-> cdabghef)。完全に確実にしたい場合は、64ビット値(abcdefgh-> efghabcd)の3番目のビットが必要になる場合があります。
関数本体内のifまたはcaseステートメントは、小さなアクセスオーバーヘッドを引き起こしますが、実際には無視できるほど小さい必要があります。それを回避するためのすべての方法は、はるかに複雑なコードにつながります。(最大のスループットを得るには、すべてのアクセスバリアントをオープンコード化__builtin_types_compatible_p()
し、関数またはマクロで使用して、使用する正しいものを決定する必要があります。エンディアン変換を考慮すると、かなりの数の関数を意味します。小さなアクセスオーバーヘッド(アクセスごとに最大で数クロック)の方がはるかに望ましいです(200 Mb / sであっても、すべてのテストはI / Oバウンドであるため、オーバーヘッドはまったく関係ありません)。
一般に、プロトタイプ値を使用した自動エンディアン変換では、タイプに対して可能な各変換をテストするだけです。プロトタイプ値の各バイトコンポーネントが一意である限り、1回の変換で正しい期待値が生成されます。一部のアーキテクチャでは、整数と浮動小数点値のエンディアンが異なります。これが、ENDIAN_
定数がタイプとサイズごとに別々になっている理由です。
上記のすべてを実装したとすると、アプリケーションコードでは、データアクセスは次のようになります。
struct file f;
/* Set first number to zero. */
file_set_number(&f, 0, Number(0));
/* Set second number according to variable v,
* which can be just about any numeric type. */
file_set_number(&f, 1, Number(v));
これがお役に立てば幸いです。