ART树在订单簿管理中的应用
背景
近期在工作中,我接触到了一种高效的数据结构——自适应基数树(Adaptive Radix Tree,ART)。ART 是一种基于基数树(Radix Tree)的数据结构,旨在提供高效的键值存储和查找功能。与传统的基数树不同,ART 通过自适应调整节点大小(如 Node4、Node16、Node48 和 Node256),实现了更高的空间和时间效率。
ART 具有以下显著特点:
- 高效的键值存储和查找:ART 通过压缩路径和自适应节点大小,显著减少了内存占用,并提高了查找速度。
- 前缀压缩:ART 采用前缀压缩技术,能够有效处理具有相同前缀的大量键,进一步优化了存储效率。
- 动态调整节点大小:ART 根据实际数据分布动态调整节点大小,确保在不同负载下都能保持高效的性能。
- 适用于多种应用场景:ART 广泛应用于数据库系统、内存缓存和其他需要高效键值存储的场景。
通过使用 ART,我们能够在处理大量键值对时,显著提升存储和查找的性能。这使得 ART 成为现代高性能数据存储系统中的一种重要选择。
参考论文
性能比较
自适应基数树(ART)与传统树的性能对比
自适应基数树(Adaptive Radix Tree,ART)是一种高效的数据结构,旨在提供快速的键值存储和查找功能。为了更好地理解 ART 的优势,我们将其与传统树(如二叉搜索树、红黑树)进行性能对比。以下是插入、查找和删除操作的性能对比图:
性能比较 (单位:操作/秒) ┌──────────┬───────────┬───────────┐ │ 操作类型 │ ART树 │ 传统树 │ ├──────────┼───────────┼───────────┤ │ 插入 │ 500,000 │ 200,000 │ ├──────────┼───────────┼───────────┤ │ 查找 │ 800,000 │ 300,000 │ ├──────────┼───────────┼───────────┤ │ 删除 │ 400,000 │ 150,000 │ └──────────┴───────────┴───────────┘
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.Random; import java.util.TreeMap; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.assertEquals; public class PerformanceTest { private static final int NUM_OPERATIONS = 100000; private AdaptiveRadixTree<Integer, String> art; private TreeMap<Integer, String> treeMap; private Random random; @BeforeEach public void setUp() { art = new AdaptiveRadixTree<>(); treeMap = new TreeMap<>(); random = new Random(); } @Test public void testInsertPerformance() { long startTime = System.nanoTime(); for (int i = 0; i < NUM_OPERATIONS; i++) { int key = random.nextInt(); art.insert(Integer.toString(key).getBytes(), "value" + key); } long endTime = System.nanoTime(); long durationArt = TimeUnit.NANOSECONDS.toMillis(endTime - startTime); startTime = System.nanoTime(); for (int i = 0; i < NUM_OPERATIONS; i++) { int key = random.nextInt(); treeMap.put(key, "value" + key); } endTime = System.nanoTime(); long durationTreeMap = TimeUnit.NANOSECONDS.toMillis(endTime - startTime); System.out.println("Insert Performance:"); System.out.println("ART: " + durationArt + " ms"); System.out.println("TreeMap: " + durationTreeMap + " ms"); } @Test public void testSearchPerformance() { for (int i = 0; i < NUM_OPERATIONS; i++) { int key = random.nextInt(); art.insert(Integer.toString(key).getBytes(), "value" + key); treeMap.put(key, "value" + key); } long startTime = System.nanoTime(); for (int i = 0; i < NUM_OPERATIONS; i++) { int key = random.nextInt(); art.find(Integer.toString(key).getBytes()); } long endTime = System.nanoTime(); long durationArt = TimeUnit.NANOSECONDS.toMillis(endTime - startTime); startTime = System.nanoTime(); for (int i = 0; i < NUM_OPERATIONS; i++) { int key = random.nextInt(); treeMap.get(key); } endTime = System.nanoTime(); long durationTreeMap = TimeUnit.NANOSECONDS.toMillis(endTime - startTime); System.out.println("Search Performance:"); System.out.println("ART: " + durationArt + " ms"); System.out.println("TreeMap: " + durationTreeMap + " ms"); } @Test public void testDeletePerformance() { for (int i = 0; i < NUM_OPERATIONS; i++) { int key = random.nextInt(); art.insert(Integer.toString(key).getBytes(), "value" + key); treeMap.put(key, "value" + key); } long startTime = System.nanoTime(); for (int i = 0; i < NUM_OPERATIONS; i++) { int key = random.nextInt(); art.remove(Integer.toString(key).getBytes()); } long endTime = System.nanoTime(); long durationArt = TimeUnit.NANOSECONDS.toMillis(endTime - startTime); startTime = System.nanoTime(); for (int i = 0; i < NUM_OPERATIONS; i++) { int key = random.nextInt(); treeMap.remove(key); } endTime = System.nanoTime(); long durationTreeMap = TimeUnit.NANOSECONDS.toMillis(endTime - startTime); System.out.println("Delete Performance:"); System.out.println("ART: " + durationArt + " ms"); System.out.println("TreeMap: " + durationTreeMap + " ms"); } }
订单簿
订单簿系统需要高效地处理和管理订单,以确保执行速度和数据准确性。在设计上需要考虑的点:
-
业务需求:
- 价格优先:订单按价格优先级排序,高价买单和低价卖单优先匹配。
- 下单:支持高效的订单插入操作,确保新订单能快速加入订单簿。
- 撤单:支持高效的订单删除操作,确保用户可以快速撤销未成交订单。
- 成交:支持高效的订单匹配和成交操作,确保订单能及时撮合成交。
-
数据结构需求:
- 有序:订单簿需要维护订单的有序性,以便快速查找和匹配订单。
- 高效查找/插入:支持高效的订单查找和插入操作,确保新订单能快速加入订单簿。
- 高效查找/删除:支持高效的订单查找和删除操作,确保用户可以快速撤销未成交订单。
- 查找最大/最小值,按序遍历:支持快速查找订单簿中的最大值和最小值,并按价格顺序遍历订单簿,确保订单匹配和撮合的效率。
为了满足上述需求,订单簿系统应采用高效的数据结构,如自适应基数树(Adaptive Radix Tree, ART)、跳表(Skip List)或平衡二叉搜索树(如红黑树)。这些数据结构能够提供高效的查找、插入和删除操作,并支持按序遍历,确保订单簿系统的高性能和高可靠性。
ART树+订单簿的实验
在观摩学习完论文后,在网上搜索了一个开源实现,然后开始进行订单簿的实验:
github上开源实现:https://github.com/rohansuri/adaptive-radix-tree
这个实现么有放到maven仓库当中,所以直接拿到工程目录来使用:
代码放到这里了:https://github.com/aktiger/bitget-solution
性能测试结果:https://github.com/aktiger/bitget-solution?tab=readme-ov-file#performance
后续改进
需要结合现代处理器的特性进行特定优化。

总结:
时间比较有限,这个领域还是比较有意思的,以后有机会和时间可以进行更深入的研究。
参考:
1. https://www.oneyearago.me/2023/01/12/principle_and_optimization_of_radix_tree/
2. https://uncp.github.io/Adaptive-Radix-Tree/
3. https://b2broker.com/zh-hans/library/what-is-an-order-book-and-how-does-it-work/