zoukankan      html  css  js  c++  java
  • Nan-boxing技术介绍

      NaN-boxing看起来像英文翻译的“南拳”,其实它是表示一个无效的double数。NaN-boxing技术:通过一个64位的数字来表示多种数据类型的技术,它通过一个nan浮点数来保存数据,根据IEEE-754浮点数标准,double类型的NAN形式为:
    sign
    | exponent
    |     |
    [0][11111111111][yyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx]
                                  |      |
                                tag     |
                                   payload
      总共64位,最高位是一个符号位,可能是0也可能是,接下来的11位全部为1,则这个浮点数就是一个NAN,符号位如果为1则表示是一个quiet NAN,如果为1则表示是一个signed NAN。因此一个NAN只需要前面的12位来表示就行了,那么剩下的52位则可以用来编码,比如我们用剩下的52位中的前4位来表示一个数据的类型,后面的48位用来表示数据的值或地址。表示类型的4位我们称为tag,它最多可以用来表示16种类型的数据,后面的48位我们称为payload,用它来表示实际的数据或数据的地址,对于小于等于32位的数字可以直接存到payload中,对于其它类型的数据可以保存其地址到payload中,因为x86 32位和64位系统中,地址最多不超过47位,所以用48位来保存数据的地址是完全够用的。
      为了演示一下nan-boxing的应用,我写一个简单的例子来展示一下它是如何存储数据的,看起来就像一个动态类型。

    #pragma once
    #include <cstddef>
    #include <assert.h>
    #include <cstdint>
    using namespace std;
    
    enum TypeTag {
        BOOL,
        INT32,
        UINT32,
        UINT64,
        INT64,
        DOUBLE,
        CHAR_ARRAY,
        STRING,
        NIL
    };
    
    #define PAYLOAD_MASK 0x00007FFFFFFFFFFFULL
    #define NAN_MASK 0x7FF8000000000000ULL
    #define TAG_MASK 0xF
    #define TAG_SHIFT 47
    
    union Value {
        uint64_t ival;
        double fval;
    
        Value(double x) : fval(x)
        {
        }
    
        Value(int v)
        {
            ival = NAN_MASK | ((uint64_t)INT32 << TAG_SHIFT) | (uint64_t)v;
        }
        Value(uint32_t v)
        {
            ival = NAN_MASK | ((uint64_t)UINT32 << TAG_SHIFT) | (uint64_t)v;
        }
    
        Value(int64_t v)
        {
            ival = static_cast<uint64_t>(v);
        }
    
        Value(uint64_t v)
        {
            ival = v;
        }
    
        Value(bool v)
        {
            ival = NAN_MASK | ((uint64_t)BOOL << TAG_SHIFT) | (uint64_t)v;
        }
    
        Value(const char* v)
        {
            ival = NAN_MASK | ((uint64_t)CHAR_ARRAY << TAG_SHIFT) | (uint64_t)v;
        }
    
        Value(const string& v)
        {
            ival = NAN_MASK | ((uint64_t)STRING << TAG_SHIFT) | (uint64_t)&v;
        }
    
        Value(TypeTag tag = NIL, void *payload = nullptr) {
            assert((uint64_t)payload <= PAYLOAD_MASK);
            ival = NAN_MASK | ((uint64_t)tag << TAG_SHIFT) | (uint64_t)payload;
        }
    
        int toInt() const
        {
            assert(getTag() == INT32);
            return (int)getPayload();
        }
    
        int64_t toInt64() const
        {
            return (int64_t)ival;
        }
    
        uint32_t toUInt() const
        {
            assert(getTag() == UINT32);
            return (int)getPayload();
        }
    
        uint64_t toUInt64() const
        {
            return ival;
        }
    
        bool toBool() const
        {
            assert(getTag() == BOOL);
            return getPayload() != 0;
        }
    
        double toDouble() const
        {
            assert(getTag() == DOUBLE);
            return fval;
        }
    
        char *toCharArray() const
        {
            assert(getTag() == CHAR_ARRAY);
            return (char *)getPayload();
        }
    
        string& toString() const
        {
            assert(getTag() == STRING);
            return *(string *)getPayload();
        }
    
        TypeTag getTag() const
        {
            return isPayload() ? DOUBLE : TypeTag((ival >> TAG_SHIFT) & TAG_MASK);
        }
    
        uint64_t getPayload() const
        {
            assert(!isPayload());
            return ival & PAYLOAD_MASK;
        }
    
        bool operator<(const Value& other)  const
        {
            return hash()<other.hash();
        }
    
        bool operator==(const Value& other)  const
        {
            return hash() == other.hash();
        }
    private:
        bool isPayload() const
        {
            return (int64_t)ival <= (int64_t)NAN_MASK;
        }
    
        uint64_t toHashUInt64() const
        {
            assert(getTag() < INT64);
            if (getTag() == UINT64)
                return ival;
    
            return (uint64_t)getPayload();
        }
    
        int64_t toHashInt64() const
        {
            assert(getTag() == INT64);
            return toInt64();
        }
    
        std::size_t hash() const {
            switch (getTag()) {
            case         UINT64:
            case         INT32:
            case         UINT32:
            case           BOOL:
                return std::hash<uint64_t>()(toHashUInt64());
            case         INT64:
                return std::hash<int64_t>()(toHashInt64());
            case DOUBLE:
                return std::hash<double>()(toDouble());
            case STRING:
                return std::hash<std::string>()(toString());
            default:
                throw std::invalid_argument("can not find this type");
            }
        }
    };

    测试代码:

    void Test()
    {
        Value t="a";
        cout << t.toCharArray() << endl;
    
        Value v = (int)2;
        auto r0 = v.toInt();
    
        Value v1 = (uint32_t)2;
        auto r1 = v1.toUInt();
        Value v2 = (int64_t)2;
        auto r2 = v2.toInt64();
        Value v3 = (uint64_t)2;
        auto r3 = v3.toUInt64();
    
        Value v4 = 2.5;
        auto r4 = v4.toDouble();
    
        string s1 = "a";
        Value v5 = s1;
        auto r5 = v5.toString();
        string s = r5;
    
        Value b = false;
        bool r = b.toBool();
        b = true;
        r = b.toBool();
    }

      通过nan-boxing技术,Value可以表示多种类型了,比如int double string等类型,这个Value看起来像一个动态类型了。这一切都很简单,看起来似乎很好,但是这只是我们玩了一个小把戏而已,就是把值或地址放到payload中了,这存在一个很大的问题:即存放在payload中的地址需要保证有效性,如果存放一个临时变量的地址就悲剧了,尤其是对于非数字类型来说,比如string或者一个自定义类型,这时我们需要在外面保证这些变量的生命周期了,这极大的影响了Value的使用。解决这个问题的办法有两个,一个是自定义类型的话就new出来,然后自己去管理其生命周期;另外一个方法是通过一个内存池来创建对象,通过内存池来保持对象地址的有效性。

      nan-boxing技术最开始是亚马逊的工程师应用到javascript的引擎中的,luajit和rubyjit也用它作为动态类型来存储数据,我相信nan-boxing技术还有很多应用场景,比如可以用它来表示json的value等等,本文的目的是抛砖引玉,关于它更多的具体应用我希望更多的人能去深入研究。

  • 相关阅读:
    001 :PCL 的基本文件类型PCD的读入和写入操作
    CMake +Vs2017+快速搭建pcl1.9.1环境
    Window 10 PCL-1.91+VS2017 源码编译以及安装pcl
    Eigen3+Cmake+Vs2017源码编译
    将Opencv加入到环境变量中
    004 :opencv 中矩阵操作以及通过内存的方式取像素
    ubuntu16.04与win10双系统安装 无法将grub-efi-amd64-signed 软件包安装到/target/中
    简单了解一下PyTest-sq
    软件测试工程师笔试题
    TT-反射-对象拷贝
  • 原文地址:https://www.cnblogs.com/qicosmos/p/4285409.html
Copyright © 2011-2022 走看看