内存计算

DolphinDB内置轻量级的内存计算引擎,非常适合性能要求高、并发较大的计算任务。DolphinDB提供了丰富的数据结构,用于内存计算。具体请参考 第2章:数据类型和形式。本节主要介绍DolphinDB各种表在内存计算中的应用。

会话

会话是一个容器。它具有唯一的ID并存储许多已经定义的对象,例如局部变量、共享变量等。创建新会话的方式有多种,如启动命令行窗口、XDB连接、 GUI连接或Web URL连接。会话中的所有变量对于其他会话是不可见的,除非使用语句share在会话之间显式共享变量。目前DolphinDB仅支持表共享。

不再使用会话时需要把它关闭,以便释放被会话占用的系统资源。这是非常重要的。如果需要关闭XDB会话,我们可以使用 close 命令或将连接变量设置为NULL;如果需要关闭GUI或Web会话,我们需要相应地关闭GUI窗口或浏览器窗口。

内存表

内存表的数据保存在内存中,写入和查询的速度很快,但是节点关闭后数据会丢失,一般用于保存临时数据。内存表分为未分区内存表和分区内存表。

table 函数创建的表都是未分区内存表。

$ sym = `C`MS`MS`MS`IBM`IBM`C`C`C$symbol
$ price= 49.6 29.46 29.52 30.02 174.97 175.23 50.76 50.32 51.29
$ qty = 2200 1900 2100 3200 6800 5400 1300 2500 8800
$ timestamp = [09:34:07,09:36:42,09:36:51,09:36:59,09:32:47,09:35:26,09:34:16,09:34:26,09:38:12]
$ t1 = table(timestamp, sym, qty, price);

未分区内存表的更多操作请参考 .

使用分区内存表进行运算能充分发挥多核CPU并行计算的优势。创建分区内存表需要指定内存分区数据库。使用 database 函数,并且指定数据库的路径为空字符串,即"". 下例创建了一个VALUE分区类型的内存数据库。

$ n=10000
$ month=take(2000.01M..2000.12M, n)
$ x=rand(1.0, n)
$ t=table(month, x)

$ db=database("", VALUE, 2000.01M..2000.12M)
$ pt = db.createPartitionedTable(t, `pt, `month)
$ pt.append!(t)
$ select * from pt;

可以使用undef函数来释放内存表。例如,释放上例创建的内存分区表:

$ undef(`pt);

DolphinDB内存计算的一大特点是,可以使用SQL对表以外的数据结构如标量、向量、集合、矩阵、字典进行操作,增加脚本语言的灵活性。下例中,股票价格保存在数据表quotes中,股票权重保存在字典weights中。使用一条SQL语句就可以直接根据数据表和字典的数据,计算股票组合的价值。

$ Symbol=take(`AAPL, 6) join take(`FB, 5)
$ Time=2019.02.27T09:45:01.000000000+[146, 278, 412, 445, 496, 789, 212, 556, 598, 712, 989]
$ Price=173.27 173.26 173.24 173.25 173.26 173.27 161.51 161.50 161.49 161.50 161.51
$ quotes=table(Symbol, Time, Price)
$ weights=dict(`AAPL`FB, 0.6 0.4)
$ ETF = select Symbol, Time, Price*weights[Symbol] as weightedPrice from quotes
$ select last(weightedPrice) from ETF pivot by Time, Symbol;

Time

AAPL

FB

2019.02.27T09:45:01.000000146

103.962

2019.02.27T09:45:01.000000212

64.604

2019.02.27T09:45:01.000000278

103.956

2019.02.27T09:45:01.000000412

103.944

2019.02.27T09:45:01.000000445

103.95

2019.02.27T09:45:01.000000496

103.956

2019.02.27T09:45:01.000000556

64.6

2019.02.27T09:45:01.000000598

64.596

2019.02.27T09:45:01.000000712

64.6

2019.02.27T09:45:01.000000789

103.962

2019.02.27T09:45:01.000000989

64.604

共享表

内存表只在当前会话可见,需要通过 share 函数共享,才能在其他会话中可见。共享表不能删除、修改记录,仅支持插入新记录和查询操作。

$ share t1 as sharedT1;

上面的例子中,把表t1共享为sharedT1。其他会话访问表sharedT1就可以访问到t1的内容。

undef函数可以删除一个共享表。

$ undef(sharedT1,SHARED)

多版本并发控制表(mvcc table)

