Spark之BloomFilter有趣的bitwise运算

简介: 最近好奇的研究了下Spark的BloomFilter的实现,发现其org/apache/spark/util/sketch/BitArray.java对bit处理的实现很巧妙(源码可能是从其他开源项目借鉴的也不好说),从中学到不少东西,记录下。BitArray巧妙的核心设计BitArray内部采用long[] data来表示一个大的bitmap,long类型相比int在相同的数组个数下可以存放更多的bit信息。比较有意思的是set方法的实现,核心代码如下:// 将指定index位置的bit位设置为1,表示指定的index处有值void set(long index) { d

最近好奇的研究了下Spark的BloomFilter的实现,发现其org/apache/spark/util/sketch/BitArray.java对bit处理的实现很巧妙(源码可能是从其他开源项目借鉴的也不好说),从中学到不少东西,记录下。

BitArray巧妙的核心设计
BitArray内部采用long[] data来表示一个大的bitmap,long类型相比int在相同的数组个数下可以存放更多的bit信息。

比较有意思的是set方法的实现,核心代码如下:

// 将指定index位置的bit位设置为1,表示指定的index处有值
void set(long index) {
data[(int) (index >>> 6)] |= (1L << index);
}
估计不少读者看到这行代码比较懵逼,由于工作中使用bit操作的场景不多,至少我当时是比较懵逼的。

先来说下>>>, >>是按位右移操作(bitwise shift)相信大家都清楚,>>是保留符号位的,>>>则是无符号右移,也就说要移的数为正,则高位补0,而若该数为负数,则右移后高位同样补0,还是看个例子比较直观:

jshell> -2 >> 1
$1 ==> -1

jshell> -2 >>> 1
$2 ==> 2147483647
那么index >>> 6又是什么鬼?

Java中一个long类型占8个byte,也就是64个bit,如果让我们实现给定一个index,判断该index属于数组的第几个元素,我们可能会这么实现:

data[index / 64]
而2^6刚好是64,index >>> 6则相当于index / 64,而按位(bitwise)操作效率上是高于买游戏账号除法的,所以,会看到代码里采用了index >>> 6的实现。

现在定位到了index所处data数组中的位置了,怎么给指定的index设置bit位为1呢?

假如我们手头上有个int类型的变量a,以a |= (1 << 3)为例:采用按位或的操作就可以将a的第3个bit位设置为1,这个比较基础,相信大家都明白。

BitArray中让人懵逼的是直接对一个long类型的值进行了1L << index操作,如果让我实现的话,我会这么做:1 << (index % 64),我们知道long只有64位,如果index大于64位呢?

如果不实际运行代码,1L << 65,由于移位数超出了long的表示范围,估计不少人会认为得到的值是0吧,我们先看下实际的结果:

jshell> 1L << 65
$1 ==> 2
为啥会是这样?各种找资料,最后在Oracle官方的Java SE Specifications中找到了答案:

If the promoted type of the left-hand operand is long, then only the six lowest-order bits of the right-hand operand are used as the shift distance. It is as if the right-hand operand were subjected to a bitwise logical AND operator & (§15.22.1) with the mask value 0x3f (0b111111). The shift distance actually used is therefore always in the range 0 to 63, inclusive.
原来Java内部在做移位(shift operation)操作时,会将右边的shift distance和0x3f按位与后,在做移位操作,这样就能保证移位的范围总是0~63。也就是说对于long类型的位移操作Java内部自动给我们做了% 64的处理。类似的,如果是int类型的操作,Java内部会将移位的距离& 0x1f确保移位范围在0~31。

我们把1L << index按Java内部的处理拆解下:

1L << index 相当于
1L << (index & 0x3f) 相当于
1L << (index & 0b111111) 相当于
1L << (index % 2^6) 相当于
1L << (index % 64)
在回头看下1L << 65其实就是1L << (65 % 64)等价于1L << 1得到的值是2。这个问题就比较清楚了。

回到BitArray的set方法的实现上:

void set(long index) {
data[(int) (index >>> 6)] |= (1L << index);
}

// 等价于
void set(long index) {
data[index / 64] |= (1L << (index % 64));
}
这下就不懵逼了。bitwise操作性能更好,data[index / 64] |= (1L << (index % 64))看起来更直观,顺便查了下这种操作JVM是否会自动优化为bitwise操作,文章提到会优化。

一些常用的bitwise运算实例
鉴于bitwise的操作比较有意思,我整理了些常用实例,至少以后遇到这种操作不让自己那么懵逼。

判断int型变量a是奇数还是偶数

a & 1 = 0 // 偶数
a & 1 = 1 // 奇数
取模运算转化成位运算 (在不产生溢出的情况下)

a % (2^n) 等价于 a & (2^n - 1)

如:a % 32 等价于 a & 0x1f
乘法运算转化成位运算 (在不产生溢出的情况下)

a * (2^n) 等价于 a << n
除法运算转化成位运算 (在不产生溢出的情况下)

a / (2^n) 等价于 a >> n
取相反数

x的相反数为~x + 1
// 原理上和计算机采用二进制补码(2's complement)的表示形式有关
其他

if (x == a):
x = b
else:
x = a

