精华内容
下载资源
问答
  • 年龄计算器说明虚岁年龄中国在习惯上常用的年龄计算方法,按出生后所经历的日历年头计算,即生下来就算1岁,以后每过一次新年便增加1岁。一般按农历新年算,也有按公历算的。例如,12末出生的婴儿,出生后就算1岁,过...

    年龄计算器说明

    虚岁年龄

    中国在习惯上常用的年龄计算方法,按出生后所经历的日历年头计算,即生下来就算1岁,以后每过一次新年便增加1岁。一般按农历新年算,也有按公历算的。例如,12月末出生的婴儿,出生后就算1岁,过了公历1月1日或当地农历新年又算1岁。这样,婴儿出生才几天,已算虚岁2岁了。这种虚岁计算方法是不科学的。

    周岁年龄

    又称实足年龄,指从出生到计算时为止,共经历的周年数或生日数。例如,1990年7月1日零时进行人口普查登记,一个1989年12月15日出生的婴儿,按虚岁计算是2岁,实际刚刚6个多月,还未过一次生日,按周岁计算应为不满1周岁,即0岁。周岁年龄比虚岁年龄常常小1~2岁,它能正确反映人们的实际生存年岁,是人口统计中最常用的年龄计算方法。

    确切年龄

    指从出生之日起到计算之日止所经历的天数。它比周岁年龄更为精确地反映人们实际生存的时间,但由于其统计汇总时较为繁琐,故人口统计中使用甚少。在实际生活中,人们除对不满1周岁的婴儿,特别是不满1个月的新生儿常常按月日计算外,一般不按日计算确切年龄。

    周岁和虚岁的关系

    虚岁只和过年有关,周岁只和生日有关

    出生时1虚岁,0周岁

    过完春节长一虚岁,过完生日长一周岁

    虚岁=今年-出生年+1

    周岁=今年-出生年(已过生日)(未过生日还要-1)

    虚岁,周岁传统都用农历计算

    在过年到生日期间 虚岁-周岁=2(即虚两岁)

    在生日到过年期间 虚岁-周岁=1(即虚一岁)

    展开全文
  • 学到老每天学习一丢丢转载至《高层混凝土结构优化设计方法探讨》 《建筑结构》2016 年 11 剪力墙结构是一种常见结构形式,特别是在量大面广的高层住宅中广泛应用。剪力墙结构由于梁和板的跨度不大,梁和板的优化...

    9497786138d30177a444f5910b3b9867.gif

    到老.学到老

    每天学习一丢丢

    转载至《高层混凝土结构优化设计方法探讨》

             《建筑结构》2016 年 11 月

            剪力墙结构是一种常见结构形式,特别是在量大面广的高层住宅中广泛应用。剪力墙结构由于梁和板的跨度不大,梁和板的优化空间相对较小。

    bee1b81033a9f77ac6d2c04755c57008.png

    下面从墙肢布置、结构计算参数取值、性能控制指标( 如位移角) 三个方面讨论剪力墙结构的优化方法。

    8aaa56d7a3db852e8e27d0a0c531731e.png

    1 平面布置原则

            墙肢布置的优劣直接从宏观上影响整个建筑结构的力学性能和经济指标,因此优化布置是进行剪力墙结构优化设计的关键。剪力墙布置宜遵循如下四点原则。

    1. 1 墙肢对齐布置

            剪力墙构件作为高层剪力墙结构主要的抗侧移构件,进行结构设计时应充分发挥墙肢间的联动效用。因此进行结构布置时,同一方向的墙肢宜均匀布置,在平面上形成多道联肢剪力墙协同工作,尽量避免剪力墙错位布置。如图 1 所示的某高层住宅结构平面 Y 向存在 4 片墙肢刚好错位布置的情况( 图1 中框起部分的墙肢) 。稍微调整该墙肢的位置,可形成 2 道联肢剪力墙,则对齐布置的计算模型局部侧向刚度可增加 10% 。

    5bd15e7a1b238bc0dbb995714f2d517f.png

     1. 2 墙肢均匀布置

            高层建筑结构在满足承受竖向荷载和结构抗侧移刚度的需要外,还应具有一定的抗扭转刚度。具体设计过程中,可通过适当加强周边剪力墙以及外圈梁,调整结构刚度中心与结构平面几何形心、质量中心的 相 对 位 置,尽 量 做 到“三 心”重 合 的 理 想效果。

    1. 3 避免使用短肢剪力墙或长墙

            由于短肢剪力墙的延性较差,且构造要求高,钢筋用量较大,结构布置时应避免使用短肢剪力墙。墙肢长度过长,刚度过大,会造成地震力比较集中。剪力墙结构中如果存在少量长墙,地震作用下的楼层剪力主要由这部分长墙承受,发生超烈度地震时该部分墙肢由于承受巨大的地震力往往首先破坏,由于其他墙肢的承载力较弱,容易造成剪力墙墙肢由强到弱各个击破的破坏形式,最终导致结构倒塌。因此,进行剪力墙结构布置时宜使各墙肢刚度

    接近,尽量避免使用长墙。

    1. 4 优先采用带翼缘墙

            L 形、T 形的剪力墙因墙肢端部的翼墙起到扶壁作用,稳定性较好,同时也比较容易满足框架梁搭接在剪力墙端部时钢筋的锚固长度要求,进行结构布置时宜优先采用,L 形、T 形墙的翼墙长度可控 制 在 0. 5 ~ 1. 0m,翼 墙 长 度 越短,则 配 筋越少。

    2 计算参数的敏感性

            对剪力墙结构钢筋用量敏感的参数包括: 周期折减系数、连梁刚度折减系数、梁刚度增大系数、考虑压筋影响的梁配筋计算、考虑楼板作为翼缘的梁配筋计算、楼板计算假定、次梁的抗震等级等。限于篇幅,以下选取周期折减系数、楼板计算假定和次梁的抗震等级进行分析。

     2. 1 周期折减系数

            周期折减系数不影响结构刚度,但影响结构的地震效应大小。周期折减系数可通过软件计算得到,如采用 GSSAP 软件分别计算有填充墙模型和无填充墙模型的第一周期,以这两个周期的比值作为折减依据。

            某 12 层剪力墙结构三维计算模型如图 2 所示,该结构填充墙比较多,计算得到的周期折减系数为0. 95。某 32 层剪力墙结构三维计算模型如图 3 所示,该结构填充墙比较少,计算得到的周期折减系数为 1. 0。两模型的计算结果见表 1。

    bca632ddc43388eba7241a3a730e6f8f.png

    1554b54376dc724db036182d61a4005f.png

            工程实践表明,周期折减系数每下降 0. 1,基底地震剪力增加 3% ~ 10% ,地震力的增大将会导致配筋增大。因此,周期折减系数应慎重选取,一般剪力墙住宅结构可取 0. 95。也可通过计算结构( 考虑填充墙刚度) 的基本周期来确定。

    2. 2 楼板计算假定

            在结构整体计算中,一般情况下楼板可采用刚性板或弹性板假定。刚性板假定下可通过梁刚度放大系数考虑楼板的刚度贡献,而弹性板假定下,楼板与梁共同工作,较真实地考虑了楼板面外刚度的贡献。采用不同的楼板假定所计算得到的梁板内力分配不同,从而梁板的计算配筋也不同。

            某 32 层剪力墙住宅结构,其标准层结构平面布置图如图 4 所示,楼板分别采用刚性板( 中梁刚度放大系数取 1. 8) 和弹性板( 壳元) 假设进行计算,得到的结构第一周期分别是 2. 784s 和 3. 025s,可见,基于弹性板假定的结构整体刚度比刚性板假定大,基于弹性板假定计算结果进行设计比基于刚性板假定要节省钢材,每平方米梁钢筋用量减少约 2kg。

    49b8c047380b87fe5e62ce5dee0b4c26.png

    2. 3 次梁的抗震等级

            采用常用设计软件建模时,与墙相连的梁的建模一般按主梁输入,是否按次梁设计由软件判断或工程师指定。次梁是非抗震构件,若按抗震构件设计将提高梁的最小配筋率和其他构造要求。当前全国各地对次梁的判断有如下 5 种选择( 按次梁数量从少到多排列) : 1) 所有与墙肢垂直相连的梁判断为框架梁; 2) 有一端与墙垂直相连的梁判断为次梁; 3) 两端与墙垂直相连的梁判断为次梁; 4) 一端与墙方向一致,另一端搭在梁上的梁判断为次梁;5) 一端与墙方向一致,另一端不论如何搭接均判断为次梁。目前多数采用第 3 种情况,即两端与墙垂直相连的梁判断为次梁。

    3 不同层间位移角的材料用量比较

            某品字形高层住宅,结构总高度为 97. 5m,地下1 层,地上 32 层,首层层高 4. 5m,标准层层高 3m,结构平面布置如图 5 所示。本工程位于 7 度区,基本地震加速度 0. 10g,Ⅱ类场地,设计地震分组为第一组,基本风压 0. 30kN /m2 ,地面粗糙度为 B 类。

            通过修改抗侧力构件的截面尺寸和局部调整结构布置,使得结构在多遇地震作用下的最大层间位移 角 分 别 满 足 1 /700,1 /800,1 /1 000,1 /1 300,1 /1 600的限值要求。

            由表 2 可知,层间位移角按 1 /1 300 的限值控制的钢筋用量最少,层间位移角按 1 /700 的限值控制的混凝土用量最少。可见,结构刚度越大,剪力墙用钢量越大,梁用钢量越小,但混凝土用量越大。比较材料总造价,则层间位移角限值越大越节省材料用量; 若执行《高层建筑混凝土结构技术规程》( JGJ 3—2010)  ( 简称高规) ,则层间位移角接近1 /1 000 限值时材料用量最节省; 若执行广东省高规 ,则层间位移角接近 1 /800 限值时材料用量最节省。

    ba8b4c0afe1296ff97ab68b597055a0c.png

    c2eaf1a2050a996d2851f26467364351.png

    章仅供学习交流,如若图文资源侵犯您权益,请及时与我们联系,我们将第一时间做出处理。be502334b30dc2cb1a1a6707df84fe42.png

    丨地产全专业两千人QQ交流群丨

    d78289d3bad539cd1a6dc90eaf2c2e03.png

    be502334b30dc2cb1a1a6707df84fe42.png

    丨微信会员地产精英交流群丨

    bb54a01dc03a14fbaaf4c292d1a0673e.png

    be502334b30dc2cb1a1a6707df84fe42.png

    丨知识星球会员丨

    新用户免费赠送3套价值6000元的成本视频教程及配套课件

    815606a48a1095d800fdc7405a5b66c0.png

    展开全文
  • HyperLogLog 是一种概率数据结构,用来...在遍历数据集时,判断当前遍历值是否已经存在唯一方法就是将这个值与已经遍历过的值进行一一对比。当数据集的数量越来越大,内存消耗就无法忽视,甚至成了问题的关键。 使用

    HyperLogLog 是一种概率数据结构,用来估算数据的基数。数据集可以是网站访客的 IP 地址,E-mail 邮箱或者用户 ID。

    基数就是指一个集合中不同值的数目,比如 a, b, c, d 的基数就是 4,a, b, c, d, a 的基数还是 4。虽然 a 出现两次,只会被计算一次。

    精确的计算数据集的基数需要消耗大量的内存来存储数据集。在遍历数据集时,判断当前遍历值是否已经存在唯一方法就是将这个值与已经遍历过的值进行一一对比。当数据集的数量越来越大,内存消耗就无法忽视,甚至成了问题的关键。

    使用 Redis 统计集合的基数一般有三种方法,分别是使用 Redis 的 HashMap,BitMap 和 HyperLogLog。前两个数据结构在集合的数量级增长时,所消耗的内存会大大增加,但是 HyperLogLog 则不会。

    Redis 的 HyperLogLog 通过牺牲准确率来减少内存空间的消耗,只需要12K内存,在标准误差0.81%的前提下,能够统计2^64个数据。所以 HyperLogLog 是否适合在比如统计日活月活此类的对精度要不不高的场景。

    这是一个很惊人的结果,以如此小的内存来记录如此大数量级的数据基数。下面我们就带大家来深入了解一下 HyperLogLog 的使用,基础原理,源码实现和具体的试验数据分析。

    HyperLogLog 在 Redis 中的使用

    Redis 提供了 PFADDPFCOUNTPFMERGE 三个命令来供用户使用 HyperLogLog。

    PFADD 用于向 HyperLogLog 添加元素。

    > PFADD visitors alice bob carol
    (integer) 1
    > PFCOUNT visitors
    (integer) 3
    

    如果 HyperLogLog 估计的近似基数在 PFADD 命令执行之后出现了变化, 那么命令返回 1 , 否则返回 0 。 如果命令执行时给定的键不存在, 那么程序将先创建一个空的 HyperLogLog 结构, 然后再执行命令。

    PFCOUNT 命令会给出 HyperLogLog 包含的近似基数。在计算出基数后,PFCOUNT 会将值存储在 HyperLogLog 中进行缓存,直到下次 PFADD 执行成功前,就都不需要再次进行基数的计算。

    PFMERGE 将多个 HyperLogLog 合并为一个 HyperLogLog , 合并后的 HyperLogLog 的基数接近于所有输入 HyperLogLog 的并集基数。

    > PFADD customers alice dan
    (integer) 1
    > PFMERGE everyone visitors customers
    OK
    > PFCOUNT everyone
    (integer) 4
    

    内存消耗对比实验

    我们下面就来通过实验真实对比一下下面三种数据结构的内存消耗,HashMap、BitMap 和 HyperLogLog。

    我们首先使用 Lua 脚本向 Redis 对应的数据结构中插入一定数量的数,然后执行
    bgsave 命令,最后使用 redis-rdb-tools 的 rdb 的命令查看各个键所占的内存大小。

    下面是 Lua 的脚本,不了解 Redis 执行 Lua 脚本的同学可以看一下我之前写的文章《基于Redis和Lua的分布式限流》

    local key = KEYS[1]
    local size = tonumber(ARGV[1])
    local method = tonumber(ARGV[2])
    
    for i=1,size,1 do
      if (method == 0)
      then
        redis.call('hset',key,i,1)
      elseif (method == 1)
      then
        redis.call('pfadd',key, i)
      else
        redis.call('setbit', key, i, 1)
      end
    end
    

    我们在通过 redis-cli 的 script load 命令将 Lua 脚本加载到 Redis 中,然后使用 evalsha 命令分别向 HashMap、HyperLogLog 和 BitMap 三种数据结构中插入了一千万个数,然后使用 rdb 命令查看各个结构内存消耗。

    [root@VM_0_11_centos ~]# redis-cli -a 082203 script load "$(cat HyperLogLog.lua)"
    "6255c6d0a1f32349f59fd2c8711e93f2fbc7ecf8"
    [root@VM_0_11_centos ~]# redis-cli -a 082203 evalsha 6255c6d0a1f32349f59fd2c8711e93f2fbc7ecf8 1 hashmap 10000000 0
    (nil)
    [root@VM_0_11_centos ~]# redis-cli -a 082203 evalsha 6255c6d0a1f32349f59fd2c8711e93f2fbc7ecf8 1 hyperloglog 10000000 1
    (nil)
    [root@VM_0_11_centos ~]# redis-cli -a 082203 evalsha 6255c6d0a1f32349f59fd2c8711e93f2fbc7ecf8 1 bitmap 10000000 2
    (nil)
    
    
    [root@VM_0_11_centos ~]# rdb -c memory dump.rdb 
    database,type,key,size_in_bytes,encoding,num_elements,len_largest_element,expiry
    
    0,string,bitmap,1310768,string,1250001,1250001,
    0,string,hyperloglog,14392,string,12304,12304,
    0,hash,hashmap,441326740,hashtable,10000000,8,
    

    我们进行了两轮实验,分别插入一万数字和一千万数字,三种数据结构消耗的内存统计如下所示。

    统计图表

    从表中可以明显看出,一万数量级时 BitMap 消耗内存最小, 一千万数量级时 HyperLogLog 消耗内存最小,但是总体来看,HyperLogLog 消耗的内存都是 14392 字节,可见 HyperLogLog 在内存消耗方面有自己的独到之处。

    基本原理

    HyperLogLog 是一种概率数据结构,它使用概率算法来统计集合的近似基数。而它算法的最本源则是伯努利过程。

    伯努利过程就是一个抛硬币实验的过程。抛一枚正常硬币,落地可能是正面,也可能是反面,二者的概率都是 1/2 。伯努利过程就是一直抛硬币,直到落地时出现正面位置,并记录下抛掷次数k。比如说,抛一次硬币就出现正面了,此时 k 为 1; 第一次抛硬币是反面,则继续抛,直到第三次才出现正面,此时 k 为 3。

    对于 n 次伯努利过程,我们会得到 n 个出现正面的投掷次数值 k_1, k_2 … k_nk1,k2…k**n, 其中这里的最大值是k_max。

    根据一顿数学推导,我们可以得出一个结论: 2^{k_ max}2kma**x 来作为n的估计值。也就是说你可以根据最大投掷次数近似的推算出进行了几次伯努利过程。

    下面,我们就来讲解一下 HyperLogLog 是如何模拟伯努利过程,并最终统计集合基数的。

    HyperLogLog 在添加元素时,会通过Hash函数,将元素转为64位比特串,例如输入5,便转为101(省略前面的0,下同)。这些比特串就类似于一次抛硬币的伯努利过程。比特串中,0 代表了抛硬币落地是反面,1 代表抛硬币落地是正面,如果一个数据最终被转化了 10010000,那么从低位往高位看,我们可以认为,这串比特串可以代表一次伯努利过程,首次出现 1 的位数为5,就是抛了5次才出现正面。

    所以 HyperLogLog 的基本思想是利用集合中数字的比特串第一个 1 出现位置的最大值来预估整体基数,但是这种预估方法存在较大误差,为了改善误差情况,HyperLogLog中引入分桶平均的概念,计算 m 个桶的调和平均值。

    示意图

    Redis 中 HyperLogLog 一共分了 2^14 个桶,也就是 16384 个桶。每个桶中是一个 6 bit 的数组,如下图所示。

    桶

    HyperLogLog 将上文所说的 64 位比特串的低 14 位单独拿出,它的值就对应桶的序号,然后将剩下 50 位中第一次出现 1 的位置值设置到桶中。50位中出现1的位置值最大为50,所以每个桶中的 6 位数组正好可以表示该值。

    在设置前,要设置进桶的值是否大于桶中的旧值,如果大于才进行设置,否则不进行设置。示例如下图所示。

    示例

    此时为了性能考虑,是不会去统计当前的基数的,而是将 HyperLogLog 头的 card 属性中的标志位置为 1,表示下次进行 pfcount 操作的时候,当前的缓存值已经失效了,需要重新统计缓存值。在后面 pfcount 流程的时候,发现这个标记为失效,就会去重新统计新的基数,放入基数缓存。

    在计算近似基数时,就分别计算每个桶中的值,带入到上文将的 DV 公式中,进行调和平均和结果修正,就能得到估算的基数值。

    Redis 源码分析

    我们首先来看一下 HyperLogLog 对象的定义

    struct hllhdr {
        char magic[4];      /* 魔法值 "HYLL" */
        uint8_t encoding;   /* 密集结构或者稀疏结构 HLL_DENSE or HLL_SPARSE. */
        uint8_t notused[3]; /* 保留位, 全为0. */
        uint8_t card[8];    /* 基数大小的缓存 */
        uint8_t registers[]; /* 数据字节数组 */
    };
    

    HyperLogLog 对象中的 registers 数组就是桶,它有两种存储结构,分别为密集存储结构和稀疏存储结构,两种结构只涉及存储和桶的表现形式,从中我们可以看到 Redis 对节省内存极致地追求。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a9FEvWJ9-1602258562370)(https://segmentfault.com/img/bVbt0zP?w=1240&h=789)]

    我们先看相对简单的密集存储结构,它也是十分的简单明了,既然要有 2^14 个 6 bit的桶,那么我就真使用足够多的 uint8_t 字节去表示,只是此时会涉及到字节位置和桶的转换,因为字节有 8 位,而桶只需要 6 位。

    所以我们需要将桶的序号转换成对应的字节偏移量 offset_bytes 和其内部的位数偏移量 offset_bits。需要注意的是小端字节序,高位在右侧,需要进行倒转。

    当 offset_bits 小于等于2时,说明一个桶就在该字节内,只需要进行倒转就能得到桶的值。

    示意图

    如果 offset_bits 大于 2 ,则说明一个桶分布在两个字节内,此时需要将两个字节的内容都进行倒置,然后再进行拼接得到桶的值,如下图所示。

    示意图

    HyperLogLog 的稀疏存储结构是为了节约内存消耗,它不像密集存储模式一样,真正找了那么多个字节数组来表示2^14 个桶,而是使用特殊的字节结构来表达。

    示意图

    Redis 为了方便表达稀疏存储,它将上面三种字节表示形式分别赋予了一条指令。

    • ZERO : 一字节,表示连续多少个桶计数为0,前两位为标志00,后6位表示有多少个桶,最大为64。
    • XZERO : 两个字节,表示连续多少个桶计数为0,前两位为标志01,后14位表示有多少个桶,最大为16384。
    • VAL : 一字节,表示连续多少个桶的计数为多少,前一位为标志1,四位表示连桶内计数,所以最大表示桶的计数为32。后两位表示连续多少个桶。

    示意图

    所以,一个初始状态的 HyperLogLog 对象只需要2 字节,也就是一个 XZERO 来存储其数据,而不需要消耗12K 内存。当 HyperLogLog 插入了少数元素时,可以只使用少量的 XZERO、VAL 和 ZERO 进行表示,如下图所示。

    示意图

    Redis从稀疏存储转换到密集存储的条件是:

    • 任意一个计数值从 32 变成 33,因为 VAL 指令已经无法容纳,它能表示的计数值最大为 32
    • 稀疏存储占用的总字节数超过 3000 字节,这个阈值可以通过 hll_sparse_max_bytes 参数进行调整。

    具体 Redis 中的 HyperLogLog 源码由于涉及较多的位运算,这里就不多带大家学习了。大家对 HyperLogLog 有什么更好的理解或者问题都欢迎积极留言。

    参考

    https://thoughtbot.com/blog/hyperloglogs-in-redis
    https://juejin.im/post/5c7fe7525188251ba53b0623
    https://juejin.im/post/5bef9c706fb9a049c23204a3

    展开全文
  • HyperLogLog 是一种概率数据结构,用来估算数据的基数。数据集可以是网站访客的 IP 地址,E-mail 邮箱或者用户 ID。基数就是指一个集合中不同值的数目,...在遍历数据集时,判断当前遍历值是否已经存在唯一方法就是...

    HyperLogLog 是一种概率数据结构,用来估算数据的基数。数据集可以是网站访客的 IP 地址,E-mail 邮箱或者用户 ID。

    基数就是指一个集合中不同值的数目,比如 a, b, c, d 的基数就是 4,a, b, c, d, a 的基数还是 4。虽然 a 出现两次,只会被计算一次。

    精确的计算数据集的基数需要消耗大量的内存来存储数据集。在遍历数据集时,判断当前遍历值是否已经存在唯一方法就是将这个值与已经遍历过的值进行一一对比。当数据集的数量越来越大,内存消耗就无法忽视,甚至成了问题的关键。

    使用 Redis 统计集合的基数一般有三种方法,分别是使用 Redis 的 HashMap,BitMap 和 HyperLogLog。前两个数据结构在集合的数量级增长时,所消耗的内存会大大增加,但是 HyperLogLog 则不会。

    Redis 的 HyperLogLog 通过牺牲准确率来减少内存空间的消耗,只需要12K内存,在标准误差0.81%的前提下,能够统计2^64个数据。所以 HyperLogLog 是否适合在比如统计日活月活此类的对精度要不不高的场景。

    这是一个很惊人的结果,以如此小的内存来记录如此大数量级的数据基数。下面我们就带大家来深入了解一下 HyperLogLog 的使用,基础原理,源码实现和具体的试验数据分析。

    HyperLogLog 在 Redis 中的使用

    Redis 提供了 PFADD 、PFCOUNT 和 PFMERGE 三个命令来供用户使用 HyperLogLog。

    PFADD 用于向 HyperLogLog 添加元素。

    > PFADD visitors alice bob carol

    (integer) 1

    > PFCOUNT visitors

    (integer) 3

    如果 HyperLogLog 估计的近似基数在 PFADD 命令执行之后出现了变化, 那么命令返回 1 , 否则返回 0 。 如果命令执行时给定的键不存在, 那么程序将先创建一个空的 HyperLogLog 结构, 然后再执行命令。

    PFCOUNT 命令会给出 HyperLogLog 包含的近似基数。在计算出基数后,PFCOUNT 会将值存储在 HyperLogLog 中进行缓存,知道下次 PFADD 执行成功前,就都不需要再次进行基数的计算。

    PFMERGE 将多个 HyperLogLog 合并为一个 HyperLogLog , 合并后的 HyperLogLog 的基数接近于所有输入 HyperLogLog 的并集基数。

    > PFADD customers alice dan

    (integer) 1

    > PFMERGE everyone visitors customers

    OK

    > PFCOUNT everyone

    (integer) 4

    内存消耗对比实验

    我们下面就来通过实验真实对比一下下面三种数据结构的内存消耗,HashMap、BitMap 和 HyperLogLog。

    我们首先使用 Lua 脚本向 Redis 对应的数据结构中插入一定数量的数,然后执行

    bgsave 命令,最后使用 redis-rdb-tools 的 rdb 的命令查看各个键所占的内存大小。

    下面是 Lua 的脚本,不了解 Redis 执行 Lua 脚本的同学可以看一下我之前写的文章《基于Redis和Lua的分布式限流》。

    local key = KEYS[1]

    local size = tonumber(ARGV[1])

    local method = tonumber(ARGV[2])

    for i=1,size,1 do

    if (method == 0)

    then

    redis.call('hset',key,i,1)

    elseif (method == 1)

    then

    redis.call('pfadd',key, i)

    else

    redis.call('setbit', key, i, 1)

    end

    end

    我们在通过 redis-cli 的 script load 命令将 Lua 脚本加载到 Redis 中,然后使用 evalsha 命令分别向 HashMap、HyperLogLog 和 BitMap 三种数据结构中插入了一千万个数,然后使用 rdb 命令查看各个结构内存消耗。

    [root@VM_0_11_centos ~]# redis-cli -a 082203 script load "$(cat HyperLogLog.lua)"

    "6255c6d0a1f32349f59fd2c8711e93f2fbc7ecf8"

    [root@VM_0_11_centos ~]# redis-cli -a 082203 evalsha 6255c6d0a1f32349f59fd2c8711e93f2fbc7ecf8 1 hashmap 10000000 0

    (nil)

    [root@VM_0_11_centos ~]# redis-cli -a 082203 evalsha 6255c6d0a1f32349f59fd2c8711e93f2fbc7ecf8 1 hyperloglog 10000000 1

    (nil)

    [root@VM_0_11_centos ~]# redis-cli -a 082203 evalsha 6255c6d0a1f32349f59fd2c8711e93f2fbc7ecf8 1 bitmap 10000000 2

    (nil)

    [root@VM_0_11_centos ~]# rdb -c memory dump.rdb

    database,type,key,size_in_bytes,encoding,num_elements,len_largest_element,expiry

    0,string,bitmap,1310768,string,1250001,1250001,

    0,string,hyperloglog,14392,string,12304,12304,

    0,hash,hashmap,441326740,hashtable,10000000,8,

    我们进行了两轮实验,分别插入一万数字和一千万数字,三种数据结构消耗的内存统计如下所示。

    从表中可以明显看出,一万数量级时 BitMap 消耗内存最小, 一千万数量级时 HyperLogLog 消耗内存最小,但是总体来看,HyperLogLog 消耗的内存都是 14392 字节,可见 HyperLogLog 在内存消耗方面有自己的独到之处。

    基本原理

    HyperLogLog 是一种概率数据结构,它使用概率算法来统计集合的近似基数。而它算法的最本源则是伯努利过程。

    伯努利过程就是一个抛硬币实验的过程。抛一枚正常硬币,落地可能是正面,也可能是反面,二者的概率都是 1/2 。伯努利过程就是一直抛硬币,直到落地时出现正面位置,并记录下抛掷次数k。比如说,抛一次硬币就出现正面了,此时 k 为 1; 第一次抛硬币是反面,则继续抛,直到第三次才出现正面,此时 k 为 3。

    对于 n 次伯努利过程,我们会得到 n 个出现正面的投掷次数值 $ k_1, k_2 ... k_n $, 其中这里的最大值是k_max。

    根据一顿数学推导,我们可以得出一个结论: $2^{k_ max}$ 来作为n的估计值。也就是说你可以根据最大投掷次数近似的推算出进行了几次伯努利过程。

    ://upload-images.jianshu.io/upload_images/623378-48360099af56a3e9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

    下面,我们就来讲解一下 HyperLogLog 是如何模拟伯努利过程,并最终统计集合基数的。

    HyperLogLog 在添加元素时,会通过Hash函数,将元素转为64位比特串,例如输入5,便转为101(省略前面的0,下同)。这些比特串就类似于一次抛硬币的伯努利过程。比特串中,0 代表了抛硬币落地是反面,1 代表抛硬币落地是正面,如果一个数据最终被转化了 10010000,那么从低位往高位看,我们可以认为,这串比特串可以代表一次伯努利过程,首次出现 1 的位数为5,就是抛了5次才出现正面。

    所以 HyperLogLog 的基本思想是利用集合中数字的比特串第一个 1 出现位置的最大值来预估整体基数,但是这种预估方法存在较大误差,为了改善误差情况,HyperLogLog中引入分桶平均的概念,计算 m 个桶的调和平均值。

    Redis 中 HyperLogLog 一共分了 2^14 个桶,也就是 16384 个桶。每个桶中是一个 6 bit 的数组,如下图所示。

    HyperLogLog 将上文所说的 64 位比特串的低 14 位单独拿出,它的值就对应桶的序号,然后将剩下 50 位中第一次出现 1 的位置值设置到桶中。50位中出现1的位置值最大为50,所以每个桶中的 6 位数组正好可以表示该值。

    在设置前,要设置进桶的值是否大于桶中的旧值,如果大于才进行设置,否则不进行设置。示例如下图所示。

    此时为了性能考虑,是不会去统计当前的基数的,而是将 HyperLogLog 头的 card 属性中的标志位置为 1,表示下次进行 pfcount 操作的时候,当前的缓存值已经失效了,需要重新统计缓存值。在后面 pfcount 流程的时候,发现这个标记为失效,就会去重新统计新的基数,放入基数缓存。

    在计算近似基数时,就分别计算每个桶中的值,带入到上文将的 DV 公式中,进行调和平均和结果修正,就能得到估算的基数值。

    Redis 源码分析

    我们首先来看一下 HyperLogLog 对象的定义

    struct hllhdr {

    char magic[4]; /* 魔法值 "HYLL" */

    uint8_t encoding; /* 密集结构或者稀疏结构 HLL_DENSE or HLL_SPARSE. */

    uint8_t notused[3]; /* 保留位, 全为0. */

    uint8_t card[8]; /* 基数大小的缓存 */

    uint8_t registers[]; /* 数据字节数组 */

    };

    HyperLogLog 对象中的 registers 数组就是桶,它有两种存储结构,分别为密集存储结构和稀疏存储结构,两种结构只涉及存储和桶的表现形式,从中我们可以看到 Redis 对节省内存极致地追求。

    我们先看相对简单的密集存储结构,它也是十分的简单明了,既然要有 2^14 个 6 bit的桶,那么我就真使用足够多的 uint8_t 字节去表示,只是此时会涉及到字节位置和桶的转换,因为字节有 8 位,而桶只需要 6 位。

    所以我们需要将桶的序号转换成对应的字节偏移量 offset_bytes 和其内部的位数偏移量 offset_bits。需要注意的是小端字节序,高位在右侧,需要进行倒转。

    当 offset_bits 小于等于2时,说明一个桶就在该字节内,只需要进行倒转就能得到桶的值。

    如果 offset_bits 大于 2 ,则说明一个桶分布在两个字节内,此时需要将两个字节的内容都进行倒置,然后再进行拼接得到桶的值,如下图所示。

    HyperLogLog 的稀疏存储结构是为了节约内存消耗,它不像密集存储模式一样,真正找了那么多个字节数组来表示2^14 个桶,而是使用特殊的字节结构来表达。

    Redis 为了方便表达稀疏存储,它将上面三种字节表示形式分别赋予了一条指令。

    ZERO : 一字节,表示连续多少个桶计数为0,前两位为标志00,后6位表示有多少个桶,最大为64。

    XZERO : 两个字节,表示连续多少个桶计数为0,前两位为标志01,后14位表示有多少个桶,最大为16384。

    VAL : 一字节,表示连续多少个桶的计数为多少,前一位为标志1,四位表示连桶内计数,所以最大表示桶的计数为32。后两位表示连续多少个桶。

    所以,一个初始状态的 HyperLogLog 对象只需要2 字节,也就是一个 XZERO 来存储其数据,而不需要消耗12K 内存。当 HyperLogLog 插入了少数元素时,可以只使用少量的 XZERO、VAL 和 ZERO 进行表示,如下图所示。

    Redis从稀疏存储转换到密集存储的条件是:

    任意一个计数值从 32 变成 33,因为 VAL 指令已经无法容纳,它能表示的计数值最大为 32

    稀疏存储占用的总字节数超过 3000 字节,这个阈值可以通过 hll_sparse_max_bytes 参数进行调整。

    具体 Redis 中的 HyperLogLog 源码由于涉及较多的位运算,这里就不多带大家学习了。大家对 HyperLogLog 有什么更好的理解或者问题都欢迎积极留言。

    参考

    展开全文
  • HyperLogLog 是一种概率数据结构,用来估算数据的基数。数据集可以是网站访客的 IP 地址,E-mail 邮箱或者用户 ID。基数就是指一个集合中不同值的数目,...在遍历数据集时,判断当前遍历值是否已经存在唯一方法就是...
  • HyperLogLog 是一种概率数据结构,用来估算数据的基数。数据集可以是网站访客的 IP 地址,E-mail 邮箱或者用户 ID。...使用 Redis 统计集合的基数一般有三种方法,分别是使用 Redis 的 HashMap,Bit...
  •   (1)输入今天的日期(年,,日)和你的出生日期(年,,日),计算你在这个世界已经生存了多少天。取值范围:年:1900 – 2100,:1 – 12,日:1 – 31   (2)输入期望的年龄,计算还将在这个世界...
  • 该材质包拥有高达超过120种不同MOD,加入了例如口渴,体温,经验,技能加点,升级解锁物品使用,血量分肢计算,恐怖生物,斯巴达武器,血,冰与火,真实砍树等一众真实物理引擎且变态的MOD!所含MOD列表:(old mod...
  • Oracle优化日记:一个金牌DBA的故事(金牌DBA精彩纷呈的经历,86个优化技巧用) 基本信息 作者: 白鳝 丛书名: 图灵程序设计丛书 数据库 出版社:人民邮电出版社 ISBN:9787115230713 上架时间:2010-7-1 出版...
  • /* *Copyright (c) 2013 ,烟台大学... *All rights reserved.... *作者:王至超 *完成日期:2013年1126 ...*问题描述:利用数组方法 ...*问题分析:用简单的方法,学会用 */ #include using namespace
  • 用指针的方法交换两个数

    千次阅读 2013-12-10 17:05:13
    *All rights reserved. 作者:王至超 *完成日期:2013年1210 *版本号:v1.0 *问题描述:用指针的方法交换两个数 ...*问题分析:用简单的方法,学会用 */ #include using namespace std; void jiaohua
  • 复习-递归方法求n!

    2013-12-14 12:04:14
    /* ...*All rights reserved. *作者:张凤宁 *完成日期:2013年1214 *版本号:v1.0 *问题描述: ...*问题分析:用简单的方法,学会用 */ #include #include using namespace std; long fac(int n);
  • /* *Copyright (c) 2013 ,烟台大学计算机与控制工程学院 *All rights reserved.... *作者:王至超 *完成日期:2014年0508 ...*问题分析:用简单的方法,学会用 */ #include using namespace std;
  • 015 计算公式结果的几种方法 20 016 使用【F9】键查看公式结果 21 017 批量复制公式——按【Ctrl+Enter】组合键 21 018 批量复制公式——按【Ctrl+D】组合键 23 019 批量复制公式——拖动控制柄 24 020 ...
  • /* *Copyright (c) 2013 ,烟台大学计算机学院 *All rights reserved.... *作者:王至超 *完成日期:2013年1130 ...*问题描述:利用递归的方法 ...*问题分析:用简单的方法,学会用 */ #incl
  • /* ...*All rights reserved. *作者:王至超 *完成日期:2013年1112 *版本号:v1.0 *问题描述:输入年月日。判断为该年的第几天 ...*问题分析:用简单的方法,学会用 */ #include using namespace
  • /* *Copyright (c) 2013 ,烟台大学... *All rights reserved.... *作者:王至超 *完成日期:2013年1105 ...*问题描述:用自定义变量来输出小星星 ...*问题分析:学会用 */ #include using namespace std
  • /* *Copyright (c) 2013 ,烟台大学计算机学院 *All rights reserved. *作者:张凤宁 *完成日期:2013年1214 *版本号:v1.0 ...*问题分析:用简单的方法,学会用 */ #include using namespace s
  • /* *Copyright (c) 2013 ,烟台... *All rights reserved. *作者:王至超 *完成日期:2013年124 *版本号:v1.0 ...*问题分析:用简单的方法,学会用 */ #include using namespace std; int main() {
  • *All rights reserved. 作者:王至超 *完成日期:2013年1210 *版本号:v1.0 *问题描述:用指针的方法求最大值,无论是输入100 10还是10 100,输出最大值都...*问题分析:用简单的方法,学会用 */ #includ
  • 郭安定VBA九课程听课笔记(0913)

    千次阅读 2007-09-13 22:50:00
    一、做项目的经验1 询问客户的需求时,需仔细观察客户原先手工实现的方法。2 应该是协助客户利用计算机来提高效率,而不是用计算机来完全代替客户3 将一个大的项目拆分成若干个小项目来做,解决一点是一点,不要一下...
  • 根据姓名查找学生的学号

    千次阅读 2013-12-11 16:38:17
    *All rights reserved. 作者:王至超 *完成日期:2013年1210 *版本号:v1.0 *问题描述:用指针的方法交换两个数 ...*问题分析:用简单的方法,学会用 */ #include #include using namespace std; vo
  • /* ...*All rights reserved. *作者:王至超 *完成日期:2014年0224 *版本号:v1.0 *问题描述:利用枚举的方法...*问题分析:用简单的方法,学会用 */ #include using namespace std; int main() { enum Color {
  • /* ...*All rights reserved. *作者:王至超 *完成日期:2014年0323 *版本号:v1.0 *问题描述:利用类的方法,对工资...*问题分析:用简单的方法,学会用 */ #include using namespace std; class Salary { p
  • /* *Copyright (c) 2013 ,烟台大学计算机学院 *All rights reserved. *作者:王至超 *完成日期:2013年123 *版本号:v1.0 *问题描述:利用二维数组 ...*问题分析:用简单的方法,学会
  • /* *Copyright (c) 2013 ,烟台大学计算机学院 *All rights reserved. *作者:王至超 *完成日期:2013年123 ...*问题描述:利用二维数组求最大值 ...*问题分析:用简单的方法,学会
  • 旱冰场造价

    2014-03-16 20:07:49
    /* ...*All rights reserved. *作者:王至超 *完成日期:2014年0316 *版本号:v1.0 *问题描述: ...*问题分析:用简单的方法,学会用 */ #include using namespace std; const double

空空如也

空空如也

1 2 3 4 5 ... 11
收藏数 207
精华内容 82
关键字:

月活计算方法