6

Cタイプのサイズについて質問したところ、かなり良い答えが得られましたが、自分の目的に役立つように質問をうまく定式化できない可能性があることに気付きました。

私の経歴は、ソフトウェアエンジニアに移る前はコンピュータエンジニアでした。そのため、コンピュータアーキテクチャが好きで、常にVMの作成を考えています。私は、JavaでVMを作成する興味深いプロジェクトを終えたところです。これは、私が非常に誇りに思っていることです。しかし、現在オープンソース化できない法的な問題がいくつかあり、現在は自由な時間があります。ですから、楽しみと教育のためだけに、Cで別のVMを(より高速に)作成できるかどうかを確認したいと思います。

問題は、私がトリビア以外のCの問題を最後に書いたのは、10年以上前のCプログラムではないということです。私はPascal、Delphi、そして今ではJavaとPHPのプログラマーでした。

私が予見できる多くの障害があり、私は1つに取り組もうとしており、それは既存のライブラリにアクセスしています(Javaでは、リフレクションがこの問題を解決します)。

データのバッファ(スタックと同様)を用意することで、これを解決する予定です。VMのクライアントは、ネイティブ関数へのポインターを指定する前に、これらのスタックにデータを配置するようにプログラムできます。

int main(void) {
    // Prepare stack
    int   aStackSize = 1024*4;
    char *aStackData = malloc(aStackSize);

    // Initialise stack
    VMStack aStack;
    VMStack_Initialize(&aStack, (char *)aStackData, aStackSize);

    // Push in the parameters
    char *Params = VMStack_CurrentPointer(&aStack);
    VMStack_Push_int   (&aStack, 10  ); // Push an int
    VMStack_Push_double(&aStack, 15.3); // Push a double

    // Prepare space for the expected return
    char *Result = VMStack_CurrentPointer(&aStack);
    VMStack_Push_double(&aStack, 0.0); // Push an empty double for result

    // Execute
    void (*NativeFunction)(char*, char*) = &Plus;
    NativeFunction(Params, Result); // Call the function

    // Show the result
    double ResultValue = VMStack_Pull_double(&aStack); // Get the result
    printf("Result:  %5.2f\n", ResultValue);               // Print the result

    // Remove the previous parameters
    VMStack_Pull_double(&aStack); // Pull to clear space of the parameter
    VMStack_Pull_int   (&aStack); // Pull to clear space of the parameter

    // Just to be sure, print out the pointer and see if it is `0`
    printf("Pointer: %d\n", aStack.Pointer);

    free(aStackData);
    return EXIT_SUCCESS;
}

ネイティブ関数のプッシュ、プル、および呼び出しは、バイトコード(VMが後で作成される方法)によってトリガーできます。

完全を期すために(マシンで試すことができるように)、Stackのコードは次のとおりです。

typedef struct {
    int  Pointer;
    int  Size;
    char *Data;
} VMStack;

inline void   VMStack_Initialize(VMStack *pStack, char *pData, int pSize) __attribute__((always_inline));
inline char   *VMStack_CurrentPointer(VMStack *pStack)                    __attribute__((always_inline));
inline void   VMStack_Push_int(VMStack *pStack, int pData)                __attribute__((always_inline));
inline void   VMStack_Push_double(VMStack *pStack, double pData)          __attribute__((always_inline));
inline int    VMStack_Pull_int(VMStack *pStack)                           __attribute__((always_inline));
inline double VMStack_Pull_double(VMStack *pStack)                        __attribute__((always_inline));

inline void VMStack_Initialize(VMStack *pStack, char *pData, int pSize) {
    pStack->Pointer = 0;
    pStack->Data    = pData;
    pStack->Size    = pSize;
}

inline char *VMStack_CurrentPointer(VMStack *pStack) {
    return (char *)(pStack->Pointer + pStack->Data);
}

inline void VMStack_Push_int(VMStack *pStack, int pData) {
    *(int *)(pStack->Data + pStack->Pointer) = pData;
    pStack->Pointer += sizeof pData; // Should check the overflow
}
inline void VMStack_Push_double(VMStack *pStack, double pData) {
    *(double *)(pStack->Data + pStack->Pointer) = pData;
    pStack->Pointer += sizeof pData; // Should check the overflow
}

