我们如何解析BSON不会产生开销性能

BSON是一个鲜为人知的图书馆,经常使用。 它是MongoKitten的核心,并且BSON规范一直被所有MongoDB用户使用。

我们开发了一个BSON库,它通过利用规范细节和我们能想到的每一个技巧,都胜过其他所有库。

序列化

在我们的BSON库中,序列化几乎不影响性能。 俩?

对于每种可能的操作,我们创建了一种专门执行该操作的专门算法。 两个操作可能共享相同的基本元数据要求,例如元素的位置。 为此,我们保留了文档中所有元素元数据的缓存。 这样,当一个操作第一次遇到一个元素时,其他操作就不需要花费时间了。

这样,我们可以高效地进行解析,并始终保持所有数据序列化。 这也意味着我们绝不会反序列化甚至读取不需要的数据。

这样可以节省CPU性能,内存副本和内存使用率,这是该库独有的。

利用规范

BSON的规范(http://bsonspec.org)具有始终位于消息核心的元素。 文献。 文档从Int32的整个长度开始,以空终止符结束。

规范中存储了长度和空终止符的原因是为了提高解析性能。 BSON可以递归嵌套文档。 因此,如果您想跳到同一级别的下一个元素,则这两个属性可以提高解析性能。 但是,如果要将元素添加到文档,则需要删除null终止符,添加该元素,然后再次添加null终止符。 并且在文档的内容更改之后,将执行其他操作以更新文档的长度。

这是没有问题的,直到您意识到此时添加5个属性会花费15个额外的堆操作。 而且堆非常昂贵。

因此,我们从顶级文档的内部存储中删除了Int32和null终止符。 这使这些操作的成本降低了四倍。

解析/提取

BSON的数据始终是序列化的,因此提取需要即时进行。 我们通过懒惰地搜索和反序列化文档中的信息来做到这一点。 在您要求我们提供某些信息之前,我们不知道文件的内容。 届时,我们将有效地解析文档,直到找到所需的信息。 这样,我们解析的内容不会超出我们的需要。

  { 
“ username”:“ Joannis”,
“年龄”:21岁,
“男”:是的,
“ admin”:是的,
“权限”:[“全部”,“更多”,“无限”]
}

如果您要访问键“用户名”,则将仅查看和缓存此“用户名”键,因为它是文档中的第一个实体。 不会扫描,缓存或查看“年龄”,“男性”,“管理员”和“权限”。

现在,当您访问male ,解析器将在username之后恢复并找到age而不反序列化值。 接下来,找到male ,将其击中,反序列化该值,停止扫描并返回该值。 现在,如果您请求age ,它已经知道age在哪里并反序列化此信息。

如果您寻找nonexistingkey ,它将一直扫描直到permissions不匹配为止,并将顶级文档标记为“完全扫描”,因此无需再次扫描。 防止将来无用的扫描无法阅读文档末尾。

如果由于某种原因您需要访问权限的第二个[1]属性,它根本不会提取permissions文档。 而是以递归的方式开始扫描permissions内的值,就像使用顶级文档一样。

而且,如果您现在将permissions密钥完全复制到一个单独的值中,则此Document类型是Array-variant,它将从顶级Document复制缓存,因此不需要扫描第一个和第二个密钥,已经知道。

此缓存是一个class ,因此在提取子文档时,它不会不必要地复制缓存。 而是,每个子文档都会跟踪其缓存是原始缓存还是其他缓存的副本。 在第二种情况下,它将在突变之前将缓存的内容复制到新类。

结果

这些优化使我思考了很多有关计算机如何工作以及如何编写高质量代码的问题。 许多人低估了堆内存的实际使用成本。 分配不必要的新内存是使您的应用程序变慢的一种常见方法,而不必要的解析会花费您的CPU精力以产生不会被使用的结果。

Interesting Posts