btcd是如何计算字节占用的

下面以交易的字节占用为例,解析btcd是如何处理golang的字节占用问题的。

交易结构

一个交易的大小分成了隔离见证占用和非隔离见证占用,非隔离见证部分包括:交易版本号、交易输入的数量、交易输入本身、交易输出数量、交易输出本身、锁定时间。

交易的结构:

type MsgTx struct {
    Version  int32
    TxIn     []*TxIn
    TxOut    []*TxOut
    LockTime uint32
}

一个交易的字节占用可被拆解为:

描述 长度(byte)
版本 4
交易输入数量 1+
交易输入 41+
交易输出数量 1+
交易输出 9+
锁定时间 4
```
func (msg *MsgTx) SerializeSize() int {
    n := msg.baseSize()

    if msg.HasWitness() {
        // The marker, and flag fields take up two additional bytes.
        n += 2

        // Additionally, factor in the serialized size of each of the
        // witnesses for each txin.
        for _, txin := range msg.TxIn {
            n += txin.Witness.SerializeSize()
        }
    }

    return n
}
```

隔离见证数据在交易结构中位于交易输入结构中,但btcd单独计算了其数据占用情况,而没有和TxIn一起计算。

交易的字节占用

一个交易包括4字节版本号、4字节锁定时间、txin长度的变长整数占用、txout长度的变长整数占用,再加上txin和txout占用。

```
func (msg *MsgTx) baseSize() int {
    // Version 4 bytes + LockTime 4 bytes + Serialized varint size for the
    // number of transaction inputs and outputs.
    n := 8 + VarIntSerializeSize(uint64(len(msg.TxIn))) + VarIntSerializeSize(uint64(len(msg.TxOut)))

    for _, txIn := range msg.TxIn {
        n += txIn.SerializeSize()
    }

    for _, txOut := range msg.TxOut {
        n += txOut.SerializeSize()
    }

    return n
}
```

VarIntSerializeSize()函数对不同大小的整数规范了不同的字节占用,也是为了优化存储,以及网络传输成本。具体内容可以参考: Variable length integer

```
func VarIntSerializeSize(val uint64) int {
    // The value is small enough to be represented by itself, so it's
    // just 1 byte.
    if val < 0xfd {
        return 1
    }

    // Discriminant 1 byte plus 2 bytes for the uint16.
    if val <= math.MaxUint16 {
        return 3
    }

    // Discriminant 1 byte plus 4 bytes for the uint32.
    if val <= math.MaxUint32 {
        return 5
    }

    // Discriminant 1 byte plus 8 bytes for the uint64.
    return 9
}
```

代码理解:

  1. 传递一个uint64类型的整数参数
  2. 如果比0xfd小,那么就返回1,说明数据在一个字节内存储而不会溢出
  3. 如果比0xfd大,在分别和math.MaxUint16、math.MaxUint32比较,也就是2字节和4字节能容纳的最大正整数,如果在相应的范围,则返回对应的字节数量。需要注意的是2字节、4字节判断前需要分别有0xFD、0xFE标记,故返回的数据需要加1
  4. 如果前面的分支都没有进入,由于变长整数的最大值被规范在uin64范围内,因此该数据占用为8个字节,加上开始的1字节0xFF标记,故返回9

txin的字节占用了:包括32字节的outpoint hash、4字节的outpoint索引、4字节的序列号,总共40字节。再加上sigScript长度的变长整数占用,以及sigScript占用字节数。

```
func (t *TxIn) SerializeSize() int {
    // Outpoint Hash 32 bytes + Outpoint Index 4 bytes + Sequence 4 bytes +
    // serialized varint size for the length of SignatureScript +
    // SignatureScript bytes.
    return 40 + VarIntSerializeSize(uint64(len(t.SignatureScript))) +
        len(t.SignatureScript)
}
```

txout的字节占用:8字节转账金额、pkScript长度变长整数、pkScript占用字节数。

```
func (t *TxOut) SerializeSize() int {
    // Value 8 bytes + serialized varint size for the length of PkScript +
    // PkScript bytes.
    return 8 + VarIntSerializeSize(uint64(len(t.PkScript))) + len(t.PkScript)
}
```

btcd在计算时加入了很多变长整数的字节占用,但是在其交易结构里面并没有对txin、txout的长度统计的字段,在使用该长度信息时在进行统计。比如在交易的序列化的过程中,对相应数据的长度进行了统计和写入。

```
func (msg *MsgTx) BtcEncode(w io.Writer, pver uint32, enc MessageEncoding) error {
    ...
    
    // 计算txin的数量,并写入到io.Writer
    count := uint64(len(msg.TxIn))
    err = WriteVarInt(w, pver, count)
    if err != nil {
        return err
    }

    for _, ti := range msg.TxIn {
        err = writeTxIn(w, pver, msg.Version, ti)
        if err != nil {
            return err
        }
    }

    // 计算txout的数量,并写入io.Writer
    count = uint64(len(msg.TxOut))
    err = WriteVarInt(w, pver, count)
    if err != nil {
        return err
    }

    for _, to := range msg.TxOut {
        err = WriteTxOut(w, pver, msg.Version, to)
        if err != nil {
            return err
        }
    }
    
    ...
```

下面通过一张图来说明整个流程


计算流程图

总结:btcd的这种统计内存占用的方式,其实和golang的内存分配和布局已经没有什么关联了,只是对bitcoin protocol的原始实现。虽然并不能体现其真实的底层内存占用情况,但是在一定程度反映了内存占用的变化情况。另外,在统计[]byte是并不是使用unsafe.SizeOf()函数简单实现,而是通过统计[]byte的长度,更贴近于真实情况,虽然golang真实占用比这个值要大。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容

  • Redis的内存优化 声明:本文内容来自《Redis开发与运维》一书第八章,如转载请声明。 Redis所有的数据都...
    meng_philip123阅读 18,874评论 2 29
  • 《深入理解Java虚拟机》笔记_第一遍 先取看完这本书(JVM)后必须掌握的部分。 第一部分 走近 Java 从传...
    xiaogmail阅读 5,062评论 1 34
  • 考虑是煎还是煮! 纸张:8k灰色卡纸 用笔:三福霹雳马132色 下图为成稿: 1.框形,画鸡蛋壳的部分; 2.逐渐...
    大王wxd阅读 686评论 5 15
  • 母校60周年华诞之际,承蒙学校领导抬爱,需要我写点什么。我思绪万千,一晃毕业已有14年了,母校种种美好情景像...
    李扭传律师阅读 3,536评论 11 9
  • 1、文章开始前,先分享一下最近生活中的某个片段。 我今年三十,已经在一个名不见经传的小城的某个偏僻又闭塞的小镇里呆...
    绿月乔木阅读 1,042评论 4 3