inline int VMStack_Pull_int(VMStack *pStack) {
    pStack->Pointer -= sizeof(int);// Should check the underflow
    return *((int *)(pStack->Data + pStack->Pointer));
}
inline double VMStack_Pull_double(VMStack *pStack) {
    pStack->Pointer -= sizeof(double);// Should check the underflow
    return *((double *)(pStack->Data + pStack->Pointer));
}

ネイティブ関数側では、テスト目的で次のものを作成しました。

// These two structures are there so that Plus will not need to access its parameter using
//    arithmetic-pointer operation (to reduce mistake and hopefully for better speed).
typedef struct {
    int    A;
    double B;
} Data;
typedef struct {
    double D;
} DDouble;

// Here is a helper function for displaying void PrintData(Data *pData, DDouble *pResult) { printf("%5.2f + %5.2f = %5.2f\n", pData->A*1.0, pData->B, pResult->D); }

// Some native function void Plus(char* pParams, char* pResult) { Data *D = (Data *)pParams; // Access data without arithmetic-pointer operation DDouble *DD = (DDouble *)pResult; // Same for return DD->D = D->A + D->B; PrintData(D, DD); }

実行すると、上記のコードは次を返します。

10.00 + 15.30 = 25.30
Result:  25.30
Pointer: 0

これは私のマシン(Linux x86 32ビットGCC-C99)でうまく機能します。これが他のOS/アーキテクチャでも機能する場合は非常に便利です。しかし、私たちが注意しなければならない少なくとも3つのメモリ関連の問題があります。

1)。データサイズ-同じアーキテクチャで同じコンパイラを使用してVM関数とネイティブ関数の両方をコンパイルする場合、サイズタイプは同じである必要があるようです。

2)。エンディアン-データサイズと同じ。

3)。メモリアライメント-これは構造体にパディングバイトが追加される可能性があるため問題ですが、パラメータスタックを準備するときに同期するのは困難です(ハードコーディング以外にパディングがどのように追加されるかを知る方法はありません)。

私の質問は次のとおりです。

1)。タイプのサイズがわかっている場合、構造体のパディングと正確に同期するようにプッシュおよびプル関数を変更する方法はありますか?(コンパイラがデータサイズやエンディアンの問題のように処理できるように変更します)。

2)。構造を1つずつパックすると(を使用#pragma pack(1)); (2.1)パフォーマンスペナルティは許容されますか?(2.2)プログラムの安定性は危険にさらされますか?

3)。2、4、または8でパディングするのはどうですか?一般的な32ビットまたは64ビットシステムにはどちらが適していますか?

4)。x86上のGCCの場合など、正確なパディングアルゴリズムのドキュメントを教えていただけますか?

5)。より良い方法はありますか?

注:クロスプラットフォームは私の究極の目標ではありませんが、抵抗することはできません。また、それほど醜くないので、パフォーマンスは私の目標ではありません。これらはすべて楽しみと学習のためのものです。

私の英語と非常に長い投稿でごめんなさい。

よろしくお願いします。

4

3 に答える 3

2

接線コメント

これらの最初の項目は、あなたが尋ねた質問に正接していますが...

// Execute
void (*NativeFunction)(char*, char*) = &Plus;
NativeFunction(Params, Result); // Call the function

ここでは、おそらく「char*」の代わりに「void*」を使用する必要があると思います。関数ポインタ型のtypedefもあります。

typedef void (*Operator)(void *params, void *result);

次に、次のように書くことができます。

Operator NativeFunction = Plus;

実際の関数も変更されますが、ごくわずかです。

void Plus(void *pParams, void *pResult)

また、名前の付け方に小さな問題があります。この関数は、汎用の関数ではなく、「IntPlusDoubleGivesDouble()」関数です。


質問への直接回答

1)。タイプのサイズがわかっている場合、構造体のパディングと正確に同期するようにプッシュおよびプル関数を変更する方法はありますか?(コンパイラがDatasizeやEndiansの問題のように処理できるように変更します)。

それを行う簡単な方法はありません。たとえば、次のことを考慮してください。

struct Type1
{
     unsigned char byte;
     int           number;
};
struct Type2
{
     unsigned char byte;
     double        number;
};

