LawrencePeng's Blog

专注收集代码小精灵

Odd Sketch

之前介绍了过一个Sketch了,现在再介绍一个吧。今天介绍的这个是Odd Sketch。它是用来快速估计集合相似度Jaccard similarity的。

  • 实现
    • 新建一个n bits的sketch S,置0。
    • 选一个随机哈希函数
    • 对集合每个元素
      • Sh(x) = Sh(x) xor 1
    • 返回S
  • 性质
    • Odd(S1) xor Odd(S2) = Odd(S1 diff S2)
  • 估计
    • 估计集合大小
      • 让m和n分别代表集合S和Odd(S)的比特大小。因为哈希函数全随机,我们可以认为构造Odd(S)的过程是m球丢n桶实验。Odd(S)i最后为第i个桶的球数的奇偶性。这样想的话我们有两种方式来构造集合大小。
        1. 每一次丢球,对于任意一个桶来说都有1/n的几率转换球数奇偶性的状态,所以就当成简单的2-state Markov chain就好了。设pi为丢完i个球后任意一个给定的桶有奇数个球的概率。我们很容易知道:pi = (1 - (1 - 2/n)^i) / 2,这是标准的马式链的题目了。
        2. 不难推出m个球丢出去后所有奇数桶的数量的期望E[X] = n (1 - ((1 - 2 / n) ^ m) / 2)
        3. 现在假设有了z为奇数桶数,不难倒估m为ln(1 - 2z/ n) / ln(1 - 2/n)
      • 当m球丢进了n桶,对于一个桶来说,可以认为其为一个均值u = m / n的泊松过程,
        • 令p为有一个桶奇数个球的概率,则p = sum all i odd (e^(-u) * (u^i)) / i! = (1 - e^(-2u))/2。
        • 令Y为整个过程结束后m个桶中奇数球数桶的期望,则有: E[Y] = np = n(1 - (e^(-2m / n)) / 2)
        • 所以通过奇数桶数z可倒估m为-(n/2) * ln(1 - 2z/n)。
        • 当n很大时,ln(1 - 2/n)约等于-2/n,这样和第一个等式就差不多了。
    • 估计Jaccard Similarity
      • E[|S1 diff S2|] = 2k(1 - J),其中k是独立置换的数量。J是我们要估计的Jaccard Similarity。
      • 我们上面的Odd Sketch的过程可以帮助我们找到|S1 diff S2|的一个估计为- (n / 2) * ln(1 - 2 |Odd(S1) diff Pdd(S2)|/n),其中|Odd(S1) diff Odd(S2)|就是我们构造的的2个Odd Sketch不同的位数。现在根据E[|S1 diff S2|] = 2k(1 - J),我们可以逆估
        • J = 1 - |S1 Diff S2|/2k = 1 + (n/4k) * ln(1 - 2|Odd(S1) diff Odd(S2)| / n)
  • 后面的其他的估计我们就不讲了,说下结论,它在高Jaccard Similarity的情况下,比minhash的表现更好哦。

CRDT:A Way to implement eventual consistency

  • BlahBlahBlah
    • 很多人都应该听过CAP Theorem,如果你没有,理解这篇文章可能有点费劲,推荐你看看。
    • 如果你选择了AP,那么你最终可能会面临的问题就是你要不要达到最终一致性了。
    • 如果你选择了否,那么这篇文章可能不对应你的场景了。
    • 如果你选择了是,那么如何实现最终一致性呢?这就导出了CRDT。
  • 原理
    • 有两类的CRDT。
    • CmRDT
      • 结点和结点传输的是符合交换律的操作,如果不符合幂等性,那么你可能需要依赖你的通讯机制能够达到extract one message delivered的要求。
      • Pure CmRDT可以减少所需要传输的metadata的大小。
    • CvRDT
      • 结点和结点传输的是状态,存在一个算子merge: (SA, SB) => SC,收到传输的状态就和自己存储的状态merge,这个算子必须满足交换律、结合律和幂等律,所以如果用抽象代数的角度形容地话,其使整个状态系统形成了一个半格。所以最终只要能接受到其他所有节点的新状态,那么永远能保证系统最终会收敛。这样也去除了对于CmRDT的底层通讯机制依赖,但是因为传递的是状态,所以最后传输可能有点大。虽然还有优化的可能性。
  • 例子我就不举了,大家G嫂一下就好了。

