0

数値データを含むいくつかの大きなファイルをメモリマップしたいものに取り組んでいます。問題は、データが実数のバイト/ショート/整数/ロング/フロート/ダブルおよび複合バイト/ショート/整数/ロング/フロート/ダブルなど、さまざまな形式になる可能性があることです。当然、これらすべての型を常に処理するとすぐに扱いにくくなるため、ユーザーのためにリアルタイムの型変換を実行できるメモリ マッピング インターフェイスを実装することを考えていました。

私は、ファイルをマッピングしてメモリ内のポインタを取得し、必要なことを行ってからマッピングを解除するというアイデアが本当に気に入っています。バッファロジーなどは必要ありません。したがって、データを読み取って型変換を行う関数は、それから多くのことを奪います。

操作対象のファイルをメモリマップし、同時に匿名ファイルをマップし、ページのフェッチ/ストアをキャッチして、オンデマンドで型変換を行うことができると考えていまし。私は 64 ビットで作業するので、これらの場合は 63 ビットのアドレス空間が得られますが、まあまあです。

この種の mmap フックが可能かどうかは誰にもわかりません。

4

3 に答える 3

1

アクセサ関数を使用しない理由はありますか?

基本的なケースには、構造化データとプレーンデータの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 numbervalue

型変換で許可されている限り、整数コンポーネントと浮動小数点コンポーネントが同じ値に設定されていることに注意してください。(大きさが非常に大きい整数の場合、浮動小数点値は概算になります。(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));

これがお役に立てば幸いです。

于 2012-07-25T11:55:17.393 に答える
1

はい(っぽい)。アクセスできないmmap領域を作成できます。誰かがそれに触れようとするときはいつでも、SIGSEGVそのアクセス許可を修正し、入力して再開することで、発生したものを処理します。

long *long_view =
   mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
double *double_view =
   mmap(NULL, 4096, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

static void on_segv(int signum, siginfo_t *info, void *data) {
    void *addr = info->si_addr;
    if ((uintptr_t)addr - (uintptr_t)long_view < 4096) {
        mprotect(long_view, 4096, PROT_READ|PROT_WRITE);
        /* translate from double_view to long_view */
        mprotect(double_view, 4096, PROT_NONE);
    } else if ((uintptr_t)addr - (uintptr_t)double_view < 4096) {
        mprotect(double_view, 4096, PROT_READ|PROT_WRITE);
        /* translate from long_view to long_view */
        mprotect(double_view, 4096, PROT_NONE);
    } else {
        abort();
    }
}

struct sigaction segv_action = {
    .sa_sigaction = on_segv,
    .sa_flags = SA_RESTART | SA_SIGINFO,
};
sigaction(SIGSEGV, &segv_action, NULL);

long_view[0] = 42;
/* hopefully, this will trigger the code to fixup double_view and resume */
printf("%g\n", double_view[0]);

(テストされていませんが、これらの線に沿った何かが機能するはずです...)

一度にページ全体を埋めたくない場合でも、それは可能だと思います... 3 番目の引数を にキャストできます。ucontext_t *これを使用すると、実行中の命令をデコードして、さらなるアクセスをキャッチするためにメモリを残しながら、予想される操作PROT_NONE...しかし、最初のアクセスだけでなくすべてのアクセスをトラップしているため、はるかに遅くなります。

于 2012-07-25T07:09:01.723 に答える
1

読書の部分は、私にはいくらか実行可能に思えます。私はその経験はありませんが、原則として、ユーザーが提示したバッファーにまだ存在しないページにアクセスするとすぐに、シグナルハンドラーがデータを取得して変換することが可能になるはずです。しかし、そのようなことは非常に非効率的である可能性があり、すべてのページでコンテキスト スイッチが必要になります。

その逆はもっと難しいと思います。デフォルトの書き込みは非同期であるため、キャプチャするのは困難です。

したがって、必要なものの「半分」が可能になる可能性があります。常に、ユーザーが必要とする形式で新しいファイルにデータを書き込みますが、そのようなファイルを読み取るときに、その場で自動的に変換します。

しかし、あなたにとってもっと重要なことは、さまざまなストレージ表現に明確なセマンティクスがあり、データ項目の読み取りまたは書き込みを適切にカプセル化することです。そのようなインターフェース(「タイプEの位置に要素を格納する」など)がある場合、ターゲット形式に関して簡単に変換をトリガーできます。iT

于 2012-07-25T06:57:16.170 に答える