精华内容
下载资源
问答
  • DDR内存时序设置详解

    2011-09-28 01:09:08
    DDR内存时序设置详解 DDR内存时序设置详解
  • DolphinDB是一款支持多用户多任务并发操作的高性能分布式时序数据库软件(distributed time-series database)。针对大数据的高效的内存管理是其性能优异的原因之一。本教程涉及的内存管理包括以下方面: 变量的内存...

    DolphinDB是一款支持多用户多任务并发操作的高性能分布式时序数据库软件(distributed time-series database)。针对大数据的高效的内存管理是其性能优异的原因之一。本教程涉及的内存管理包括以下方面:

    • 变量的内存管理:为用户提供与回收编程环境所需内存。
    • 分布式表的缓存管理:多个session共享分区表数据,以提高内存使用率。
    • 流数据缓存:流数据发送节点提供持久化和发送队列缓存,订阅节点提供接收数据队列缓存。
    • DFS数据库写入缓存:写入DFS的数据先写到WAL和缓存,通过批量写入提升吞吐量。


    1. 内存管理机制

    DolphinDB向操作系统申请内存块,自行进行管理。当申请的内存块闲置时,系统会定期检查并释放。目前vector和table以及所有字符串的内存分配都已经纳入DolphinDB的内存管理系统。

    通过参数maxMemSize设定节点的最大内存使用量:该参数制定节点的最大可使用内存。如果设置太小,会严重限制集群的性能,如果设置太大,例如超过物理内存,可能会触发操作系统强制关闭进程。若机器内存为16GB,并且只部署1个节点,建议将该参数设置为12GB左右。

    以512MB为单位向操作系统申请内存块:当用户查询操作或者编程换进所需要内存时,DolphinDB会以512MB为单位向操作系统申请内存。如果操作系统无法提供大块的连续内存,则会尝试256MB,128MB等更小的内存块。

    系统充分利用可用内存缓存数据库数据:当节点的内存使用总量小于maxMemSize时,DolphinDB会尽可能多的缓存数据库分区数据,以便提升用户下次访问该数据块的速度。当内存不足时,系统自动会剔除部分缓存。

    每隔30秒扫描一次,空闲的内存块还给操作系统:当用户使用释放内存中变量,或者使用函数clearAllCache释放缓存时,如果内存块完全空闲,则会整体还给操作系统,如果仍有小部分内存在使用,比如512MB的内存块中仍有10MB在使用,则不会归还操作系统。


    2. 变量的内存管理

    2.1 创建变量

    在DolphinDB节点上,先创建一个用户user1,然后登陆。创建一个vector,含有1亿个INT类型元素,约400MB。

    示例1. 创建vector变量

     login("admin","123456")  //创建用户需要登陆admin
    createUser("user1","123456")
    login("user1","123456")
    v = 1..100000000
    sum(mem().blockSize - mem().freeSize) //输出内存占用结果

    结果为: 402,865,056,内存占用400MB左右,符合预期。

    再创建一个table,1000万行,5列,每列4字节,约200MB。

    示例2. 创建table变量

    n = 10000000
    t = table(n:n,["tag1","tag2","tag3","tag4","tag5"],[INT,INT,INT,INT,INT])
    (mem().blockSize - mem().freeSize).sum()

    结果为:612,530,448,约600MB,符合预期。


    2.2 释放变量

    可通过undef函数,释放变量的内存。

    示例3. 使用undef函数或者赋值为NULL释放变量

    undef(`v)

    或者

    v = NULL

    除了手动释放变量,当session关闭时,比如关闭GUI和其他API连接,都会触发对该session的所有内存进行回收。当通过web notebook连接时,10分钟内无操作,系统会关闭session,自动回收内存。


    3. 分布式表的缓存管理

    DolphinDB对分布式表是以分区为单位管理的。分布式表的缓存是全局共享的,不同的session或读事务在大部分情况下,会看到同一份数据copy(版本可能会有所不同),这样极大的节省了内存的使用。

    历史数据库都是以分布式表的形式存在数据库中,用户平时查询操作也往往直接与分布式表交互。分布式表的内存管理有如下特点:

    • 内存以分区列为单位进行管理。
    • 数据只加载到所在的节点,不会在节点间转移。
    • 多个用户访问相同分区时,使用同一份缓存。
    • 内存使用不超过maxMemSize情况下,尽量多缓存数据。
    • 缓存数据达到maxMemSize时,系统自动回收。

    以下多个示例是基于以下集群:部署于2个节点,采用单副本模式。按天分30个区,每个分区1000万行,11列(1列DATE类型,1列INT类型,9列LONG类型),所以每个分区的每列(LONG类型)数据量为1000万行 * 8字节/列 = 80M,每个分区共1000万行 * 80字节/行 = 800M,整个表共3亿行,大小为24GB。

    函数clearAllCache()可清空已经缓存的数据,下面的每次测试前,先用该函数清空节点上的所有缓存。


    3.1 内存以分区列为单位进行管理

    DolphinDB采用列式存储,当用户对分布式表的数据进行查询时,加载数据的原则是,只把用户所要求的分区和列加载到内存中。

    示例4. 计算分区2019.01.01最大的tag1的值。该分区储存在node1上,可以在controller上通过函数getClusterChunksStatus()查看分区分布情况,而且由上面可知,每列约80MB。在node1上执行如下代码,并查看内存占用。

    select max(tag1) from loadTable(dbName,tableName) where day = 2019.01.01
    sum(mem().blockSize - mem().freeSize) 

    输出结果为84,267,136。我们只查询1个分区的一列数据,所以把该列数据全部加载到内存,其他的列不加载。

    示例5. 在node1 上查询 2019.01.01的前100条数据,并观察内存占用。

    select top 100 * from loadTable(dbName,tableName) where day = 2019.01.01
    sum(mem().blockSize - mem().freeSize)

    输出结果为839,255,392。虽然我们只取100条数据,但是DolphinDB加载数据的最小单位是分区列,所以需要加载每个列的全部数据,也就是整个分区的全部数据,约800MB。

    注意: 合理分区以避免"out of memory":DolphinDB是以分区为单位管理内存,因此内存的使用量跟分区关系密切。假如用户分区不均匀,导致某个分区数据量超大,甚至机器的全部内存都不足以容纳整个分区,那么当涉及到该分区的查询计算时,系统会抛出"out of memory"的异常。一般原则,如果用户设置maxMemSize=8,则每个分区常用的查询列之和为100-200MB为宜。如果表有10列常用查询字段,每列8字段,则每个分区约100-200万行。


    3.2 数据只加载到所在的节点

    在数据量大的情况下,节点间转移数据是非常耗时的操作。DolphinDB的数据是分布式存储的,当执行计算任务时,把任务发送到数据所在的节点,而不是把数据转移到计算所在的节点,这样大大降低数据在节点间的转移,提升计算效率。

    示例6. 在node1上计算两个分区中tag1的最大值。其中分区2019.01.02数组存储在node1上,分区2019.01.03数据存储在node2上。

    select max(tag1) from loadTable(dbName,tableName) where day in [2019.01.02,2019.01.03]
    sum(mem().blockSize - mem().freeSize) 

    输出结果为84,284,096。在node2上用查看内存占用结果为84,250,624。每个节点存储的数据都为80M左右,也就是node1上存储了分区2019.01.02的数据,node2上存储了2019.01.03的数据。

    示例7. 在node1上查询分区2019.01.02和2019.01.03的所有数据,我们预期node1加载2019.01.02数据,node2加载2019.01.03的数据,都是800M左右,执行如下代码并观察内存。

    select top 100 * from loadTable(dbName,tableName) where day in [2019.01.02,2019.01.03]
    sum(mem().blockSize - mem().freeSize)

    node1上输出结果为839,279,968。node2上输出结果为839,246,496。结果符合预期。

    注意: 谨慎使用没有过滤条件的"select *",因为这会将所有数据载入内存。在列数很多的时候尤其要注意该点,建议仅加载需要的列。若使用没有过滤条件的"select top 10 *",会将第一个分区的所有数据载入内存。


    3.3 多个用户访问相同分区时,使用同一份缓存

    DolphinDB支持海量数据的并发查询。为了高效利用内存,对相同分区的数据,内存中只保留同一份副本。

    示例8. 打开两个GUI,分别连接node1和node2,查询分区2019.01.01的数据,该分区的数据存储在node1上。

    select * from loadTable(dbName,tableName) where date = 2019.01.01
    sum(mem().blockSize - mem().freeSize)

    上面的代码不管执行几次,node1上内存显示一直是839,101,024,而node2上无内存占用。因为分区数据只存储在node1上,所以node1会加载所有数据,而node2不占用任何内存。


    3.4 节点内存占用情况与缓存数据的关系

    3.4.1 节点内存使用不超过maxMemSize情况下,尽量多缓存数据

    通常情况下,最近访问的数据往往更容易再次被访问,因此DolphinDB在内存允许的情况下(内存占用不超过用户设置的maxMemSize),尽量多缓存数据,来提升后续数据的访问效率。

    示例9. 数据节点设置的maxMemSize=8。连续加载9个分区,每个分区约800M,总内存占用约7.2GB,观察内存的变化趋势。

    days = chunksOfEightDays();
    for(d in days){
        select * from loadTable(dbName,tableName) where  = day
        sum(mem().blockSize - mem().freeSize)
    }

    内存随着加载分区数的增加变化规律如下图所示:

    当遍历每个分区数据时,在内存使用量不超过maxMemSize的情况下,分区数据会全部缓存到内存中,以在用户下次访问时,直接从内存中提供数据,而不需要再次从磁盘加载。

     

    3.4.2 节点内存使用达到maxMemSize时,系统自动回收


    如果DolphinDB server使用的内存,没有超过用户设置的maxMemSize,则不会回收内存。当总的内存使用达到maxMemSize时,DolphinDB 会采用LRU的内存回收策略, 来腾出足够的内存给用户。

    示例10. 上面用例只加载了8天的数据,此时我们继续共遍历15天数据,查看缓存达到maxMemSize时,内存的占用情况。如下图所示:

    如上图所示,当缓存的数据超过maxMemSize时,系统自动回收内存,总的内存使用量仍然小于用户设置的最大内存量8GB。

    示例11. 当缓存数据接近用户设置的maxMemSize时,继续申请Session变量的内存空间,查看系统内存占用。此时先查看系统的内存使用:

    sum(mem().blockSize - mem().freeSize)

    输出结果为7,550,138,448。内存占用超过7GB,而用户设置的最大内存使用量为8GB,此时我们继续申请4GB空间。

    v = 1..1000000000
    sum(mem().blockSize - mem().freeSize)

    输出结果为8,196,073,856。约为8GB,也就是如果用户定义变量,也会触发缓存数据的内存回收,以保证有足够的内存提供给用户使用。


    4. 流数据消息缓存队列

    当数据进入流数据系统时,首先写入流表,然后写入持久化队列和发送队列(假设用户设置为异步持久化),持久化队列异步写入磁盘,将发送队列发送到订阅端。

    当订阅端收到数据后,先放入接受队列,然后用户定义的handler从接收队列中取数据并处理。如果handler处理缓慢,会导致接收队列有数据堆积,占用内存。如下图所示:

    流数据内存相关的配置选项:

    • maxPersistenceQueueDepth: 流表持久化队列的最大消息数。对于异步持久化的发布流表,先将数据放到持久化队列中,再异步持久化到磁盘上。该选项默认设置为1000万。在磁盘写入成为瓶颈时,队列会堆积数据。
    • maxPubQueueDepthPerSite: 最大消息发布队列深度。针对某个订阅节点,发布节点建立一个消息发布队列,该队列中的消息发送到订阅端。默认值为1000万,当网络出现拥塞时,该发送队列会堆积数据。
    • maxSubQueueDepth: 订阅节点上最大的每个订阅线程最大的可接收消息的队列深度。订阅的消息,会先放入订阅消息队列。默认设置为1000万,当handler处理速度较慢,不能及时处理订阅到的消息时,该队列会有数据堆积。
    • 流表的capacity:在函数enableTablePersistence()中第四个参数指定,该值表示流表中保存在内存中的最大行数,达到该值时,从内存中删除一半数据。当流数据节点中,流表比较多时,要整体合理设置该值,防止内存不足。

    运行过程,可以通过函数getStreamingStat()来查看流表的大小以及各个队列的深度。


    5. 为写入DFS数据库提供缓存

    DolphinDB为了提高读写的吞吐量和降低读写的延迟,采用先写入WAL和缓存的通用做法,等累积到一定数量时,批量写入。这样减少和磁盘文件的交互次数,提升写入性能,可提升写入速度30%以上。因此,也需要一定的内存空间来临时缓存这些数据,如下图所示:

    当事务t1,t2,t3都完成时,将三个事务的数据一次性写入到DFS的数据库磁盘上。Cache Engine空间一般推荐为maxMemSize的1/8~1/4,可根据最大内存和写入数据量适当调整。CacheEngine的大小可以通过配置参数chunkCacheEngineMemSize来配置。

    • chunkCacheEngineMemSize:指定cache engine的容量。cache engine开启后,写入数据时,系统会先把数据写入缓存,当缓存中的数据量达到chunkCacheEngineMemSize的30%时,才会写入磁盘。


    6. 高效使用内存

    在企业的生产环境中,DolphinDB往往作为流数据中心以及历史数据仓库,为业务人员提供数据查询和计算。当用户较多时,不当的使用容易造成Server端内存耗尽,抛出"out of memory" 异常。可遵循以下建议,尽量避免内存的不合理使用。

    • 合理均匀分区:DolphinDB是以分区为单位加载数据,因此,分区大小对内存影响巨大。合理均匀的分区,不管对内存使用还是对性能而言,都有积极的作用。因此,在创建数据库的时候,根据数据规模,合理规划分区大小。每个分区的常用字段数据量约100MB左右为宜。
    • 及时释放数据量较大的变量:若用户创建数据量较大的变量,例如v = 1..10000000,或者将含有大量数据的查询结果赋值给一个变量t = select * from t where date = 2010.01.01,v和t将会在用户的session占用大量的内存。如果不及时释放,当其他用户申请内存时,就有可能因为内存不足而抛出异常。
    • 只查询需要的列:避免使用select *,如果用select *会把该分区所有列加载到内存。实际中,往往只需要几列。因此为避免内存浪费,尽量明确写出所有查询的列,而不是用*代替。
    • 数据查询尽可能使用分区过滤条件:DolphinDB按照分区进行数据检索,如果不加分区过滤条件,则会全部扫描所有数据,数据量大时,内存很快被耗尽。有多个过滤条件的话,要优先写分区的过滤条件。
    • 尽快释放不再需要的变量或者session:根据以上分析可知,用户的私有变量在创建的session里面保存。session关闭的时候,会回收这些内存。因此,尽早使用undef函数或者关闭session来释放内存。
    • 合理配置流数据的缓存区:一般情况下流数据的容量(capacity)会直接影响发布节点的内存占用。比如,capacity设置1000万条,那么流数据表在超过1000万条时,会回收约一半的内存占用,也就是内存中会保留500万条左右。因此,应根据发布节点的最大内存,合理设计流表的capacity。尤其是在多张发布表的情况,更需要谨慎设计。


    7. 内存监控及常见问题


    7.1 内存监控


    7.1.1 controller上监控集群中节点内存占用

    在controller上提供函数getClusterPerf()函数,显示集群中各个节点的内存占用情况。包括:

    MemAlloc:节点上分配的总内存,近似于向操作系统申请的内存总和。

    MemUsed:节点已经使用的内存。该内存包括变量、分布式表缓存以及各种缓存队列等。

    MemLimit:节点可使用的最大内存限制,即用户配置的maxMemSize。

     

    7.1.2 mem()函数监控某个节点内存占用

    mem()函数可以显示整个节点的内存分配和占用情况。该函数输出4列,其中列blockSize表示分配的内存块大小,freeSize表示剩余的内存块大小,通过sum(mem().blockSize - mem().freeSize) 得到节点所使用的总的内存大小。


    7.1.3 监控节点上不同session的内存占用

    可通过函数getSessionMemoryStat()查看节点上每个session占用的内存量,该内存只包含session内定义的变量。当节点上内存占用太高时,可以通过该函数排查哪个用户使用了大量的内存。


    7.1.4 查看某个对象占用的内存大小

    通过函数memSize来查看某个对象占用内存的具体大小,单位为字节。比如:

    v=1..1000000
    memSize(v)

    输出:4000000。


    7.2 常见问题


    7.2.1 监控显示节点内存占用太高

    通过上面的分析可知,DolphinDB会在内存允许的情况下,会尽可能多的缓存数据。因此,如果只显示节点内存占用太高,接近maxMemSize,而没有其他内存相关的错误,那么这种情况是正常的。 如果出现"out of memory"等类似的错误,首先可以通过函数getSessionMemoryStat()查看各个session占用的内存大小,其次通过函数clearAllCache()来手动释放节点的缓存数据。

    7.2.2 MemAlloc显示值跟操作系统实际显示值有差异

    DolphinDB是C++程序,本身需要一些基础的数据结构和内存开销,MemAlloc显示内存不包括这些内存,如果两者显示相差不大,几百MB以内,都属于正常现象。


    7.2.3 查询时,报告"out of memory"

    该异常往往是由于query所需的内存大于系统可提供的内存导致的。可能由以下原因导致:

    • 查询没有加分区过滤条件或者条件太宽,导致单个query涉及的数据量太大。
    • 分区不均匀。可能某个分区过大,该分区的数据超过节点配置的最大内存。
    • 某个session持有大的变量,导致节点可用的内存很小。

    7.2.4 查询时,DolphinDB进程退出,没有coredump产生

    这种情况往往是由于给节点分配的内存超过系统物理内存的限制,操作系统把DolphinDB强制退出。Linux上可以通过操作系统的日志查看原因。


    7.2.5 执行clearAllCache()函数后,MemUsed没有明显降低

    可以通过getSessionMemoryStst()查看各个session占用的内存大小。可能是由于某个session持有占用大量内存的变量不释放,导致该部分内存一直不能回收。

    展开全文
  • 内存表是DolphinDB数据库的重要组成部分。内存表不仅可以直接用于存储数据,实现高速数据读写,而且可以缓存计算引擎的中间结果,加速计算过程。本教程主要介绍DolphinDB内存表的分类、使用场景以及各种内存表在数据...

    内存表是DolphinDB数据库的重要组成部分。内存表不仅可以直接用于存储数据,实现高速数据读写,而且可以缓存计算引擎的中间结果,加速计算过程。本教程主要介绍DolphinDB内存表的分类、使用场景以及各种内存表在数据操作以及表结构(schema)操作上的异同。


    1. 内存表类别

    根据不同的使用场景以及功能特点,DolphinDB内存表可以分为以下四种:

    • 常规内存表
    • 键值内存表
    • 流数据表
    • MVCC内存表


    1.1 常规内存表

    常规内存表是DolphinDB中最基础的表结构,支持增删改查等操作。SQL查询返回的结果通常存储在常规内存表中,等待进一步处理。

    • 创建

    使用table函数可创建常规内存表。table函数有两种用法:第一种用法是根据指定的schema(字段类型和字段名称)以及表容量(capacity)和初始行数(size)来生成;第二种用法是通过已有数据(矩阵,表,数组和元组)来生成一个表。

    使用第一种方法的好处是可以预先为表分配内存。当表中的记录数超过容量时,系统会自动扩充表的容量。扩充时系统首先会分配更大的内存空间(增加20%到100%不等),然后复制旧表到新的表,最后释放原来的内存。对于规模较大的表,扩容的成本会比较高。因此,如果我们可以事先预计表的行数,建议创建内存表时预先分配一个合理的容量。如果表的初始行数为0,系统会生成空表。如果初始行数不为0,系统会生成一个指定行数的表,表中各列的值都为默认值。例如:

    //创建一个空的常规内存表
    t=table(100:0,`sym`id`val,[SYMBOL,INT,INT])
    
    //创建一个10行的常规内存表
    t=table(100:10,`sym`id`val,[SYMBOL,INT,INT])
    select * from t
    
    sym id val
    --- -- ---
        0  0  
        0  0  
        0  0  
        0  0  
        0  0  
        0  0  
        0  0  
        0  0  
        0  0  
        0  0  

    table函数也允许通过已有的数据来创建一个常规内存表。下例是通过多个数组来创建。

    sym=`A`B`C`D`E
    id=5 4 3 2 1
    val=52 64 25 48 71
    t=table(sym,id,val)
    • 应用

    常规内存表是DolphinDB中应用最频繁的数据结构之一,仅次于数组。SQL语句的查询结果,分布式查询的中间结果都存储在常规内存表中。当系统内存不足时,该表并不会自动将数据溢出到磁盘,而是Out Of Memory异常。因此我们进行各种查询和计算时,要注意中间结果和最终结果的size。当某些中间结果不再需要时,请及时释放。关于常规内存表增删改查的各种用法,可以参考另一份教程内存分区表加载和操作


    1.2 键值内存表

    键值内存表是DolphinDB中支持主键的内存表。通过指定表中的一个或多个字段作为主键,可以唯一确定表中的记录。键值内存表支持增删改查等操作,但是主键值不允许更新。键值内存表通过哈希表来记录每一个键值对应的行号,因此对于基于键值的查找和更新具有非常高的效率。

    • 创建

    使用keyedTable函数可创建键值内存表。该函数与table函数非常类似,唯一不同之处是增加了一个参数指明键值列的名称。

    //创建空的键值内存表,主键由sym和id字段组成
    t=keyedTable(`sym`id,1:0,`sym`id`val,[SYMBOL,INT,INT])
    
    //使用向量创建键值内存表,主键由sym和id字段组成
    sym=`A`B`C`D`E
    id=5 4 3 2 1
    val=52 64 25 48 71
    t=keyedTable(`sym`id,sym,id,val)
    注意:指定容量和初始大小创建键值内存表时,初始大小必须为0。

    我们也可以通过keyedTable函数将常规内存表转换为键值内存表。例如:

    sym=`A`B`C`D`E
    id=5 4 3 2 1
    val=52 64 25 48 71
    tmp=table(sym, id, val)
    t=keyedTable(`sym`id, tmp)
    • 数据插入和更新的特点

    往键值内存表中添加新纪录时,系统会自动检查新记录的主键值。如果新记录中的主键值不存在于表中,那么往表中添加新的记录;如果新记录的主键值与已有记录的主键值重复时,会更新表中该主键值对应的记录。请看下面的例子。

    首先,往空的键值内存表中插入新记录,新记录中的主键值为AAPL, IBM和GOOG。

    t=keyedTable(`sym,1:0,`sym`datetime`price`qty,[SYMBOL,DATETIME,DOUBLE,DOUBLE]);
    insert into t values(`APPL`IBM`GOOG,2018.06.08T12:30:00 2018.06.08T12:30:00 2018.06.08T12:30:00,50.3 45.6 58.0,5200 4800 7800);
    t;
    
    sym  datetime            price qty 
    ---- ------------------- ----- ----
    APPL 2018.06.08T12:30:00 50.3  5200
    IBM  2018.06.08T12:30:00 45.6  4800
    GOOG 2018.06.08T12:30:00 58    7800

    再次往表中插入一批主键值为AAPL, IBM和GOOG的新记录。

    insert into t values(`APPL`IBM`GOOG,2018.06.08T12:30:01 2018.06.08T12:30:01 2018.06.08T12:30:01,65.8 45.2 78.6,5800 8700 4600);
    t;
    
    sym  datetime            price qty 
    ---- ------------------- ----- ----
    APPL 2018.06.08T12:30:01 65.8  5800
    IBM  2018.06.08T12:30:01 45.2  8700
    GOOG 2018.06.08T12:30:01 78.6  4600

    可以看到,表中记录条数没有增加,但是主键对应的记录已经更新。

    继续往表中插入一批新记录,新记录本身包含了重复的主键值MSFT。

    可以看到,表中有且仅有一条主键值为MSFT的记录。

    • 应用场景

    (1)键值表对单行的更新和查询有非常高的效率,是数据缓存的理想选择。与redis相比,DolphinDB中的键值内存表兼容SQL的所有操作,可以完成根据键值更新和查询以外的更为复杂的计算。

    (2)作为时间序列聚合引擎的输出表,实时更新输出表的结果。具体请参考教程使用DolphinDB计算K线

     


    1.3 流数据表

    流数据表顾名思义是为流数据设计的内存表,是流数据发布和订阅的媒介。流数据表具有天然的流表对偶性(Stream Table Duality),发布一条消息等价于往流数据表中插入一条记录,订阅消息等价于将流数据表中新到达的数据推向客户端应用。对流数据的查询和计算都可以通过SQL语句来完成。

    • 创建

    使用streamTable函数可创建流数据表。streamTable的用法和table函数完全相同。

    //创建空的流数据表
    t=streamTable(1:0,`sym`id`val,[SYMBOL,INT,INT])
    
    //使用向量创建流数据表
    sym=`A`B`C`D`E
    id=5 4 3 2 1
    val=52 64 25 48 71
    t=streamTable(sym,id,val)

    我们也可以使用streamTable函数将常规内存表转换为流数据表。例如:

    sym=`A`B`C`D`E
    id=5 4 3 2 1
    val=52 64 25 48 71
    tmp=table(sym, id, val)
    t=streamTable(tmp)

    流数据表也支持创建单个键值列,可以通过函数keyedStreamTable来创建。但与keyed table的设计目的不同,keyedstreamtable的目的是为了在高可用场景(多个发布端同时写入)下,避免重复消息。通常key就是消息的ID。

    • 数据操作特点

    由于流数据具有一旦生成就不会发生变化的特点,因此流数据表不支持更新和删除记录,只支持查询和添加记录。流数据通常具有连续性,而内存是有限的。为解决这个矛盾,流数据表引入了持久化机制,在内存中保留最新的一部分数据,更旧的数据持久化在磁盘上。当用户订阅旧的数据时,直接从磁盘上读取。启用持久化,使用函数enableTableShareAndPersistence,具体参考流数据教程

    • 应用场景

    共享的流数据表在流计算中发布数据。订阅端通过subscribeTable函数来订阅和消费流数据。

     

    1.4 MVCC内存表

    MVCC内存表存储了多个版本的数据,当多个用户同时对MVCC内存表进行读写操作时,互不阻塞。MVCC内存表的数据隔离采用了快照隔离模型,用户读取到的是在他读之前就已经存在的数据,即使这些数据在读取的过程中被修改或删除了,也对之前正在读的用户没有影响。这种多版本的方式能够支持用户对内存表的并发访问。需要说明的是,当前的MVCC内存表实现比较简单,更新和删除数据时锁定整个表,并使用copy-on-write技术复制一份数据,因此对数据删除和更新操作的效率不高。在后续的版本中,我们将实现行级的MVCC内存表。

    • 创建

    使用mvccTable函数创建MVCC内存表。例如:

    //创建空的流数据表
    t=mvccTable(1:0,`sym`id`val,[SYMBOL,INT,INT])
    
    //使用向量创建流数据表
    sym=`A`B`C`D`E
    id=5 4 3 2 1
    val=52 64 25 48 71
    t=mvccTable(sym,id,val)

    我们可以将MVCC内存表的数据持久化到磁盘,只需创建时指定持久化的目录和表名即可。例如,

    t=mvccTable(1:0,`sym`id`val,[SYMBOL,INT,INT],"/home/user1/DolphinDB/mvcc","test")

    系统重启后,我们可以通过loadMvccTable函数将磁盘中的数据加载到内存中。

    loadMvccTable("/home/user1/DolphinDB/mvcc","test")

    我们也可以使用mvccTable函数将常规内存表转换为MVCC内存表。

    sym=`A`B`C`D`E
    id=5 4 3 2 1
    val=52 64 25 48 71
    tmp=table(sym, id, val)
    t=mvccTable(tmp)
    • 应用场景

    当前的MVCC内存表适用于读多写少,并有持久化需要的场景。譬如动态的配置系统,需要持久化配置项,配置项的改动不频繁,已新增和查询操作为主,非常适合MVCC表。

     

    2. 共享内存表

    DolphinDB中的内存表默认只在创建内存表的会话中使用,不支持多用户多会话的并发操作,当然对别的会话也不可见。如果希望创建的内存表能被别的用户使用,保证多用户并发操作的安全,必须共享内存表。4种类型的内存表均可共享。在DolphinDB中,我们使用share命令将内存表共享。

    t=table(1..10 as id,rand(100,10) as val)
    share t as st
    //或者share(t,`st)

    上面的代码将表t共享为表st。

    使用undef函数可以删除共享表。

    undef(`st,SHARED)

    2.1 保证对所有会话可见

    内存表仅在当前会话可见,在其他会话中不可见。共享之后,其他会话可以通过访问共享变量来访问内存表。例如,我们在当前会话中把表t共享为表st。

    t=table(1..10 as id,rand(100,10) as val)
    share t as st

    我们可以在其他会话中访问变量st。例如,往共享表st插入一条数据。

    insert into st values(11,200)
    select * from st
    
    id val
    -- ---
    1  1  
    2  53 
    3  13 
    4  40 
    5  61 
    6  92 
    7  36 
    8  33 
    9  46 
    10 26 
    11 200

    切换到原来的会话,我们可以发现,表t中也增加了一条记录。

    select * from t
    
    id val
    -- ---
    1  1  
    2  53 
    3  13 
    4  40 
    5  61 
    6  92 
    7  36 
    8  33 
    9  46 
    10 26 
    11 200

     

    2.2 保证线程安全

    在多线程的情况下,内存表中的数据很容易被破坏。共享则提供了一种保护机制,能够保证数据安全,但同时也会影响系统的性能。

    常规内存表、流数据表和MVCC内存表都支持多版本模型,允许多读一写。具体说,读写互不阻塞,写的时候可以读,读的时候可以写。读数据时不上锁,允许多个线程同时读取数据,读数据时采用快照隔离(snapshot isolation)。写数据时必须加锁,同时只允许一个线程修改内存表。写操作包括添加,删除或更新。添加记录一律在内存表的末尾追加,无论内存使用还是CPU使用均非常高效。常规内存表和MVCC内存表支持更新和删除,且采用了copy-on-write技术,也就是先复制一份数据(构成一个新的版本),然后在新版本上进行删除和修改。由此可见删除和更新操作无论内存和CPU消耗都比较高。当删除和更新操作很频繁,读操作又比较耗时(不能快速释放旧的版本),容易导致OOM异常。

    键值内存表写入时需维护内部索引,读取时也需要根据索引获取数据。因此键值内存表共享采用了不同的方法,无论读写都必须加锁。写线程和读线程,多个写线程之间,多个读线程之间都是互斥的。对键值内存表尽量避免耗时的查询或计算,否则会使其它线程长时间处于等待状态。

     

    3. 分区内存表

    当内存表数据量较大时,我们可以对内存表进行分区。分区后一个大表有多个子表(tablet)构成,大表不使用全局锁,锁由每个子表独立管理,这样可以大大增加读写并发能力。DolphinDB支持对内存表进行值分区、范围分区、哈希分区和列表分区,不支持组合分区。在DolphinDB中,我们使用函数createPartitionedTable创建内存分区表。

    • 创建分区常规内存表
    t=table(1:0,`id`val,[INT,INT]) 
    db=database("",RANGE,0 101 201 301) 
    pt=db.createPartitionedTable(t,`pt,`id)
    • 创建分区键值内存表
    kt=keyedTable(1:0,`id`val,[INT,INT]) 
    db=database("",RANGE,0 101 201 301) 
    pkt=db.createPartitionedTable(t,`pkt,`id)
    • 创建分区流数据表

    创建分区流数据表时,需要传入多个流数据表作为模板,每个流数据表对应一个分区。写入数据时,直接往这些流表中写入;而查询数据时,需要查询分区表。

    st1=streamTable(1:0,`id`val,[INT,INT]) 
    st2=streamTable(1:0,`id`val,[INT,INT]) 
    st3=streamTable(1:0,`id`val,[INT,INT]) 
    db=database("",RANGE,1 101 201 301) pst=db.createPartitionedTable([st1,st2,st3],`pst,`id)  
    st1.append!(table(1..100 as id,rand(100,100) as val)) 
    st2.append!(table(101..200 as id,rand(100,100) as val)) 
    st3.append!(table(201..300 as id,rand(100,100) as val))  
    select * from pst
    • 创建分区MVCC内存表

    与创建分区流数据表一样,创建分区MVCC内存表,需要传入多个MVCC内存表作为模板。每个表对应一个分区。写入数据时,直接往这些表中写入;而查询数据时,需要查询分区表。

    mt1=mvccTable(1:0,`id`val,[INT,INT])
    mt2=mvccTable(1:0,`id`val,[INT,INT])
    mt3=mvccTable(1:0,`id`val,[INT,INT])
    db=database("",RANGE,1 101 201 301)
    pmt=db.createPartitionedTable([mt1,mt2,mt3],`pst,`id)
    
    mt1.append!(table(1..100 as id,rand(100,100) as val))
    mt2.append!(table(101..200 as id,rand(100,100) as val))
    mt3.append!(table(201..300 as id,rand(100,100) as val))
    
    select * from pmt

    由于分区内存表不使用全局锁,创建以后不能再动态增删子表。


    3.1 增加查询的并发性

    分区表增加查询的并发性有三层含义:(1)键值表在查询时也需要加锁,分区表由子表独立管理锁,相当于把锁的粒度变细了,因此可以增加读的并发性;(2)批量计算时分区表可以并行处理每个子表;(3)如果SQL查询的过滤指定了分区字段,那么可以缩小分区范围,避免全表扫描。

    以键值内存表为例,我们对比在分区和不分区的情况下,并发查询的性能。首先,创建模拟数据集,一共包含500万行数据。

    n=5000000
    id=shuffle(1..n)
    qty=rand(1000,n)
    price=rand(1000.0,n)
    kt=keyedTable(`id,id,qty,price)
    share kt as skt
    
    id_range=cutPoints(1..n,20)
    db=database("",RANGE,id_range)
    pkt=db.createPartitionedTable(kt,`pkt,`id).append!(kt)
    share pkt as spkt

    我们在另外一台服务器上模拟10个客户端同时查询键值内存表。每个客户端查询10万次,每次查询一条数据,统计每个客户端查询10万次的总耗时。

    def queryKeyedTable(tableName,id){
    	for(i in id){
    		select * from objByName(tableName) where id=i
    	}
    }
    conn=xdb("192.168.1.135",18102,"admin","123456")
    n=5000000
    
    jobid1=array(STRING,0)
    for(i in 1..10){
    	rid=rand(1..n,100000)
    	s=conn(submitJob,"evalQueryUnPartitionTimer"+string(i),"",evalTimer,queryKeyedTable{`skt,rid})
    	jobid1.append!(s)
    }
    time1=array(DOUBLE,0)
    for(j in jobid1){
    	time1.append!(conn(getJobReturn,j,true))
    }
    
    jobid2=array(STRING,0)
    for(i in 1..10){
    	rid=rand(1..n,100000)
    	s=conn(submitJob,"evalQueryPartitionTimer"+string(i),"",evalTimer,queryKeyedTable{`spkt,rid})
    	jobid2.append!(s)
    }
    time2=array(DOUBLE,0)
    for(j in jobid2){
    	time2.append!(conn(getJobReturn,j,true))
    }

    time1是10个客户端查询未分区键值内存表的耗时,time2是10个客户端查询分区键值内存表的耗时,单位是毫秒。

    time1
    [6719.266848,7160.349678,7271.465094,7346.452625,7371.821485,7363.87979,7357.024299,7332.747157,7298.920972,7255.876976]
    
    time2
    [2382.154581,2456.586709,2560.380315,2577.602019,2599.724927,2611.944367,2590.131679,2587.706832,2564.305815,2498.027042]

    可以看到,每个客户端查询分区键值内存表的耗时要低于查询未分区内存表的耗时。

    查询未分区的内存表,可以保证快照隔离。但查询一个分区内存表,不再保证快照隔离。如前面所说分区内存表的读写不使用全局锁,一个线程在查询时,可能另一个线程正在写入而且涉及多个子表,从而可能读到一部分写入的数据。


    3.2 增加写入的并发性

    以分区的常规内存表为例,我们可以同时往不同的分区写入数据。

    t=table(1:0,`id`val,[INT,INT])
    db=database("",RANGE,1 101 201 301)
    pt=db.createPartitionedTable(t,`pt,`id)
    
    def writeData(mutable t,id,batchSize,n){
    	for(i in 1..n){
    		idv=take(id,batchSize)
    		valv=rand(100,batchSize)
    		tmp=table(idv,valv)
    		t.append!(tmp)
    	}
    }
    
    job1=submitJob("write1","",writeData,pt,1..100,1000,1000)
    job2=submitJob("write2","",writeData,pt,101..200,1000,1000)
    job3=submitJob("write3","",writeData,pt,201..300,1000,1000)

    上面的代码中,同时有3个线程对pt的3个不同的分区进行写入。需要注意的是,我们要避免同时对相同分区进行写入。例如,下面的代码可能会导致系统崩溃。

    job1=submitJob("write1","",writeData,pt,1..300,1000,1000)
    job2=submitJob("write2","",writeData,pt,1..300,1000,1000)

    上面的代码定义了两个写入线程,并且写入的分区相同,这样会破坏内存。为了保证每个分区数据的安全性和一致性,我们可将分区内存表共享。这样即可定义多个线程同时对相同分区分入。

    share pt as spt
    job1=submitJob("write1","",writeData,spt,1..300,1000,1000)
    job2=submitJob("write2","",writeData,spt,1..300,1000,1000)


    4. 数据操作比较


    4.1 增删改查

    下表总结了4种类型内存表在共享/分区的情况下支持的增删改查操作。

    说明:

    • 常规内存表、键值内存表、MVCC内存表都支持增删改查操作,流数据表仅支持增加数据和查询,不支持删除和更新操作。
    • 对于键值内存表,如果查询的过滤条件中包含主键,查询的性能会得到明显提升。
    • 对于分区内存表,如果查询的过滤条件中包含分区列,系统能够缩小要扫描的分区范围,从而提升查询的性能。


    4.2 并发性

    在没有写入的情况下,所有内存表都允许多个线程同时查询。在有写入的情况下,4种内存表的并发性有所差异。下表总结了4种内存表在共享/分区的情况下支持的并发读写情况。

    说明:

    • 共享表允许并发读写。
    • 对于没有共享的分区表,不允许多线程对相同分区同时写入的。


    4.3 持久化

    • 常规内存表和键值内存表不支持数据持久化。一旦节点重启,内存中的数据将全部丢失。
    • 只有空的流数据表才支持数据持久化。要对流数据表进行持久化,首先要配置流数据持久化的目录persistenceDir,再使用enableTableShareAndPersistence使用将流数据表共享,并持久化到磁盘上。例如,将流数据表t共享并持久化到磁盘上。
    t=streamTable(1:0,`id`val,[INT,INT])
    enableTableShareAndPersistence(t,`st)

    流数据表启用了持久化后,内存中仍然会保留流数据表中部分最新的记录。默认情况下,内存会保留最新的10万条记录。我们也可以根据需要调整这个值。

    流数据表持久化可以设定采用异步/同步、压缩/不压缩的方式。通常情况下,异步模式能够实现更高的吞吐量。

    系统重启后,再次执行enableTableShareAndPersistence函数,会将磁盘中的所有数据加载到内存。

    • MVCC内存表支持持久化。在创建MVCC内存表时,我们可以指定持久化的路径。例如,创建持久化的MVCC内存表。
    t=mvccTable(1:0,`id`val,[INT,INT],"/home/user/DolphinDB/mvccTable")
    t.append!(table(1..10 as id,rand(100,10) as val))

    系统重启后,我们可以使用loadMvccTable函数将磁盘中的数据加载到内存中。例如:

    t=loadMvccTable("/home/user/DolphinDB/mvccTable","t")

    5. 表结构操作比较

    内存表的结构操作包括新增列、删除列、修改列(内容和数据类型)以及调整列的顺序。下表总结了4种类型内存表在共享/分区的情况下支持的结构操作。

    说明:

    • 分区表以及MVCC内存表不能通过addColumn函数新增列。
    • 分区表可以通过update语句来新增列,但是流数据表不允许修改,因此流数据表不能通过update语句来新增列。


    6. 小结

    DolphinDB支持4种类型内存表,还引入了共享和分区的概念,基本能够满足内存计算和流计算的各种需求。

    展开全文
  • IIC总线时序详解

    千次阅读 2017-07-06 09:52:45
    I2C(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。I2C总线产生于在80年代,最初为...可随时监控内存、硬盘、网络、系统温度等多个参数,增加了系统的安全

    I2C(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。I2C总线产生于在80年代,最初为音频和视频设备开发,如今主要在服务器管理中使用,其中包括单个组件状态的通信。例如管理员可对各个组件进行查询,以管理系统的配置或掌握组件的功能状态,如电源和系统风扇。可随时监控内存、硬盘、网络、系统温度等多个参数,增加了系统的安全性,方便了管理。

    1、I2C总线特点
    I2C总线最主要的优点是其简单性和有效性。由于接口直接在组件之上,因此I2C总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。总线的长度可高达25英尺,并且能够以10Kbps的最大传输速率支持40个组件。I2C总线的另一个优点是,它支持多主控(multimastering), 其中任何能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。

    2、总线的构成及信号类型
    I2C总线是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传送,最高传送速率100kbps。各种被控制电路均并联在这条总线上,但就像电话机一样只有拨通各自的号码才能工作,所以每个电路和模块都有唯一的地址,在信息的传输过程中,I2C总线上并接的每一模块电路既是主控器(或被控器),又是发送器(或接收器),这取决于它所要完成的功能。CPU发出的控制信号分为地址码和控制量两部分,地址码用来选址,即接通需要控制的电路,确定控制的种类;控制量决定该调整的类别(如对比度、亮度等)及需要调整的量。这样,各控制电路虽然挂在同一条总线上,却彼此独立,互不相关。


    I2C总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。
    开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
    结束信号:SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。
    应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。


      目前有很多半导体集成电路上都集成了I2C接口。带有I2C接口的单片机有:CYGNAL的 C8051F0XX系列,PHILIPSP87LPC7XX系列,MICROCHIP的PIC16C6XX系列等。很多外围器件如存储器、监控芯片等也提供I2C接口。

    在I2C总线通信的过程中,参与通信的双方互相之间所传输的信息种类归纳如下。

      主控器向被控器发送的信息种类有:启动信号、停止信号、7位地址码、读/写控制位、10位地址码、数据字节、重启动信号、应答信号、时钟脉冲。

      被控器向主控器发送的信息种类有:应答信号、数据字节、时钟低电平。

      下面对I2C总线通信过程中出现的几种信号状态和时序进行分析。


    总线空闲状态
      I2C总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。

    启动信号

    在时钟线SCL保持高电平期间,数据线SDA上的电平被拉低(即负跳变),定义为I2C总线总线的启动信号,它标志着一次数据传输的开始。

    启动信号是一种电平跳变时序信号,而不是一个电平信号。启动信号是由主控器主动建立的,在建立该信号之前I2C总线必须处于空闲状态,如图1所示



    图1


    停止信号
      在时钟线SCL保持高电平期间,数据线SDA被释放,使得SDA返回高电平(即正跳变),称为I2C总线的停止信号,它标志着一次数据传输的终止。

      停止信号也是一种电平跳变时序信号,而不是一个电平信号,停止信号也是由主控器主动建立的,建立该信号之后,I2C总线将返回空闲状态。

    ④数据位传送。  
      在I2C总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。

      进行数据传送时,在SCL呈现高电平期间,SDA上的电平必须保持稳定,低电平为数据0,高电平为数据1。

      只有在SCL为低电平期间,才允许SDA上的电平改变状态。逻辑0的电平为低电压,而逻辑1的电平取决于器件本身的正电源电压VDD(当使用独立电源时),如图2所示。


    图2

    ⑤应答信号。
      I2C总线上的所有数据都是以8位字节传送的,发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。

      应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。

      对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。


      如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P,如图3所示。


    图3


    ⑥插入等待时间。
      如果被控器需要延迟下一个数据字节开始传送的时间,则可以通过把时钟线SCL电平拉低并且保持,使主控器进入等待状态。

      一旦被控器释放时钟线,数据传输就得以继续下去,这样就使得被控器得到足够时间转移已经收到的数据字节,或者准备好即将发送的数据字节。

      带有CPU的被控器在对收到的地址字节做出应答之后,需要一定的时间去执行中断服务子程序,来分析或比较地址码,其间就把SCL线钳位在低电平上,直到处理妥当后才释放SCL线,进而使主控器继续后续数据字节的发送,如图4所示。


    图4


    ⑦重启动信号
      在主控器控制总线期间完成了一次数据通信(发送或接收)之后,如果想继续占用总线再进行一次数据通信(发送或接收),而又不释放总线,就需要利用重启动Sr信号时序。

      重启动信号Sr既作为前一次数据传输的结束,又作为后一次数据传输的开始。利用重启动信号的优点是,在前后两次通信之间主控器不需要释放总线,这样就不会丢失总线的控制权,即不让其他主器件节点抢占总线。

      

    ⑧时钟同步。

      如果在某一I2C总线系统中存在两个主器件节点,分别记为主器件1和主器件2,其时钟输出端分别为CLK1和CLK0,它们都有控制总线的能力。

      假设在某一期间两者相继向SCL线发出了波形不同的时钟脉冲序列CLK1和CLK2(时钟脉冲的高、低电平宽度都是依靠各自内部专用计数器定时产生的),在总线控制权还没有裁定之前这种现象是可能出现的。

      鉴于I2C总线的“线与”特性,使得时钟线SCL上得到的时钟信号波形,既不像主器件1所期望的CLK1,也不像主器件2所期望的CLK2,而是两者进行逻辑与的结果。

      CLKI和CLK2的合成波形作为共同的同步时钟信号,一旦总线控制权裁定给某一主器件,则总线时钟信号将会只由该主器件产生,如图5所示。


    图5


    ⑨总线冲突和总线仲裁。
      假如在某I2C总线系统中存在两个主器件节点,分别记为主器件1和主器件2,其数据输出端分别为DATA1和DATA2,它们都有控制总线的能力,这就存在着发生总线冲突(即写冲突)的可能性。

      假设在某一瞬间两者相继向总线发出了启动信号,鉴于:I2C总线的“线与”特性,使得在数据线SDA上得到的信号波形是DATA1和DATA2两者相与的结果,该结果略微超前送出低电平的主器件1,其DATA1的下降沿被当做SDA的下降沿。

      在总线被启动后,主器件1企图发送数据“101……”,主器件2企图发送数据“100101……”。

      两个主器件在每次发出一个数据位的同时都要对自己输出端的信号电平进行抽检,只要抽检的结果与它们自己预期的电平相符,就会继续占用总线,总线控制权也就得不到裁定结果。

      主器件1的第3位期望发送“1”,也就是在第3个时钟周期内送出高电平。

      在该时钟周期的高电平期间,主器件1进行例行抽检时,结果检测到一个不相匹配的电平“0”,这时主器件1只好决定放弃总线控制杈;因此,主器件2就成了总线的惟一主宰者,总线控制权也就最终得出了裁定结果,从而实现了总线仲裁的功能。

      从以上总线仲裁的完成过程可以得出:仲裁过程主器件1和主器件2都不会丢失数据;各个主器件没有优先级别之分,总线控制权是随机裁定的,即使是抢先发送启动信号的主器件1最终也并没有得到控制杈。

      系统实际上遵循的是“低电平优先”的仲裁原则,将总线判给在数据线上先发送低电平的主器件,而其他发送高电平的主器件将失去总线控制权,如图6所示。


    图6


    ⑩总线封锁状态。
      在特殊情况下,如果需要禁止所有发生在I2C总线上的通信活动,封锁或关闭总线是一种可行途径,只要挂接于该总线上的任意一个器件将时钟线SCL锁定在低电平上即可。


    展开全文
  • JVM内存模型详解

    万次阅读 多人点赞 2018-02-09 14:24:27
    笔记大纲1、jvm内存结构图2、jvm按照线程共享和私有内存区域划分结构图3、堆和栈在功能、内存大小、线程共享私有进行比较4、JVM运行结构图5、线程安全本质时序图6、jdk6、7、8三个版本内存模型比较7、jdk1.8为什么将...
    笔记大纲
    1、jvm内存结构图
    2、jvm按照线程共享和私有内存区域划分结构图
    3、堆和栈在功能、内存大小、线程共享私有进行比较
    4、JVM运行结构图
    5、线程安全本质时序图
    6、jdk6、7、8三个版本内存模型比较
    7、jdk1.8为什么将方法区移除到本地内存
    8、jvm内存启动参数详解

    JVM内存结构图(JDK1.6)
    多线程共享内存区域:方法区、堆。
    每一个线程独享内存:java栈、本地方法栈、程序计数器。
    程序计数器:较小的内存空间,当前线程执行的字节码的行号指示器;各线程之间独立存储,互不影响;
    java 栈:线程私有,生命周期和线程,每个方法在执行的同时都会创建一个 栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。方法的执行就对应着栈帧在虚拟机栈中入栈和出栈的过程;栈里面存放着各种基本数据类型和对象的引用;
    本地方法栈:本地方法栈保存的是native方法的信息,当一个JVM创建的线程调用native方法后,JVM不再为其在虚拟机栈中创建栈帧,JVM只是简单地动态链接并直接调用native方法
    堆:Java堆是程序员需要重点关注的一块区域,因为涉及到内存的分配(new关键字,反射等)与回收(回收算法,收集器等);
    方法区:也叫永久区,用于存储已经被虚拟机加载的类信息,常量("zdy","123"等),静态变量(static变量)等数据。(jdk1.8已经将方法区去掉了,将方法区移动到直接内存)
    运行时常量池:运行时常量池是方法区的一部分,用于存放编译期生成的各种字面("zdy","123"等)和符号引用。
    直接内存:不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域;
    1)如果使用了NIO,这块区域会被频繁使用,在java堆内可以用directByteBuffer对象直接引用并操作;
    2) 这块内存不受java堆大小限制,但受本机总内存的限制,可以通过MaxDirectMemorySize来设置(默认与堆内存最大值一样),所以也会出现OOM异常;

    JVM内存中按照线程共享和线程私有划分结构图(JDK1.6)

    堆和栈的区别
    1)堆和栈功能上的区别
    以栈帧的方式存储方法调用的过程,并存储方法调用过程中基本数据类型的变
    量(int、short、long、byte、float、double、boolean、char等)以及对象的引
    用变量,其内存分配在栈上,变量出了作用域就会自动释放
    而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,
    它们指向的对象都存储在堆内存中;
    2)堆和栈在线程共享和线程私有区别
    栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存
    堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。
    3)空间大小
    栈的内存要远远小于堆内存,栈的深度是有限制的,如果递归没有及时跳出,很可能发生StackOverFlowError问题。
    你可以通过-Xss选项设置栈内存的大小(这个参数是设定单个线程的栈空间)。-Xms选项可以设置堆的开始时的大小,-Xmx选项可以设置堆的最大值

    JVM内存运行时结构图
    每一个线程独享的内存区域有:程序计数器、java栈、本地方法栈
    线程共享区域:堆内存、方法区(JDK1.8已经去掉了方法区)

    线程安全本质
    线程安全本质是由于多个线程对同一个堆内存中的Count变量操作的时候,每一个线程会在线程内部创建这个堆内存Count变量的副本,线程内所有的操作都是对这个Count副本进行操作。这时如果其他线程操作这个堆内存Count变量,改变了Count值对这个线程是不可见的。当前线程操作完Count变量将值从副本空间写到主内存(堆内存)的时候就会覆盖其他线程操作Count变量的结果,引发线程不安全问题。

    JDK1.6,JDK1.7,JDK1.8不同版本JVM内存模型区别
    相对于jdk1.6,jDK1.7将运行时常量池从方法区移除到堆内存。
    相对于JDK1.6,JDK1.8直接将方法区去掉,在本地内存中新增元数据空间。运行时常量池仍然在堆中。元数据区存放类加载信息。

    JDK1.8为什么要移除方法区
    1)永久代来存储类信息、常量、静态变量等数据不是个好主意, 很容易遇到内存溢出的问题.JDK8的实现中将类的元数据放入 native memory, 将字符串池和类的静态变量放入java堆中. 可以使用MaxMetaspaceSize对元数据区大小进行调整;
    2)对永久代进行调优是很困难的,同时将元空间与堆的垃圾回收进行了隔离,避免永久代引发的Full GC和OOM等问题;

    JVM内存参数设定
    -Xms 初始堆内存大小
    -Xmx 最大堆内存大小
    -Xss 单个线程栈大小
    -XX:NewSize 初始新生代堆大小
    -XX:MaxNewSize 生代最大堆大小
    -XX:PermSize 方法区初始大小(JDK1.7及以前)
    -XX:MaxPermSize 方法区最大大小(JDK1.7及以前)
    -XX:MetaspaceSize 元数据区初始值(JDK1.8)
    -XX:MaxMetaspaceSize 元数据区最大值(JDK1.8)
    参数设置示例
    jdk1.7 windows设置tomcat的catalina.bat
    set JAVA_OPTS=-Xms1024m -Xmx1024m -Xss1m -XX:PermSize=128m -XX:MaxPermSize=256m -XX:NewSize=256m -XX:MaxNewSize=256m
    jdk1.8 windows设置tomcat的catalina.bat
    set JAVA_OPTS=-Xms1024m -Xmx1024m -Xss1m -XX:MetaspaceSize=128m -XX:MAXMetaspaceSize=256m -XX:NewSize=256m -XX:MaxNewSize=256m

    jdk1.7 linux设置tomcat的catalina.sh
    JAVA_OPTS=-Xms1024m -Xmx1024m -Xss1m -XX:PermSize=128m -XX:MaxPermSize=256m -XX:NewSize=256m -XX:MaxNewSize=256m
    jdk1.8 linux设置tomcat的catalina.sh
    JAVA_OPTS=-Xms1024m -Xmx1024m -Xss1m -XX:MetaspaceSize=128m -XX:MAXMetaspaceSize=256m -XX:NewSize=256m -XX:MaxNewSize=256m
    展开全文
  • DDR3内存详解,存储器结构+时序+初始化过程 2017-06-17 16:10:33a_chinese_man阅读数 23423更多 分类专栏:硬件开发基础 转自:http://www.360doc.com/content/14/0116/16/15528092_345730642.shtml 首先,...
  • DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合...校准系数以程序的形式储存在OTP内存中,传感器内部在检测信号的处理过程中要调用这些校准系数。 单线制串行接口, 使系统集成变得简易快捷。超小
  • Java内存模型详解

    2019-09-01 09:58:07
    Java内存模型(Java Memory Model,简称JMM),即Java虚拟机定义的一种用来屏蔽各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能够达到一致的内存访问效果的内存模型。本篇文章大致设涉及到五个...
  • 每毫秒记录一下电脑内存的使用情况,然后就可以根据统计的数据,利用图形化界面(InfluxDB V1一般配合Grafana)制作内存使用情况的折线图;可以理解为按时间记录一些数据(常用的监控数据、埋点统计数据等)...
  • ML之时序详解

    2020-01-29 21:56:26
    动态图概念 : 从静态图中抽取瞬间值的变化描述系统随时间变化的行为, 动态图包括交互图活动图状态图, 这篇博客研究交互图 包括时序图和协作图; – 时序图 : 显示对象之间的关系, 强调对象之间消息的时间顺序, 显示...
  • UML之时序详解

    万次阅读 多人点赞 2015-12-21 19:10:42
    作者 : 万境绝尘 转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/17927131动态图概念 : 从静态图...– 时序图 : 显示对象之间的关系, 强调对象之间消息的时间顺序, 显示对象之间的交互; – 协作
  • DDR时序参数详解(转载)

    千次阅读 2019-06-14 15:49:00
    转自: ...2、Post CAS技术在单次的突发访问中是没有影响的,只有连续多次的突发访问才有性能的提升,但是对于内存操作一般不可能做单次的突发,就算程序这样做了硬件缓存机制也会做多次的突发。
  • 内存详解

    2007-09-22 13:06:43
    前不久去买内存,看到很多水货,为了提高警惕,收集一些资料与各位分享: 内存是电脑必不可少的部件,也是影响电脑性能的关键部件。而对于内存颗粒,则是内存条上必不可少的一部分,同时也与内存的性能息息相关。一条...
  • 内存重要参数详解

    千次阅读 2014-02-12 17:25:17
    内存重要参数详解 重新花时间温习一下memory的一些概念,找了一些之前保存的文档,介绍ddr的相关概念的。现已找不到原文链接。 时序  内存时序参数一般简写为2/2/2/6-11/1T的格式,分别代表CAS/tRCD/tRP...
  • S3C2440内存控制详解

    千次阅读 2013-10-14 08:52:09
    内存控制器地址,摘自三星技术手册: BWSCON 0x48000000 R/W Bus Width & Wait Status Control BANKCON0 0x48000004 Boot ROM Control BANKCON1 0x48000008 BANK1 Control BANKCON2 0x4800000C BANK...
  • UML交互图 -- 时序图 协作图详解

    万次阅读 2016-07-10 17:11:13
    作者 : 万境绝尘 ... . ...动态图概念 : 从静态图中抽取瞬间值的变化描述系统随时间变化的行为, 动态图包括交互图活动图状态图, ... 这篇博客研究交互图 包括...-- 时序图 : 显示对象之间的关系, 强调对象之间消息
  • 内存重要参数详解 RAS CAS 分类:LINUX 2014-09-12 09:41:58 ...http://blog.chinaunix.net/u/9205/showart_1091970.h时序 内存时序参数一般简写为 2/2/2/6-11/1T的格式,分别代表CAS/tRCD/tRP...
  • 时序图组成 : 对象, 生命线, 激活, 消息; ② 协作图组成 : 对象, 链接, 消息; ③ 协作图与时序图是等价的, 可以无损转换; ④ 时序图侧重时间顺序 对象创建撤销, 协作图侧重链接, 结构;
  • FBD 内存详解

    2009-03-11 17:11:18
    什么是FB-DIMM? 要说清FB-DIMM,我们最好还是先了解一下传统的Reg-DIMM。...在高容量模组上,内存芯片数量很多,而且在需要大容量内存的工作场合,内存模组的安插数量也是很多的,这使命令与寻址信号的稳定性受到...
  • IO端口与IO内存详解

    千次阅读 2018-03-07 10:19:55
    物理地址中很大一部分是留给内存条中的内存的,但也常被映射到其他存储器上(如显存、BIOS等)。在程序指令中的虚拟地址经过段映射和页面映射后,就生成了物理地址,这个物理地址被放到CPU的地址线上。物理地址空间...
  • 内存相关概念详解

    2018-01-03 15:41:00
    和表格的检索原理一样,先指定一个行(Row),再指定一个列(Column),我们就可以准确地找到所需要的单元格,这就是内存芯片寻址的基本原理。对于内存,这个单元格可称为存储单元,那么这个表格(存储阵列)就是逻辑...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,519
精华内容 1,807
关键字:

内存时序详解