针对内存中频繁并发读写但很少更新或删除记录的场景,DolphinDB提供了一种特殊的内存表:多版本并发控制表。多版本并发控制表通过日志记录每次操作,读写互不影响。多版本并发控制表还可以持久化到磁盘上,后续可以加载到内存中操作。

使用 mvccTable 函数创建多版本并发控制表:

$ n=5
$ syms=`IBM`C`MS`MSFT`JPM`ORCL`FB`GE
$ timestamp=09:30:00+rand(18000,n)
$ sym=rand(syms,n)
$ qty=100*(1+rand(100,n))
$ price=5.0+rand(100.0,n)
$ temp=table(timestamp,sym,qty,price)
$ t1= mvccTable(1:0,`timestamp`sym`qty`price,[TIMESTAMP,SYMBOL,INT,DOUBLE],"/home/DolphinDB/Data","t1")
$ t1.append!(temp);

上例中,多版本并发控制表t1会持久化到磁盘/home/DolphinDB/Data/t1目录。

使用 loadMvccTable 函数把多版本并发控制表加载到内存:

$ loadMvccTable("/home/DolphinDB/Data",t1);

timestamp

sym

qty

price

1970.01.01T00:00:39.091

MSFT

4500

99.808702

1970.01.01T00:00:35.293

FB

3600

26.644715

1970.01.01T00:00:36.334

MSFT

3800

66.754334

1970.01.01T00:00:40.362

ORCL

4800

15.480288

1970.01.01T00:00:35.565

MSFT

1700

23.107408

磁盘表

磁盘表直接利用本地文件系统存储数据,数据写入和查询速度稍慢于内存表。磁盘表的优点是即使节点重启,数据也不会丢失。磁盘表分为未分区磁盘表和分区磁盘表,它们都依赖于磁盘数据库。磁盘数据库的路径为本地文件系统的路径,如"/home/DolphinDB/db1"。每个数据库对应一个路径,不能共享一个路径。

创建未分区的磁盘数据库时只需要指定数据库路径。我们使用 saveTable 函数把未分区内存表保存到磁盘数据库中。下面的例子把表t保存为未分区磁盘表pt。

$ n=10000
$ month=take(2000.01M..2000.12M, n)
$ x=rand(1.0, n)
$ t=table(month, x)

$ db=database("/home/DolphinDB/db1")
$ saveTable(db,t,`pt);

我们可以使用 loadTable 函数把磁盘未分区表加载到内存中。

$ tmp=loadTable("/home/DolphinDB/db1",`pt)
$ typestr(tmp);

$ IN-MEMORY TABLE

上面的例子中,tmp是一个内存表。后续对tmp的操作不会同步到磁盘表pt中。例如,我们往表tmp中追加数据,但是磁盘表pt中不会包含新的数据

$ t1=table(take(2001.01M..2001.12M,n) as month,rand(1.0,n) as x)
$ tmp.append!(t1);

$ select count(*) from tmp;

count

20000

$ select count(*) from loadTable("/home/DolphinDB/db1",`pt);

count

10000

需要再次执行saveTable函数把pt保存到数据库中。

$ saveTable(db,tmp,`pt)
$ select count(*) from loadTable("/home/llin/hzy/dolphindb/db1",`pt);

count

20000

创建分区磁盘表时需要指定分区类型和分区方案。例如,下例创建了VALUE类型的磁盘分区数据库。

$ n=10000
$ month=take(2000.01M..2000.12M, n)
$ x=rand(1.0, n)
$ t=table(month, x)

$ db=database("/home/DolphinDB/db2", VALUE, 2000.01M..2000.12M)
$ pt = db.createPartitionedTable(t, `pt, `month)
$ pt.append!(t);

loadTable函数可以把分区磁盘表的元数据加载到内存中。

$ tmp=loadTable("/home/DolphinDB/db2",`pt)
$ typestr(tmp);

$ SEGMENTED TABLE

上例中,tmp是分区表的句柄,后续对tmp进行操作相当于直接对磁盘表pt进行操作。例如,往tmp中追加数据。

$ tmp.append!(t)
$ select count(*) from loadTable("/home/DolphinDB/db2",`pt);

count

10000

dropTable 函数可以删除磁盘表,dropDatabase 函数可以删除磁盘数据库。

$ db=database("/home/DolphinDB/db1")
$ dropTable(db,`pt);

$ dropDatabase("/home/DolphinDB/db1");