等价于:x = a ^ b ^ x
附:Spark的BloomFilter之Hash算法
最后看了下Spark内部的BloomFilter关于Hash算法选择的问题,发现其采用的是Murmur3Hash,第一次听说这个Hash算法,了解下是谷歌的东西,优势在于性能。Spark源码里直接将MurmurHash从Guava包中搬过来了。

Spark内部除了BloomFilter在用MurmurHash以外,UnsafeRow在计算Hash时,也有用到。

查了下MurmurHash应用的还是挺广泛的,Redis,Memcached,Cassandra,HBase,Lucene都有在用。对于MurmurHash自己真是孤陋寡闻了,看来平时还得多看看源码。

目录
相关文章
|
分布式计算 Ubuntu Java
【Spark】一个例子带你了解Spark运算流程
【Spark】一个例子带你了解Spark运算流程
156 0
|
9天前
|
分布式计算 大数据 Apache
ClickHouse与大数据生态集成:Spark & Flink 实战
【10月更文挑战第26天】在当今这个数据爆炸的时代,能够高效地处理和分析海量数据成为了企业和组织提升竞争力的关键。作为一款高性能的列式数据库系统,ClickHouse 在大数据分析领域展现出了卓越的能力。然而,为了充分利用ClickHouse的优势,将其与现有的大数据处理框架(如Apache Spark和Apache Flink)进行集成变得尤为重要。本文将从我个人的角度出发,探讨如何通过这些技术的结合,实现对大规模数据的实时处理和分析。
38 2
ClickHouse与大数据生态集成:Spark & Flink 实战
|
1月前
|
存储 分布式计算 算法
大数据-106 Spark Graph X 计算学习 案例:1图的基本计算、2连通图算法、3寻找相同的用户
大数据-106 Spark Graph X 计算学习 案例:1图的基本计算、2连通图算法、3寻找相同的用户
58 0
|
1月前
|
消息中间件 分布式计算 NoSQL
大数据-104 Spark Streaming Kafka Offset Scala实现Redis管理Offset并更新
大数据-104 Spark Streaming Kafka Offset Scala实现Redis管理Offset并更新
38 0
|
1月前
|
消息中间件 存储 分布式计算
大数据-103 Spark Streaming Kafka Offset管理详解 Scala自定义Offset
大数据-103 Spark Streaming Kafka Offset管理详解 Scala自定义Offset
75 0
|
10天前
|
SQL 机器学习/深度学习 分布式计算
Spark快速上手:揭秘大数据处理的高效秘密,让你轻松应对海量数据
【10月更文挑战第25天】本文全面介绍了大数据处理框架 Spark,涵盖其基本概念、安装配置、编程模型及实际应用。Spark 是一个高效的分布式计算平台,支持批处理、实时流处理、SQL 查询和机器学习等任务。通过详细的技术综述和示例代码,帮助读者快速掌握 Spark 的核心技能。
36 6
|
9天前
|
存储 分布式计算 Hadoop
数据湖技术:Hadoop与Spark在大数据处理中的协同作用
【10月更文挑战第27天】在大数据时代,数据湖技术凭借其灵活性和成本效益成为企业存储和分析大规模异构数据的首选。Hadoop和Spark作为数据湖技术的核心组件,通过HDFS存储数据和Spark进行高效计算,实现了数据处理的优化。本文探讨了Hadoop与Spark的最佳实践,包括数据存储、处理、安全和可视化等方面,展示了它们在实际应用中的协同效应。
44 2
|
9天前
|
存储 分布式计算 Hadoop
数据湖技术:Hadoop与Spark在大数据处理中的协同作用
【10月更文挑战第26天】本文详细探讨了Hadoop与Spark在大数据处理中的协同作用,通过具体案例展示了两者的最佳实践。Hadoop的HDFS和MapReduce负责数据存储和预处理,确保高可靠性和容错性;Spark则凭借其高性能和丰富的API,进行深度分析和机器学习,实现高效的批处理和实时处理。
42 1
|
10天前
|
分布式计算 大数据 OLAP
AnalyticDB与大数据生态集成:Spark & Flink
【10月更文挑战第25天】在大数据时代,实时数据处理和分析变得越来越重要。AnalyticDB(ADB)是阿里云推出的一款完全托管的实时数据仓库服务,支持PB级数据的实时分析。为了充分发挥AnalyticDB的潜力,将其与大数据处理工具如Apache Spark和Apache Flink集成是非常必要的。本文将从我个人的角度出发,分享如何将AnalyticDB与Spark和Flink集成,构建端到端的大数据处理流水线,实现数据的实时分析和处理。
41 1
|
21天前
|
分布式计算 大数据 Apache
利用.NET进行大数据处理:Apache Spark与.NET for Apache Spark
【10月更文挑战第15天】随着大数据成为企业决策和技术创新的关键驱动力,Apache Spark作为高效的大数据处理引擎,广受青睐。然而,.NET开发者面临使用Spark的门槛。本文介绍.NET for Apache Spark,展示如何通过C#和F#等.NET语言,结合Spark的强大功能进行大数据处理,简化开发流程并提升效率。示例代码演示了读取CSV文件及统计分析的基本操作,突显了.NET for Apache Spark的易用性和强大功能。
32 1
下一篇
无影云桌面