分类 编码技巧 下的文章

基于缓存的两个局部性,可以充分利用这两个局部性写出缓存友好的代码。

0 时间局部性

时间局部性最常见的例子就是循环中访问同一变量了。

比如下面求min(inclusive)到max(exclusive)之间的整数之和:

public static int sum(int min, int max) {
    int sum = 0;
    for (int i = min; i < max; i++) {
        sum += i;
    }
    return sum;
}

sum这一变量在循环外声明,在循环内使用。这样就利用了sum的时间局部性。

实践应用提示

  1. 循环处理时,将每次都要处理的数据声明放在循环外,即用数据的时间局部性
  2. 批量处理数据时,将相同的操作放在一起执行,利用指令的时间局部性,比如读数据-处理数据-写数据,再读数据-处理数据-写数据,可以转变成读数据-读数据-处理数据-处理数据-写数据-写数据

1 空间局部性

空间局部性的例子就是数组了。

比如下面求数据之和:

public static int sum(int[] arr) {
    int sum = 0;
    for (int i = 0; i < arr.length; i++) {
        sum += arr[i];
    }
    return sum;
}

上面的例子中,arr中的每个成员在内存中的地址是相邻的,这样访问第一个元素的时候,会把相邻的第二个、第三个等元素都放在缓存中,这样后续再访问这些元素的时候就快了。

实践应用提示

  1. 经常一并访问的对象尽量放在内存地址相近的地方

本周三(2020-09-23)我进行了disruptor的分享,其中disruptor的一个优化就是缓存友好。

我就上网搜了资料,分享下在实际工作中,如何写出缓存友好的代码。

0 缓存简介

wiki链接:Cache_(computing))

计算机缓存的产生是一下两种情况的权衡:

  1. 大小和速度(缓存越大,意味着物理距离上越远,这样就会影响处理速度)
  2. 前进但昂贵的的存储技术和便宜但容易制造的存储技术(前者比如SRAM,后者比如DRAM或硬盘)

缓存有两个优势:

  1. 延迟
  2. 吞吐量

现在的计算机缓存有如下两个局部性原理:

  • 时间局部性,基于假设:最近引用的对象在不久的将来大概率会再次引用

Temporal Locality: If an address gets accessed, then it is very likely that the exact same address will be accessed once again in the near future.

int a = 1;
//  'a' will hopefully be used again soon
  • 空间局部性,基于假设:需要引用的对象周围的对象大概率会被引用

Spatial Locality: If an address gets accessed, then it is very likely that nearby addresses will be accessed in the near future.

int[] arr = new int[10];
arr[5] = 10;
//  'A[4]' or 'A[6]' will hopefully be used soon

参考资料:

  1. Writing Cache-Friendly Code

不要自己造轮子

Don't repeat yourself

DRY这句话大家可能耳朵都听出茧子了。不过,这个确实是非常重要的一个填坑要诀。

拿正常的开发(非填坑)来说,使用别人造好的轮子可以大大提高效率和开发进度。
我上次用的别人的大轮子是开发微信公众号和小程序的时候,用的wechat这个开源lib。使得集成微信的工作量大大减少,不必看微信那么多的文档和接口。

专门拿填坑来说,使用别人造好的轮子比正常开发要显得还重要一些。当然这里的轮子主要是原来工程中使用的轮子了。尽量使用工程中原来使用的轮子,别自己造,更别使用其他人的轮子。

我见过最常最轮子的方法就是类似于StringUtils和CollectionUtils这种,用来判断empty,用来split等。


- 阅读剩余部分 -

最近有这么一个需求,要给每个用户生成一个唯一的邀请码,用户可以将这个邀请码分享出去,当新用户使用这个邀请码注册登录的时候,就会给邀请者和被邀请者双方发放奖励。

常见的方法有如下几种:

  1. 直接使用用户uid做邀请码,简单直接,就好像其他app中填入邀请者的手机号一样
  2. 对uid做hash生成一串字符串做邀请码,这个主要是为了避免用户的uid泄露,但是要保存用户uid和邀请码的对应关系
  3. 采用对称加密算法加密uid,这样直接根据邀请码就可以解出用户uid,不需要保存uid和邀请码的对应关系了

我们这边的用户uid是纯数字的,共有13位,给这13位uid生成hash或加密的话,结果会较长,不方便用户分享和填写,产品经理限制邀请码长度为6位,同时生成的邀请码中不能要oO1ILl等容易混淆的字符。

- 阅读剩余部分 -

Java8中推出了Optional这个class,用来做null check,我在实际工作中也经常使用,确实方便很多,不过也遇到了不少坑,现在写出来供大家参考。

0x01 这些情况不要使用Optional

  1. 集合类不要包装为Optional。集合类永远必须返回集合,不允许返回null值,即使如果没有数据,也要返回空集合。返回空集合避免了NPE,特别是使用stream接口处理起来非常方便。
  2. POJO的属性不要使用Optional。Optional不要作为class的属性,如果要对空属性进行处理,总是应该在对应属性的getter方法上做。同时由于Optional没有实现Serializable接口,所以这些类在序列化的时候也非常麻烦。
  3. RPC服务的方法返回值和参数不要使用Optional。正如第二点所说,Optional不能正常序列化,所以使用RPC服务时,如果Optional作为参数或者返回值进行调用的时候,序列化失败就会报错。(但是如果方法返回值不能返回Optional对象,而返回null的话又失去了Optional存在的意义。SO上也有Optional为啥不实现Serializable接口的讨论,地址在此stackoverflow

- 阅读剩余部分 -