通过延续(Continuations)传递数据库对象
Passing DBs through continuations

原始链接: https://remy.wang/blog/cps.html

构建高性能数据库通常需要在缓慢的“迭代器模型”与工作量巨大的向量化或查询编译方法之间做出选择。 作者为其系统 **Prela** 提出了一种优雅的替代方案,即使用**续体传递风格 (CPS)**。基于 CPS 的算子不再将算子定义为物化中间表的函数,而是通过续体函数 ($k$) 来定义如何处理数据。 通过以 CPS 定义算子并利用编译器内联(消除中间数据结构),Prela 能够自动将复杂的查询链“融合”为单一且高效的机器码循环。例如,多步连接操作可以从嵌套函数调用转换为紧凑且对缓存友好的循环,直接访问列式数据。 这种设计非常精简,仅用 1,000 行 Julia 代码就实现了选择、投影、连接和聚合功能。尽管目前该方案仍依赖于稠密主键和 JIT 编译时间等假设,但 CPS 方法构建了一种模块化且可扩展的架构。它成功弥合了高层数据库逻辑与底层执行效率之间的鸿沟,实现了与 DuckDB 等行业标准引擎相当的性能。

```Hacker News最新 | 过往 | 评论 | 提问 | 展示 | 招聘 | 提交登录通过续延传递数据库 (remy.wang)4 分,由 remywang 于 1 小时前发布 | 隐藏 | 过往 | 收藏 | 讨论 帮助 准则 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:```
相关文章

原文
cps

Dedicated to the Minnowbrook Analytic Reasoning Seminar with special thanks to Kris Micinski and Michael Ballantyne

Suppose you want to write a database. You'd probably start by implementing relational algebra operators — projection, filter, join, etc. The easy way is to implement them as functions that take in tables and return tables, and assemble them into a larger expression. That was how Prela worked in its first incarnation. The code was clean, but it was hella slow! Which was not surprising, because every operator materialized every intermediate result. The standard solution to this is the iterator model, where each operator implements an Iterator interface that streams intermediate tables row by row instead of materializing them. But implementing the iterator model naively still incurs overhead: every call to Iterator.next() triggers a dynamic dispatch, which costs vtable lookups and destroys cache locality. There are two standard remedies: vectorization and compilation. A vectorized database amortizes the overhead by implementing Iterator.next_batch() which returns a whole batch of data that can be processed together; a compiled database, well, compiles the incoming query directly to fast machine code that runs without any dynamic dispatch. Either approach takes a lot of very smart people spending their entire working life to build, and it's why systems like DuckDB and Umbra exist. I'm moderately smart but don't have a lot of time, so I was looking for a shortcut. The shortcut I stumbled upon was so beautiful that I literally cried when I finally understood it, and I hope my explanation below will make you cry too :' )

To keep things simple, let's suppose we're just dealing with lists of numbers, and we want to do two very simple things to them: inc adds 1 to every number, and dbl doubles them. That's pretty easy to write:

Cthulhu.jl that does that interactively. In summary, the example shows that if we define operators modularly with CPS, inlining the definitions will automatically produce tightly fused compiled code.

None of these is really new, and have been known to functional programmers for decades by the name of deforestation. But when implemented in Prela, something incredible happens: a clean CPS-style interpreter for Prela automagically recovers fast columnar execution when compiled!

The central design principle behind Prela is "everything is a binary relation". This means Prela maps cleanly to both a logical Entity/Relationship data model, as well as to a columnar physical storage. I won't go into details here, but encourage you to play with the language to get a feel for that. Practically, this means we fully normalize every wide table with m attributes to m binary relations. For example, a table movie with columns ID, year, title becomes:

  1. The identity relation (which I'll call movie) over ID (you can think of this as a unary table over ID)
  2. A table mapping each ID to year
  3. A table mapping each ID to title

But now the issue is, even for a simple SELECT * FROM movie we need to join together 3 different tables! Whereas a column store would simply run: