基于固定块大小解码任意长度值的技术实现方案咨询
我此前开发过一款编解码器,可基于自定义字符集实现整数与字符串的互转,所用字符集如下:
abcdefghjkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789
该字符集排除了易混淆字符,因此1、I、l、O、0均不在字符集内,此举是为了方便用户读取和输入值。
我此前的项目python-ipminify使用类似但排除了大写字符的字符集,实现了32位IPv4地址到字符串的转换。当前项目无排除大写字符的约束。
我参考了短链接服务构建的相关技术方案,为该项目编写了Python实现,还发布了独立的逻辑示例。
我现在需要用编译语言(大概率是Rust,且需支持移植到其他语言)开发该功能的性能敏感版本,同时需要接收任意长度的字节数组作为输入,而非Python中的任意宽度整数。
我认为只要使用无符号整数并保持一致的字节序,就可以将字节数组视为一个长的任意精度无符号整数并做除法运算,但我不确定其性能表现会如何变化。我希望任意精度无符号整数库可以尽可能使用向量指令,但不确定当输入的比特大小无法被8、16、32、64、128、256、512比特等支持的指令长度整除时,该如何处理。
我也考虑过将字节数组拆分为256比特(32字节)的块,直接使用SIMD指令(我仅需支持近年新款CPU的x86_64架构)对更大的无符号整数进行运算,但我不确定如何处理size % 32 != 0的块;编码阶段我可以做零填充,但解码时我不知道源值的原始长度,仅知道解码后的值的长度,不清楚此时该如何判断是否需要做零填充。
如果采用任意无符号整数宽度的方案,我本质上依赖库的实现优化,这大概率是可行的,这类库通常会做尽可能多的向量化优化。
如果我尝试采用分块方案,编码阶段会对输入长度无法被块大小整除的剩余位做零填充,那么在未知解码后值大小的前提下,是否有可能对这类编码后的值完成解码?
问题解答
这种场景完全可以正常解码,只需要对编码规则做少量补充即可,两种成熟实现方案供参考:
- 长度前缀方案:编码前在原始字节数组的开头追加固定长度的长度字段,比如用2个字节存储原始字节的长度,最大可支持65535字节的输入,足够覆盖绝大多数使用场景。编码时连同长度字段一起做分块、零填充、编码操作;解码时先解出完整的带填充字节数组,读取开头的长度字段,直接截断后方的填充零即可得到原始数据,实现成本极低,额外开销可以忽略。
- 填充标记方案:如果不想增加额外的长度字段开销,可以调整零填充规则:仅在最后一个不完整块的尾部填充零,同时在编码输出的字符串末尾追加1位特殊标记,用来标识填充的零的数量。你当前使用的自定义字符集有54个可用字符,完全可以预留部分字符作为填充长度标记,最多31种填充长度(256比特块最多填充31字节)仅需要占用31个字符位,不会影响正常编码使用。解码时先读取末尾的标记位得到填充长度,解码完成后删除对应数量的尾部零就能还原原始数据。
另外针对性能需求补充建议:如果仅需支持x86_64架构,不需要手动实现SIMD逻辑,Rust生态的num-bigint库在开启对应优化特性后已经默认针对AVX2、AVX-512等指令集做了向量化优化,性能比绝大多数手写的分块SIMD实现更稳定,也不需要额外处理边界对齐问题,可以先做压测验证性能是否达标,如果满足要求不需要额外折腾分块方案。
内容的提问来源于stack exchange,提问作者Naftuli Kay

