发布网友 发布时间:2022-04-30 10:52
共2个回答
懂视网 时间:2022-04-30 15:13
Arena { public: Arena(); ~Arena(); // Return a pointer to a newly allocated memory block of "bytes" bytes. char* Allocate(size_t bytes); // Allocate memory with the normal alignment guarantees provided by malloc char* AllocateAligned(size_t bytes); // Returns an estimate of the total memory usage of data allocated // by the arena (including space allocated but not yet used for user // allocations). size_t MemoryUsage() const { return blocks_memory_ + blocks_.capacity() * sizeof(char*); } ...... }Arena实际上就是一个Allocator,在Heap上执行内存管理,其好处有二:
1. 提高程序性能:减少Heap调用次数,专有Arena统一分配后返回到应用层。
2. 统一内存管理:分配后无需执行dealloc,当Arena对象释放时,统一释放由其创建的所有内存。
当然,这是个功能并不完善的Allocator,但适用于MemTable的特点3.a。
class Slice { public: ...... private: const char* data_; size_t size_; // Intentionally copyable };
Slice的含义和其名称一致,代表了一个数据块,data_为数据地址,size_为数据长度。
Slice一般和Arena配合使用,其仅保持了数据信息,并未拥有数据的所有权。而数据在Arena对象的整个声明周期内有效。
Slice在LevelDB中一般用于传递Key、Value或编解码处理后的数据块。
和string相比,Slice具有的明显好处包括:避免不必要的拷贝动作、具有比string更丰富的语义(可包含任意内容)。
class MemTableIterator : public Iterator{......}
MemTableIterator为SkipList::Iterator的封装类,简单,略。
在LevelDB中,共有三种Key:
1. User Key: 用户传入的Key内容,下表中的Part2.
2. Internal Key: LevelDB内部存储时使用的Key,下表中的Part2-Part3。除User Key外,还包括了本次操作的序号及操作类型信息。
3. MemTable Key: 下表中的Part1-Part3。
Part 1 | Part 2 | Part 3 | Part 4 | Part 5 |
Key Size + 8 | Key Content | Seq Number << 8 | ValueType | Value Size | Value Content |
表1
最后一个比较重要的类InternalKeyComparator,这是Internal Key的比较器,其中的关键在于Compare方法:
int InternalKeyComparator::Compare(const Slice& akey, const Slice& bkey) const { // Order by: // increasing user key (according to user-supplied comparator) // decreasing sequence number // decreasing type (though sequence# should be enough to disambiguate) int r = user_comparator_->Compare(ExtractUserKey(akey), ExtractUserKey(bkey)); //首先调用user comparator进行键值比较 if (r == 0) { const uint64_t anum = DecodeFixed64(akey.data() + akey.size() - 8); const uint64_t bnum = DecodeFixed64(bkey.data() + bkey.size() - 8); if (anum > bnum) { //序号及类型比较比较,即操作版本号比较。较新的操作版本号较高,比较器认为较新的版本<较老的版本或者老版本>新版本 r = -1; } else if (anum < bnum) { r = +1; } } return r; }
值得注意的是序号及类型的比较,实际上由于任意操作序号的唯一性,类型比较时非必须的。这里同时进行了类型比较也是出于性能的考虑(减少了从中分离序号、类型的工作)。
序号或者说版本号采用的是降序原则,这导致的效果是,比较器认为老版本>新版本,为什么不是新版本>老版本呢?因为Snapshot机制的支持,当用户想查看Snapshot(快照)时,希望找到指定版本号之前的数据。
有了以上储备后,再来看MemTable就比较简单了,首先看插入方法Add:
void MemTable::Add(SequenceNumber s, ValueType type, const Slice& key, const Slice& value) { // Format of an entry is concatenation of: // key_size : varint32 of internal_key.size() // key bytes : char[internal_key.size()] // value_size : varint32 of value.size() // value bytes : char[value.size()] size_t key_size = key.size(); size_t val_size = value.size(); size_t internal_key_size = key_size + 8; //总长度 const size_t encoded_len = VarintLength(internal_key_size) + internal_key_size + VarintLength(val_size) + val_size; char* buf = arena_.Allocate(encoded_len); //Key char* p = EncodeVarint32(buf, internal_key_size); memcpy(p, key.data(), key_size); p += key_size; //Seq Number + Value Type EncodeFixed64(p, (s << 8) | type); p += 8; //Value p = EncodeVarint32(p, val_size); memcpy(p, value.data(), val_size); assert((p + val_size) - buf == encoded_len); table_.Insert(buf); }
以下几点备注:
1. MemTable将用户数据编码为表1的结构进行存储,其中Part1、Part4采用了Variant32的存储方式,Part3则采用Fixed64。LevelDB中何处采用Variant、何处采用Fixed的方式,规律尚未摸清,想必也和性能有关?
2. MemTable不提供删除方法,客户端的删除动作将被转换为一次ValueType为Deletion的添加动作,等MemTable需要被Compaction到磁盘时方执行真正的删除。
3. 数据编码完毕后,将数据插入到SkipList中(table_)。
再来看Get方法:
1 bool MemTable::Get(const LookupKey& key, std::string* value, Status* s) 2 { 3 Slice memkey = key.memtable_key(); 4 5 Table::Iterator iter(&table_); 6 iter.Seek(memkey.data()); 7 8 if (iter.Valid()) { 9 // entry format is: 10 // klength varint32 11 // userkey char[klength - 8] 12 // tag uint64 13 // vlength varint32 14 // value char[vlength] 15 // Check that it belongs to same user key. We do not check the 16 // sequence number since the Seek() call above should have skipped 17 // all entries with overly large sequence numbers. 18 const char* entry = iter.key(); 19 uint32_t key_length; 20 const char* key_ptr = GetVarint32Ptr(entry, entry + 5, &key_length); 21 if (comparator_.comparator.user_comparator()->Compare( 22 Slice(key_ptr, key_length - 8), key.user_key()) == 0) 23 { 24 // Correct user key 25 const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8); 26 switch (static_cast<ValueType>(tag & 0xff)) { 27 case kTypeValue: { 28 Slice v = GetLengthPrefixedSlice(key_ptr + key_length); 29 value->assign(v.data(), v.size()); 30 return true; 31 } 32 case kTypeDeletion: 33 *s = Status::NotFound(Slice()); 34 return true; 35 } 36 } 37 } 38 return false; 39 }
Line3的memtable_key即表1中Part1-Part3内容,其中Seq Number在指定了Snapshot时为Snapshot的Seq Number,否则为最后一次更新的Seq Nmuber。
Line6调用SkipList的Seek方法定位,随后校验找到的iter是否和指定的key一致,如果一致并且ValueType为kTypeValue,返回找到的数据,否则查找失败。
小结:
1. 性能是第一要义:Arena、Slice、甚至数据编码的Variant都是为此准备的。
2. Seq Number在LevelDB中扮演了重要角色,在数据查找、Snapshot中至关重要。
3. MemTable是SkipList的必要封装。
LevelDB源码之二MemTable
标签:
热心网友 时间:2022-04-30 12:21
关于内存表的使用(kbmMemTable)
关于内存表的使用说明 一、 Delphi使用内存表 1.1 Delphi创建内存表步骤: 1. 创建一个Ttable实例。 2. 设置一个DataBaseName为一个目录或是已有的数据库别名。 3. 指定TableName的值。 4.设置TableType属性指明要创建的数据库表类型。(如果此属性为ttDefault表示数据库类型与TableName指定值的扩展名对应)。 5. 调用TTable.FidldDefs对象的Add方法向数据库表中添加字段。Add有4个参数: 字段名:string。 字段类型:TfieldType。 字段大小:Word。一般只对String和Memo类型使用。 字段是否NotNull: Boolean。 6. 使用TTable.IndexDefs.Add()方法定义索引。Add有三个参数: 索引名:string; 索引字段名:string; 索引类型:TIndexOptions; 7. 调用TTable的CreateTable。这种方法适用于本地表。SQL表要用TQuery来创建。 代码如下: var MemTable: TTable; begin with MemTable.Create(Self) do begin DatabaseName := 'c:\Temp'; TableName := 'Test'; TableType := ttParadox; with FieldDefs do begin Add('Age', ftInteger, 0, True); Add('Name', ftString, 25, False); Add('Weight', ftFloat, 0, False); end; IndexDefs.Add('', 'Age', [ixPrimary, ixUnique]); CreateTable; end; end; 二、 kbmMemTable使用简述 2.1 kbmMemTable创建步骤: 1. 创建一个kbmMemTable对象实例。 2. 调用kbmMemTable.FidldDefs对象的Add方法向数据库表中添加字段。Add的方法和Delphi相同。 3. 使用kbmMemTable.IndexDefs.Add()方法定义索引。Add的方法和Delphi相同。 4. 调用kbmMemTable的CreateTable。 重要的区别:因为kbmMemTable不需要BDE的支持。所以不要指明DatabaseName, TableName和TableType三个属性。 代码如下: with kbmMemTable1 do begin with kbmMemTable1.FieldDefs do begin Clear; Add('Period', ftInteger, 0, false); Add('VALUE', ftLargeInt, 0, false); Add('BytesField', ftBytes, 20, false); Add('Color', ftInteger, 0, false); Add('Date', ftDate, 0, false); Add('Memo', ftMemo, 0, false); Add('AutoInc', ftAutoInc, 0, false); end; with kbmMemTable1.IndexDefs do begin Clear; Add('Index1', 'VALUE', []); end; CreateTable; end; 三、与Delphi创建内存表的对比 3.1主从表功能 kbmMemTable可以象其它TDataSet一样,通过设置MasterSource和MasterField来简单的完成主从表的操作。 3.2 SQL功能 没有发现kbmMemTable可以支持SQL语句的操作。它提供按字段排序和对排序字段的查找功能。 三、 kbmMemTable特点 从其它TDataSet得到数据。 代码如下: LoadFromDataSet(Table1, [mtcpoStructure, mtcpoProperties]); 这样kbmMemTable就完全得到来自一个DataSet对象中的全部数据. 保存和载入内存表中数据的功能 Delphi的TTable不提供SaveToFile功能。 kbmMemTable提供保存到文件的功能,保存的文件有两种格式: Options: TkbmMemTable.SaveFlags; 1. 二进制格式。kbmMemTable.SaveToBinaryFile('c:\test.bin', Options). kbmMemTable1.LoadFromBinaryFile('c:\test.bin') 2. .csv格式。kbmMemTable.SaveToFile('c:\test.csv', Options); kbmMemTable1.LoadFromFile('c:\test.csv') (一种Excel支持的文档格式)打开后的内容如下: @@FILE VERSION@@ 200 @@TABLEDEF START@@ Period=Integer,0,"Period","",10 VALUE=LargeInt,0,"VALUE","",15 BytesField=Bytes,20,"BytesField","",10 Color=Integer,0,"Color","",10 Date=Date,0,"Date","",10 Memo=Memo,0,"Memo","",10 AutoInc=AutoInc,0,"AutoInc","",10 CALC=String,20,"CALC","",20 @@TABLEDEF END@@ Period VALUE BytesField Color Date Memo AutoInc CALC 1 198 0 02/11/2001 This is a memo%n2001-11-2 10:19:52 1 0 1-二月 2 196 3 03/11/2001 This is a memo%n2001-11-2 10:19:52 2 1 2-三月 在文档的头部份描述了表的字段结构,在下面则是数据区域. 以下代码用ClientDataSet建立内存表,包含Field1和Field2两个字段,在Field1上建索引: with ClientDataSet1 do begin //添加Integer类型的字段Field1 with FieldDefs.AddFieldDef do begin DataType := ftInteger; Name := 'Field1'; end; //添加string类型的字段Field2 with FieldDefs.AddFieldDef do begin DataType := ftString; Size := 10; Name := 'Field2'; end; //在Field1上建索引 with IndexDefs.AddIndexDef do begin Fields := 'Field1'; Name := 'IntIndex'; end; //创建内存表 CreateDataSet; end
》》》》》》》》》》》》》》》》》
KbmMemTable是一款高效且强大的内存表控件,内存表顾名思义其最大的优势就是速度,KbmMemTable不仅完美地实现了高效的特征,同时,最新版本还支持索引、SQL语句等高级用法。其用法超简单,基本用法和ClientDataSet一致,以下是我简单的应用示例,实现了增删改查等基本功能:
Delphi <wbr>: <wbr>kbmMemTable关于内存表的使用
unit Unit1
interface
use
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Grids, DBGrids, DB, StdCtrls, kbmMemTable
type
TForm1 = class(TForm)
Button1: TButto
DataSource1: TDataSource
DBGrid1: TDBGrid
Button2: TButto
Button3: TButto
Button4: TButto
Button5: TButto
Button6: TButto
Button7: TButto
rocere Button1Click(Sender: TObject)
rocere FormCreate(Sender: TObject)
rocere Button2Click(Sender: TObject)
rocere Button4Click(Sender: TObject)
rocere Button3Click(Sender: TObject)
rocere Button5Click(Sender: TObject)
rocere Button6Click(Sender: TObject)
rocere Button7Click(Sender: TObject)
rivate
{ Private declarations }
ublic
{ Public declarations }
end
var
Form1: TForm1
kbmMemTable1 : TkbmMemTable
Id:Integer
implementatio
{$R *.dfm}
rocere TForm1.Button1Click(Sender: TObject)
egi
//建表
kbmMemTable1.FieldDefs.Clear
kbmMemTable1.FieldDefs.Add('Id', ftInteger, 0, False)
kbmMemTable1.FieldDefs.Add('Value', FtString, 20, False)
kbmMemTable1.FieldDefs.Add('Time', ftDateTime, 0, False)
kbmMemTable1.IndexDefs.Add('Index1','Id',[]);//定义索引
kbmMemTable1.CreateTable
kbmMemTable1.Active := True
end
rocere TForm1.Button2Click(Sender: TObject)
var
fld_Id:TIntegerField
fld_Value:TStringField
fld_Time:TDateTimeField
i:Integer
egi
//插入
with kbmMemTable1 do
egi
DisableControls; //切断数据感知控件
try
Open; //打开
//定义Field
fld_Id:=TIntegerField(FieldByName('Id'))
fld_Value:=TStringField(FieldByName('Value'))
fld_Time:=TDateTimeField(FieldByName('Time'))
for i := 0 to 9 do
egi
Inc(Id)
Append; //附加数据
//赋值
fld_Id.AsInteger := Id
fld_Value.AsString := 'Hello PFeng!'+inttostr(Id)
fld_Time.AsDateTime := Now
Post; //确定
end
UpdateIndexes;//更新索引
finally
EnableControls; //连接数据感知控件
end
end
end
rocere TForm1.Button3Click(Sender: TObject)
var
fld_Id:TIntegerField
fld_Value:TStringField
fld_Time:TDateTimeField
egi
//更新
with kbmMemTable1 do
egi
Ope
fld_Id:=TIntegerField(FieldByName('Id'))
fld_Value:=TStringField(FieldByName('Value'))
fld_Time:=TDateTimeField(FieldByName('Time'))
if Locate('Id',5,[]) the
egi
Edit
fld_Value.AsString := '内容被修改'
fld_Time.AsDateTime := Now
Post
end
end
end
rocere TForm1.Button4Click(Sender: TObject)
var
fld_Id:TIntegerField
fld_Value:TStringField
fld_Time:TDateTimeField
egi
//查询
with kbmMemTable1 do
egi
Ope
fld_Id:=TIntegerField(FieldByName('Id'))
fld_Value:=TStringField(FieldByName('Value'))
fld_Time:=TDateTimeField(FieldByName('Time'))
if Locate('Id',5,[]) the
ShowMessage(fld_Value.AsString)
//还可以用FindKey实现,结合索引速度更快
// kbmMemTable1.IndexFieldNames:='Id'
// if kbmMemtable1.FindKey([5]) then ...
end
end
rocere TForm1.Button5Click(Sender: TObject)
var
fld_Id:TIntegerField
fld_Value:TStringField
fld_Time:TDateTimeField
egi
//删除
with kbmMemTable1 do
egi
Ope
fld_Id:=TIntegerField(FieldByName('Id'))
fld_Value:=TStringField(FieldByName('Value'))
fld_Time:=TDateTimeField(FieldByName('Time'))
if Locate('Id',5,[]) the
egi
Delete
UpdateIndexe
end
end
end
rocere TForm1.Button6Click(Sender: TObject)
var
fld_Id:TIntegerField
fld_Value:TStringField
fld_Time:TDateTimeField
id:Integer
egi
//循环比较最小值
with kbmMemTable1 do
egi
Ope
fld_Id:=TIntegerField(FieldByName('Id'))
fld_Value:=TStringField(FieldByName('Value'))
fld_Time:=TDateTimeField(FieldByName('Time'))
DisableControl
try
First
id := fld_Id.AsInteger
while not Eof do
egi
if id > fld_Id.AsInteger the
id := fld_Id.AsInteger
Next
end
Locate('Id',id,[])
finally
EnableControl
end
end
end
rocere TForm1.Button7Click(Sender: TObject)
egi
//清空
kbmMemTable1.EmptyTable
end
rocere TForm1.FormCreate(Sender: TObject)
egi
kbmMemTable1 := TkbmMemTable.Create(Self)
DataSource1.DataSet := kbmMemTable1
end
end.
http://www.pfeng.org/archives/372
》》》》》》》》》》》》》》》》》》》》》》》》》》
前端时间一直在研究三层通讯框架,计划封装一套稳定性较好的远程数据库服务接口,前期的方案主要有以下几种: 1,改造自己写的indy+压缩xml流三层应用项目 2,基于MMZ asio远程数据库项目进一步完善 3,寻找并测其他相对成熟的三层通讯框架,知道的有:QuickBurro、RemoteAdo、Middle ADO System、dbIOCP、DataSnap、ASTA、Remobject SDK、kbmMW,接下来我将就我对以上三层框架的浅薄认识一一说明。 首先说方案1,在早些时候为了实现远程数据库与本地数据库的同步,我写过一套基于indy+压缩xml流的C/S应用,客户端的请求封装在xml中,发送到服务端,服务端在数据库中执行后,把执行结果(例如结果集)拼装成xml再压缩成流,返回到客户端,虽然当时基本上满足了需求,但项目有着很大的局限:并发性差、效率低、通讯不稳定等,一旦网络条件稍差,服务端就会报异常,想想那段维护服务端软件的日子简直就是噩梦,如果本次项目仍然采用这种思路,我需要去改进很多地方,比如数据库连接池、多线程、数据校验、底层通讯等,在有限的时间里很难去把多个方面都做稳定,权衡后,觉得自己应该站在巨人的肩膀上来实现自己的业务逻辑,于是放弃了这个方案。 方案2,ASIO(http://think-async.com/)是C++的一个强大的通讯库,据说是工业级别的,我的一个Delphi技术群(群号:15637473)的群主把ASIO编译成了一个DLL动态库,然后又结合unidac、连接池等,封装了一个简单的三层通讯框架MMZ ASIO(http://mmz-asio4delphi.googlecode.com),我在早些时候用这个框架做了几个小项目,感觉比较简单,效率也很高,但是有几个bug迟迟得不到解决(客户端不能主动断开、服务端容易报线程异常等),给群主写邮件,群主说他现在很忙,没时间解决这些问题,而以我个人的水平,又很难去从核心代码上做改动。目前有几个忠实的asio爱好者还在深入研究(群号:91684553),相信有朝一日会稳定起来,考虑到时间成本,同样放弃了这个方案。
最后,最佳的选择就是选择一款成熟的三层框架,在此基础上做二次开发,于是从网上搜了好几天时间,了解各个框架的情况,首先基于成本和稳定性的考虑放弃了国产框架: 1,QuickBurro(http://www.quickburro.com/),樵夫的作品,到目前还一直在做升级,具体价格参见2,http://www.quickburro.com/orders.html,授权方式比较宽松,免费版的用户数有*(好像是16个?不确定)。 3,RemoteAdo(http://www.remoteado.cn/index.html),了解的不多,但是就从网站上来介绍来看,功能比较局限,当然,也是收费的。 4,Middle ADO System(http://www.middle-ado.com/),厦门一家公司的产品,同样了解的不多,价格不详,功能也比较局限。 5,dbIOCP(群号:35916846),楠楠的作品,据说效率很高(IOCP不多说,网上一搜便知道),价格好像是3000/套,带源码,升级不是很频繁,网上的免费1.6版连楠楠自己都说已经很久了,我看群里很多讨论2.4版本的,楠楠同样自己说性能没法和3.x版本的比,新版的架构都变了,效率说的很神,但是个人作品,在没有很好地推广的情况下,3000刀个人感觉有点儿贵。
再来说其他的几个著名框架: 1,DataSnap,这个其实很多人都比较熟悉,它是Delphi的三层框架MIDAS的延续,李维在多次讲座和个人书籍中都强烈推荐,而且国外社区很多人都提到它,估计能力不俗,但是网上褒贬不一,个人深入了解的不够。 2,ASTA(http://www.astatech.com/procts/asta3/),也是一个老牌的三层框架,据说在200x年的时候很火,但是开发者后续升级不给力,单从网站首页上的新闻就让人很惊讶:ASTA 3.1 is here and it’s HOT with support for Delphi 2006…当然,如果你还在用D2007以下版本做开发的话,可以试试,但是我个人觉得缺乏后续的SAU(Service and Update),心理上总是不踏实。价格不详,但是网上Full Source版本的一搜一大把。 3,Remobject SDK(http://www.remobjects.com/default.aspx),其实我对这个框架的印象很好,因为之前计数群里已经基于它做了很多成熟的项目,而且在讨论过程中口碑也相当不错,而且现在for XE2的版本网上也很好找到,建议有兴趣的人可以深入研究以下。 4,KbmMW(http://www.components4programmers.com/procts/kbmmw/index.htm),说到本文的重点了,KbmMW在国内圈子里能够得到认可,很大程度上得益于窑主xalion详细的博文教程(http://www.cnblogs.com/xalion/),作者Kim Madsen对中国国情的充分考虑(对中国程序员价格上优惠25%),KbmMW在国外社区很火,国内可能很多人对kbmMemTable都不陌生,而KbmMW也是基于kbmMemTable做的封装。我对KbmMW最大的认可就是其开放的架构思路:通讯控件支持Indy(9和10)、Synapse、DxSock,数据库控件支持ADO和Unidac等、当然还有其他的加密、压缩等均支持开源的第三方控件,给开发人员了很大自由度。作者的热心也让我很感动,每次遇到问题,白天我在StackOverFlow上提问,夜里Kim就在上面认真回答。 当然还有很多其他的优秀框架(据说Dephi的三次框架解决方案是所有开发语言中最多的),比如德国的RTC(http://www.realthinclient.com/)据说也是很不错的,有兴趣的可以自己去了解下,当我了解了KbmMW之后,我觉得,KbmMW是我最好的选择。
接下来的博文中,我不再累述教程(没法超越窑主xalion的高度),更侧重资源的分享,容易犯错的地方提醒、原有Bug的勘误,自己开发经验的总结,希望能够有兴趣或有需求的人提供一些帮助。