为什么你应该对生活充满希望?

  • 最近刚好到了秋招的时间段,相信各位老饼们肯定都在摩拳擦掌地准备ing,或者成功ed。不管怎样,都要努力哦。
  • Birthday Paradox
    • 为什么你应该对生活充满希望?或者说为什么你应该坚持呢?
    • 我相信生日悖论会把很多东西解释地很清楚。
    • n个人随机分发给n^2个坑(或者生日),有50%的几率会有冲突(两人站一个坑。)
    • 攻击方式:生日攻击。
      • 既然那么高那就搞事情嘛,所以crypto hash的话,大小要设好哦。
    • 所以你应该勇敢暴破(坚持)哦。
    • 对了,最近CACM有个讲暴破的文章挺好玩的。
  • 祝大家秋招都有好job哦。

SuccinctStore深度解析 — CSA实现

  • 前提
    • SuccinctStore依靠Compressed Suffix Array这一数据结构进行构建,实际上这也是因为CSA在空间使用上会优于CST。不过,如果没有从理论上明白它的一些时空复杂度,我相信是没人刚用的。要是突然我能构造出一个异常的输入样本,导致糟糕的怎么办?所以我们先来看下它的时间空间复杂度吧。
    • 时间复杂度:O(n),实际上有很多好的CSA的构建算法都能达到这个上界,做竞赛的同学们可能会比较熟悉DC3算法。
    • 空间复杂度:O(n),这个也没什么悬念的?真的吗?对于CSA来说,我们需要更加紧的上界,这里就要提出k-th order empirical entropy了。
      • zero-th order empirical entropy
        • 要理解k-th order empirical entropy,首先要理解zero-th order empirical entropy。
        • zero-th order empirical entropy是单独压缩每个单词最后能压缩的大小,可以看出,它并没有利用到上下文信息。
      • k-th order empirical entropy,高级的压缩器能利用这一上下文并根据这一上下文选出不同的codeword,k是这个上下文的大小,k越大,当然压缩率会越高,但是这个上下文表示所需要的空间也会相应变大,一般我们需要有个empirical的选值。公式大家可以g娘一下。
      • CSA的空间复杂度为O(nHk(T)) + o(n)
  • 实现
    • 目标
      • f = compress(file)
      • append(f, buffer)
      • buffer = extract(f, offset, len)
      • cnt = count(f, str)
      • offsets = search(f, str)
      • offsets = rangesearch(f, str1, str2)
      • ranges = wildcardsearch(f, prefix, suffix, dist)
      • offsets = regex(f, pattern)
    • Flat Text To Semi-structured Data
      • 一些简单的拓展就能让我们实现更加复杂的数据模型。
    • Suffix Array 运用
      • 我们维护两个数组AoS2Input和Input2AoS,不难看出这两个互为反函数。
      • 当我们要search某Pattern p的时候,binary search p所在的两个端点,返回成区间形式即可。
      • 当我们要extract的时候,利用Input2AoS,直接是一个O(1)的操作。
    • 压压压
      • 原先的实现会需要维护O(nlog + n^2)的空间。所以考虑引入新的数组NextCharIdx,NextCharIdx(idx) -> NextIndexInAoS。
      • 这样我们只需要mark每个单词出现的首个index即刻,后面需要定位的时候follow NextCharIdx,通过binary search到对应的字符Offset即可。
      • 对于AoS2Input和Input2AoS两个输出,我们采用取样的方法减小其大小。默认选择这两个数组的大小为原数组的1/32。
      • 因为NextCharIdx本身是单调递增的,所以我们可以选择一些编码方式做DeltaCompression, 这里我使用的是Elias Gamma Code。