一部のアーキテクチャ(たとえば、32ビットまたは64ビットSPARC)では、Type1構造の「number」は4バイト境界に整列されますが、Type2構造の「number」は8バイト境界に整列されます(また、16バイト境界に「longdouble」がある場合があります)。「個々の要素をプッシュする」戦略では、「バイト」値をプッシュした後、スタックポインターが1つ増えます。したがって、スタックポインターがまだ適切でない場合は、「数値」をプッシュする前に、スタックポインターを3または7移動する必要があります。整列。VMの説明の一部は、特定のタイプに必要な配置になります。対応するプッシュコードは、プッシュする前に正しい位置合わせを確認する必要があります。

2)。構造を1つずつパックする場合(#pragma pack(1)を使用)。(2.1)パフォーマンスペナルティは許容されますか?(2.2)プログラムの安定性は危険にさらされますか?

x86およびx86_64マシンでは、データをパックすると、データアクセスの位置がずれているためにパフォーマンスが低下します。SPARCやPowerPCmeckiあたり)などのマシンでは、代わりにバスエラーなどが発生します。適切な配置でデータにアクセスする必要があります。パフォーマンスを犠牲にして、メモリスペースを節約できる場合があります。スペースの限界費用でパフォーマンス(ここでは「クラッシュする代わりに正しく実行する」を含む)を確保する方がよいでしょう。

3)。2、4、または8でパディングするのはどうですか?一般的な32ビットまたは64ビットシステムにはどちらが適していますか?

SPARCでは、Nバイトの基本型をNバイトの境界にパディングする必要があります。x86では、同じことを行うと最高のパフォーマンスが得られます。

4)。x86上のGCCについて、正確なパディングアルゴリズムのドキュメントを教えていただけますか?

マニュアルを読む必要があります。

5)。より良い方法はありますか?

単一の文字の後にタイプが続く「Type1」トリックは、アライメント要件を与えることに注意してください-おそらく:からの「offsetof()」マクロを使用し<stddef.h>ます:

offsetof(struct Type1, number)

ええと、私はスタックにデータをパックしません-それが最高のパフォーマンスを与えるように設定されているので、私はネイティブアライメントで作業します。コンパイラーの作成者は、構造体にパディングをぼんやりと追加しません。アーキテクチャに「最適」に機能するため、そこに配置しました。あなたがよりよく知っているとあなたが決めるならば、あなたは通常の結果を期待することができます-時々失敗しそしてそれほど移植性がないより遅いプログラム。

また、スタックに構造体が含まれていると想定するために、演算子関数にコードを記述することも確信していません。正しいオフセットとタイプが何であるかを知って、Params引数を介してスタックから値を引き出します。整数と倍精度浮動小数点数をプッシュした場合、整数と倍精度浮動小数点数をプルします(または、逆の順序で、倍精度整数と整数をプルします)。異常なVMを計画していない限り、多くの引数を持つ関数はほとんどありません。

于 2009-10-24T04:45:03.073 に答える
1

興味深い投稿であり、あなたが多くの仕事をしたことを示しています。ほぼ理想的なSOポスト。

回答がありませんので、ご容赦ください。もう少し質問する必要があります:P

1)。タイプのサイズがわかっている場合、構造体のパディングと正確に同期するようにプッシュおよびプル関数を変更する方法はありますか?(コンパイラがDatasizeやEndiansの問題のように処理できるように変更します)。

これはパフォーマンスの観点からのみですか?ネイティブの算術型とともにポインターを導入することを計画していますか?

2)。構造を1つずつパックする場合(#pragma pack(1)を使用)。(2.1)パフォーマンスペナルティは許容されますか?(2.2)プログラムの安定性は危険にさらされますか?

これは実装で定義されたものです。プラットフォーム間で信頼できるものではありません。

3)。2、4、または8でパディングするのはどうですか?一般的な32ビットまたは64ビットシステムにはどちらが適していますか?

ネイティブワードサイズと一致する値は、最適なパフォーマンスを提供するはずです。

4)。x86上のGCCの場合など、正確なパディングアルゴリズムのドキュメントを教えていただけますか?

頭のてっぺんは知らない。しかし、私はこれに似たコードが使用されているのを見てきました。

