web-dev-qa-db-ja.com

NaNボクシングの目的は何ですか?

読む 21st Century C 第6章の "NaNを使用した例外的な数値のマーキング" に到達しました。ここでは、仮数のビットを使用して任意の値を格納する方法を説明していますマーカーまたはポインターとして使用するためのビットパターン(この本では、WebKitがこの手法を使用していると述べています)。

私はこのテクニックの有用性を理解していることを本当に確信していません。ハック(ハードウェアがNaNの仮数の値を気にしないことに依存している)であるように見えますが、Javaの背景から来ています。 Cの粗さに慣れていない。

これは、NaNでマーカーを設定および読み取るコードのスニペットです

#include <stdio.h>
#include <math.h> //isnan

double ref;

double set_na(){
    if (!ref) {
        ref=0/0.;
        char *cr = (char *)(&ref);
        cr[2]='a';
    }
    return ref;
}

int is_na(double in){
    if (!ref) return 0;  //set_na was never called==>no NAs yet.

    char *cc = (char *)(&in);
    char *cr = (char *)(&ref);
    for (int i=0; i< sizeof(double); i++)
        if (cc[i] != cr[i]) return 0;
    return 1;
}

int main(){
    double x = set_na();
    double y = x;
    printf("Is x=set_na() NA? %i\n", is_na(x));
    printf("Is x=set_na() NAN? %i\n", isnan(x));
    printf("Is y=x NA? %i\n", is_na(y));
    printf("Is 0/0 NA? %i\n", is_na(0/0.));
    printf("Is 8 NA? %i\n", is_na(8));
}

それは印刷します:

Is x=set_na() NA? 1
Is x=set_na() NAN? 1
Is y=x NA? 1
Is 0/0 NA? 0
Is 8 NA? 0

そして JSValue.h でwebkitはエンコーディングを説明していますが、それが使用される理由は説明していません。

このテクニックの目的は何ですか?スペース/パフォーマンスのメリットは、ハックな性質のバランスをとるのに十分なものですか?

45
andijcr

動的に型付けされた言語を実装する場合、オブジェクトを保持できる単一の型が必要です。これについて私が知っている3つの異なるアプローチがあります。

まず、ポインタを渡すことができます。これがCPythonの実装です。すべてのオブジェクトはPyObjectポインターです。これらのポインターは渡され、PyObject構造体の詳細を調べて型を判別することで操作が実行されます。

欠点は、数値のような小さな値がボックス化された値として格納されるため、小さな5がメモリブロックとしてどこかに格納されることです。したがって、これはLuaによって使用されるユニオンアプローチにつながります。 PyObject*の代わりに、各値は、1つのフィールドでタイプを指定する構造体であり、サポートされるすべての異なるタイプの結合です。そうすることで、小さな値にメモリを割り当てることを避け、代わりにそれらを共用体に直接格納します。

NaNアプローチはすべてをdoubleとして格納し、NaNの未使用部分を追加のストレージに再利用します。 unionメソッドよりも優れている点は、typeフィールドを保存することです。有効なdoubleの場合はdoubleです。それ以外の場合、仮数は実際のオブジェクトへのポインターです。

これはすべてのJavaScriptオブジェクトです。すべての変数、オブジェクトのすべての値、すべての式。これらすべてを96ビットから64ビットに減らすことができれば、それはかなり印象的です。

ハックする価値はありますか?効率的なJavascriptに対する多くの需要があることを思い出してください。 Javascriptは多くのWebアプリケーションのボトルネックであるため、高速化することが優先度が高くなります。パフォーマンス上の理由から、ある程度のハックを導入することは妥当です。ほとんどの場合、少しの利益のためにある程度の複雑さを導入するので、それは悪い考えです。しかし、この特定のケースでは、メモリと速度の改善に価値があります。

64
Winston Ewert

「例外的な値」にNaNを使用することは、追加のブール変数this_value_is_invalidの必要性を回避するためのよく知られており、時には役立つテクニックです。賢く使用すると、パフォーマンスを犠牲にすることなく、コードをより簡潔、よりクリーン、よりシンプルで読みやすくすることができます。

もちろん、この手法にはいくつかの落とし穴があります(ここを参照 http://ppkwok.blogspot.co.uk/2012/11/Java-cafe-1-never-write-nan-nan_24.html ) 、しかしJava(または非常によく似たC#)のような言語では、NaNを簡単に扱うためのFloat.isNaNなどの標準ライブラリ関数があります。もちろん、Java代わりにthe FloatおよびDoubleクラスを使用し、C#ではnull値の型float?およびdouble?を使用して、null無効な浮動小数点数に対してNaNの代わりに使用しますが、これらの手法はプログラムのパフォーマンスとメモリ使用量に重大な悪影響を及ぼす可能性があります。

Cでは、NaNの使用は100%移植可能ではありませんが、それは事実ですが、IEEE 754浮動小数点標準が利用可能なあらゆる場所で使用できます。私の知る限り、これは今日のほとんどすべての主流ハードウェアです(または、少なくともほとんどのコンパイラのランタイム環境でサポートされています)。たとえば、 this SO post には、CでのNaNの使用に関する詳細を確認するための情報が含まれています。

7
Doc Brown