如何给SuccinctStore添加正则引擎

  • 引论
    • 正则语言本身属于3型语言,原本按道理只需要一个简单的实现就可以了,但是这部分的水其实非常的深。
      • 强化正则有back reference。
      • 需不需要支持jit
      • 要兼容POSIX标准?
      • 要做online parsing么?
      • 实现打算怎么做?
    • 常见实现方式
      • 回溯
        • 性能可能会非常差,ReDOS的元凶。
      • NFA
        • 很容易把正则语言parse成NFA,然而这样就很难做backreference了。
      • m-gram index
        • 空间换时间
      • DFA
        • 马马虎虎,也有NFA的局部问题。
  • SuccinctStore Regex
    • SuccinctStore Regex选择把底层依赖的SuccinctStore当成BlackBox,因为SuccinctStore的Compressed Suffix Array的实现,其效果和m-gram时间复杂度差不多,但是因为SuccinctStore无需耗费多余的空间做index,其本身就是index,所以其在空间和时间中找到了比较好的平衡。
  • 参考资料
    • Russ Cox的blog提及了很多Regex的实现方面的好文章。
    • 《Parsing Technology》讲的也很透。

在压缩数据进行检索-SuccinctStore解析

  • 动机
    • 当今数据库实现Secondary Index的方式主要包括列族和建立索引两种方式。前者对于空间非常高效,但是当数据量达到一定规模后,检索性能会有开始下降。而后者虽然能够狠高效的检索,却可能会需要付出额外的存储空间,最后可能只能store在外存,无法很好的利用memory,虽然索引也可以选择一些前缀压缩的方式,但是也带来了其他问题。
    • 如果能够将数据进行压缩后直接进行检索,那么我们就能把更多的数据放在memory中,关键是这种技术真的存在么?
    • 答案是肯定的,这种技术叫做Succinct Data Structure,虽然我觉得称为技术有点不太对,因为这本质上是一种更加省空间的数据结构的实现方式。并且能达到Self-Indexing(数据就是索引)的效果。这部分的内容MIT的Advanced Data Structures有提及,对这部分感兴趣的同学可以去看看。
  • 设计选择
    • 我选择了Succinct Suffix Array来实现整个数据库存储引擎。
    • 通过引入NPA(Next Position Array)来减少对于SA,ISA等其他存储的需要。这些后面我会有机会会介绍。
    • 正则表达式的支持是我目前在努力实现的下一部分功能。
  • 目标
    • 通过SuccinctStore提供的高效检索实现,实现一个减量不减质的缩水版lucene,构建一个单机上性能较高的搜索引擎。预期应该会比其他同类型的实现快上许多:)
  • 缺陷
    • SuccinctStore的实现会导致顺序读的性能有些折扣,它更加适合随机读多的场景。

如何Query on Compressed Data?

  • 常见做法:
    • 使用通用压缩技术
      • snappy、lz4
    • FM-Index
      • 一种基于BWT的Self-Index技术
    • Succinct Data Structure
      • 一种以信息论下界压缩数据结构的方式。
      • 目前使用这种方法
      • 缺点是构造速度慢,immutable。
      • 所以选择使用Duo Store的方式解决
  • 支持正则搜索
    • 目前是写了个正则parser,对应interpret到Succinct Store的底层api。
  • 优势
    • 放更多数据到内存
    • 无需索引设计,或者数据就是索引本身
    • 用于搜索引擎无需分词降低命中率
  • 劣势
    • 顺序读性能下降。

CountMin Sketch

  • 目的:在流计算的场景中,我们经常需要做Freq Counting,而普通的HashTable、Counted BF都有Linear的空间复杂度,CountMin Sketch能让你把性能降到Sub Linear,当然可能会有误差。
  • 过程:
    • 构建一个m * d的二维数组,每一行都有一个hash函数控制。
    • 遍历(添加)每个元素时,算出d个0 - (m - 1)的hash value,对应d个槽自增下
    • 如果要计算某个值的Freq,依然hash出那d个value,去对应槽中值最小的。
  • 性质
    • 很明显这个一个只会高估不会低估的过程,对于在长尾上的元素,很可能因为其他元素也映射到了一样的槽中,产生大量冲突,造成误差,所以也有Count-Min-Mean Sketch,这里就不介绍了,有兴趣的可以自己查阅相关资料。