GCC(パディングをオフにするというものもあります)を使用して変数の属性を指定できることに注意してください。default_struct __attribute__((packed))

于 2009-10-23T22:29:58.567 に答える
1

ここにはいくつかの非常に良い質問があり、それらの多くはいくつかの重要な設計の問題に巻き込まれますが、私たちのほとんどにとって-私たちはあなたが何に取り組んでいるのかを見ることができますあなたが取り組んでいるのはいくつかのコンパイラの問題といくつかの言語設計の問題であるほどあなたの英語を十分に理解することができます-質問をするのは難しくなりますが、あなたはすでにJNIで働いているという点で希望があります...

一つには、私はプラグマから逃げようとします。多くの人々、非常に多くの人々がそれに同意しません。なぜこの問題に関するD言語の立場の正当性を参照するのかについての標準的な議論については。もう1つは、コードに16ビットのポインターが埋め込まれていることです。

問題はほぼ無限であり、よく研究されており、私たちを反対と壁内の非情に埋もれる可能性があります。KennethLoudenのホームページとIntelアーキテクチャのマニュアルを読むことをお勧めします。私はそれを持っています、私はそれを読もうとしました。データ構造の整合性は、議論のために提起した他の多くの問題とともに、歴史的なコンパイラー科学に深く埋もれており、誰が何を知っているかに夢中になりそうです。(予期せぬ結果の問題については俗語または慣用句)

そうは言っても、ここに行きます:

  1. Cタイプのサイズ どのタイプのサイズですか?
  2. ソフトウェアエンジニアに移る前のコンピュータエンジニア マイクロコントローラを勉強したことがありますか?ドン・ランカスターの作品のいくつかを見てください。
  3. Pascal、Delphi、そして今ではJavaとPHPのプログラマー。 これらはプロセッサの基本的な基本アーキテクチャから比較的削除されていますが、多くの人が強力で基本的なルーチンを作成するためにそれらをどのように使用できるかを示したり、示したりしようとします。David Eckの再帰下降パーサーを見て、問題の調査を開始する方法を正確に確認することをお勧めします。また、Kenneth Loudenには、実際のコンパイラである「Tiny」の実装があります。少し前にasmdotorgと呼ばれるものを見つけました...そこでは非常に高度で非常に強力な作業が研究に利用できましたが、コンパイラ科学に入るつもりでアセンブラで書き始めるのは長い道のりです。さらに、ほとんどのアーキテクチャには、プロセッサごとに一貫性のない違いがあります。
  4. 既存のライブラリへのアクセス

周りにはたくさんのライブラリがありますが、Javaにはいくつかの良いライブラリがあります。私は他の人について知りません。1つのアプローチは、libを書き込もうとすることです。Javaには優れた基盤があり、人々がより良いものを考え出そうとする余地があります。Knuth-Morris-Prattなどを改善することから始めましょう。開始する場所が不足することはありません。Computer Programming Algorithms Directoryを試してみてください。確かに、NISTのアルゴリズムとデータ構造の辞書を見てください。

  1. always_inline

必ずしもそうとは限りません。DovBulkaを参照してください。労働者はCSで博士号を取得しており、時間効率/信頼性-堅牢性などが「ビジネスモデル」パラダイムの対象とならない分野で熟練した著者でもあります。実際に重要な問題についての「ああ!それは問題ではない」の。

最後に、計装と制御は、あなたが説明するように、熟練したプログラミングスキルの実際の市場の60%以上を占めています。どういうわけか、私たちは主にビジネスモデルについて聞いています。私はあなたと私が信頼できる情報源から持っているちょっとした情報を共有させてください。10%から60%以上の実際の安全性と財産のリスクは、泥棒や盗難などから来るよりも、車両の問題から生じます。「郡の鉱物抽出施設で90日間のバスチン鉱物」の訴えを聞くことは決してありません。交通違反切符の場合、実際、ほとんどの人は交通違反切符が(NA-USA)クラス4の軽罪であり、実際にはそのように分類できることに気づいていません。

あなたがいくつかの良い仕事に向けて良い一歩を踏み出したように私には聞こえます、...

于 2009-10-23T23:08:06.013 に答える