用Go实现JVM指令集

  • go-jvm项目已经实现了接近80%的指令集,目前已经能执行一些简单的jvm程序。
  • 指令集的实现在go-jvm项目的instructions目录中。
  • 取一部分go-jvm做高斯5050的代码执行给大家看看。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
pc:11 inst:*loads.ILOAD_2 &{{}}
pc:12 inst:*math.IADD &{{}}
pc:13 inst:*stores.ISTORE_1 &{{}}
pc:14 inst:*math.IINC &{2 1}
pc:17 inst:*control.GOTO &{{-13}}
pc: 4 inst:*loads.ILOAD_2 &{{}}
pc: 5 inst:*constants.BIPUSH &{100}
pc: 7 inst:*comparisons.IF_ICMPGT &{{13}}
pc:10 inst:*loads.ILOAD_1 &{{}}
pc:11 inst:*loads.ILOAD_2 &{{}}
pc:12 inst:*math.IADD &{{}}
pc:13 inst:*stores.ISTORE_1 &{{}}
pc:14 inst:*math.IINC &{2 1}
pc:17 inst:*control.GOTO &{{-13}}
pc: 4 inst:*loads.ILOAD_2 &{{}}
pc: 5 inst:*constants.BIPUSH &{100}
pc: 7 inst:*comparisons.IF_ICMPGT &{{13}}
pc:10 inst:*loads.ILOAD_1 &{{}}
pc:11 inst:*loads.ILOAD_2 &{{}}
pc:12 inst:*math.IADD &{{}}
pc:13 inst:*stores.ISTORE_1 &{{}}
pc:14 inst:*math.IINC &{2 1}
pc:17 inst:*control.GOTO &{{-13}}
pc: 4 inst:*loads.ILOAD_2 &{{}}
pc: 5 inst:*constants.BIPUSH &{100}
pc: 7 inst:*comparisons.IF_ICMPGT &{{13}}
pc:10 inst:*loads.ILOAD_1 &{{}}
pc:11 inst:*loads.ILOAD_2 &{{}}
pc:12 inst:*math.IADD &{{}}
pc:13 inst:*stores.ISTORE_1 &{{}}
pc:14 inst:*math.IINC &{2 1}
pc:17 inst:*control.GOTO &{{-13}}
pc: 4 inst:*loads.ILOAD_2 &{{}}
pc: 5 inst:*constants.BIPUSH &{100}
pc: 7 inst:*comparisons.IF_ICMPGT &{{13}}
pc:10 inst:*loads.ILOAD_1 &{{}}
pc:11 inst:*loads.ILOAD_2 &{{}}
pc:12 inst:*math.IADD &{{}}
pc:13 inst:*stores.ISTORE_1 &{{}}
pc:14 inst:*math.IINC &{2 1}
pc:17 inst:*control.GOTO &{{-13}}
pc: 4 inst:*loads.ILOAD_2 &{{}}
pc: 5 inst:*constants.BIPUSH &{100}
pc: 7 inst:*comparisons.IF_ICMPGT &{{13}}
pc:10 inst:*loads.ILOAD_1 &{{}}
pc:11 inst:*loads.ILOAD_2 &{{}}
pc:12 inst:*math.IADD &{{}}
pc:13 inst:*stores.ISTORE_1 &{{}}
pc:14 inst:*math.IINC &{2 1}
pc:17 inst:*control.GOTO &{{-13}}
pc: 4 inst:*loads.ILOAD_2 &{{}}
pc: 5 inst:*constants.BIPUSH &{100}
pc: 7 inst:*comparisons.IF_ICMPGT &{{13}}
LocalVars:[{0 <nil>} {5050 <nil>} {101 <nil>}]
  • 确实说明高斯确实是对的。。。