现在的位置: 首页 > 综合 > 正文

mysql内核分析–innodb动态数组内部实现(下)

2013年05月13日 ⁄ 综合 ⁄ 共 6557字 ⁄ 字号 评论关闭

2)used

    used表示data[DYN_ARRAY_DATA_SIZE]字段中已经使用的字节的数量,假设需要申请len字节的长度,在使用之前需要判断的是,尾 block中的可用空间是否够用。也就是判断判断下used+len是否满足used+len<= DYN_ARRAY_DATA_SIZE,如果满足就可以放进该block,并将已使用的字节数used加上len。

    如果,该block空间不够,那么就会申请一个新的block,这里我们就可以明白了,为什么需要满足len的长度小于等于DYN_ARRAY_DATA_SIZE。

    好,我们下面先看下分配空间的函数。

/*************************************************************************

Makes room on top of a dyn array and returns a pointer to the added element.

The caller must copy the element to the pointer returned. */

UNIV_INLINE

void*

dyn_array_push(

/*===========*/

                            /* out: pointer to the element */

       dyn_array_t*  arr,  /* in: dynamic array */

       ulint        size) /* in: size in bytes of the element */

{

       dyn_block_t*  block;

       ulint        used;

 

       ut_ad(arr);

       ut_ad(arr->magic_n == DYN_BLOCK_MAGIC_N);

       ut_ad(size <= DYN_ARRAY_DATA_SIZE);

       ut_ad(size);

      

       block = arr;

       used = block->used;

 

       if (used + size > DYN_ARRAY_DATA_SIZE) {

              /* Get the last array block */

             

              block = dyn_array_get_last_block(arr);

              used = block->used;

 

              if (used + size > DYN_ARRAY_DATA_SIZE) {

                     block = dyn_array_add_block(arr);

                     used = block->used;

              }

       }

 

       block->used = used + size;

       ut_ad(block->used <= DYN_ARRAY_DATA_SIZE);

 

       return((block->data) + used);

}

   在这里面,我们要关注一下:

       block = arr;

       used = block->used;

   首先得到arr的首节点,在前面的内容中,我们描述过,在只有一个节点的时候(参考图1),arr本身就是block,如果是多个节点,这个代码相当于获得的是链表的首节点。

    那么如果是只有一个节点,那么很容易理解。假设链表是多个节点,这里面的used肯定是大于DYN_ARRAY_DATA_SIZE。因为每次生成一个block,就会调用dyn_array_add_block函数,在该函数中,会将前一个block的used进行置位操作。

block->used = block->used | DYN_BLOCK_FULL_FLAG;

    所以,当arr存在多个block的时候,首节点的条件“used + size > DYN_ARRAY_DATA_SIZE”必为真。这时候,我们就去获取尾节点。

       block = dyn_array_get_last_block(arr);

       used = block->used;

 

       if (used + size > DYN_ARRAY_DATA_SIZE) {

              block = dyn_array_add_block(arr);

              used = block->used;

       }

   尾节点的used肯定没有进行过置位操作。然后判断是否需要新增block节点。

   回过头来,函数dyn_array_push的返回值,是一个指针,指向新增加元素的指针,然后用户对该指针进行操作赋值。我们来看下,系统中的一个使用实例。

/*******************************************************

Pushes an object to an mtr memo stack. */

UNIV_INLINE

void

mtr_memo_push(

/*==========*/

       mtr_t*     mtr, /* in: mtr */

       void*      object,     /* in: object */

       ulint type)       /* in: object type: MTR_MEMO_S_LOCK, ... */

{

       dyn_array_t*         memo;

       mtr_memo_slot_t* slot;

 

       ut_ad(object);

       ut_ad(type >= MTR_MEMO_PAGE_S_FIX);     

       ut_ad(type <= MTR_MEMO_X_LOCK);

       ut_ad(mtr);

       ut_ad(mtr->magic_n == MTR_MAGIC_N);

 

       memo = &(mtr->memo);     

 

    //分配大小为sizeof(mtr_memo_slot_t)的动态数组空间

       slot = dyn_array_push(memo, sizeof(mtr_memo_slot_t));

 

       slot->object = object;  //对返回的指针进行操作

       slot->type = type;     //对返回的指针进行操作

}

   通过上文的描述,我们可以使用dyn进行操作。分配一个元素,然后进行赋值。那么我们再思考下,有没有优化的可能?

   假设现在一个应用场景:

   我们需要插入三个元素,我们插入一个元素,就需要修改一次used,再插入,又得调用push函数进行操作。频繁的对dyn的数据结构进行操作。这样的效率是很低的。

   这时候我们,会想到,为什么不直接一次申请大小长度为三个需要使用的总长度。假设这个总长度为len,那么我们是否就直接通过mtr_memo_push进行分配长度为len的空间?

   好,这是可以的,但是,我们再假想一种情况,这三个元素的长度是变长的,我们没法预先知道这样的一个长度。假设可能需要10个字节,也可能需要12个字节,如果是分配了12个字节给它,那么就会多余两个。所以,我们可以事先通过一个新函数dyn_array_open来分配足够大的字节数,然后通过dyn_array_close进行重设used。

/*************************************************************************

Makes room on top of a dyn array and returns a pointer to a buffer in it.

After copying the elements, the caller must close the buffer using

dyn_array_close. */

UNIV_INLINE

byte*

dyn_array_open(

/*===========*/

                            /* out: pointer to the buffer */

       dyn_array_t*  arr,  /* in: dynamic array */

       ulint        size) /* in: size in bytes of the buffer; MUST be

                            smaller than DYN_ARRAY_DATA_SIZE! */

{

       dyn_block_t*  block;

       ulint        used;

 

       ut_ad(arr);

       ut_ad(arr->magic_n == DYN_BLOCK_MAGIC_N);

       ut_ad(size <= DYN_ARRAY_DATA_SIZE);

       ut_ad(size);

      

       block = arr;

       used = block->used;

 

       if (used + size > DYN_ARRAY_DATA_SIZE) {

              /* Get the last array block */

             

              block = dyn_array_get_last_block(arr);

              used = block->used;

 

              if (used + size > DYN_ARRAY_DATA_SIZE) {

                     block = dyn_array_add_block(arr);

                     used = block->used;

                     ut_a(size <= DYN_ARRAY_DATA_SIZE);

              }

       }

 

       ut_ad(block->used <= DYN_ARRAY_DATA_SIZE);

#ifdef UNIV_DEBUG

       ut_ad(arr->buf_end == 0);

 

       arr->buf_end = used + size;

#endif    

       return((block->data) + used);

}

   留意下,函数的英文注释“After copying the elements”,说明是赋值多元组的。而且和dyn_array_push函数相比,函数体里面没有对used进行重新赋值。这个重新赋值的工作留给了dyn_array_close函数,我们看下该函数的实现。

/*************************************************************************

Closes the buffer returned by dyn_array_open. */

UNIV_INLINE

void

dyn_array_close(

/*============*/

       dyn_array_t*  arr,  /* in: dynamic array */

       byte*             ptr)  /* in: buffer space from ptr up was not used */

{

       dyn_block_t*  block;

 

       ut_ad(arr);

       ut_ad(arr->magic_n == DYN_BLOCK_MAGIC_N);

      

       block = dyn_array_get_last_block(arr);

 

       ut_ad(arr->buf_end + block->data >= ptr);

 

       block->used = ptr - block->data;

      

       ut_ad(block->used <= DYN_ARRAY_DATA_SIZE);

 

#ifdef UNIV_DEBUG

       arr->buf_end = 0;

#endif

}

   在该函数里面通过下面的一行代码进行赋值。

block->used = ptr - block->data;

   其中ptr是dyn_array_open函数返回指针并使用之后的调整值。假设dyn_array_open返回的是ptr,赋值第一个长度len1的元素后,ptr得加上len1,继续len2长度的元素,ptr加上len2,以此类推。ptr指示下一个赋元组时的指针。所以通过block->used = ptr - block->data;就能够计算出偏移量,这个偏移量就是used。

 看下代码的实际应用场景。

/************************************************************

Writes 8 bytes to a file page buffered in the buffer pool.

Writes the corresponding log record to the mini-transaction log. */

void

mlog_write_dulint(

/*==============*/

       byte*      ptr,  /* in: pointer where to write */

       dulint      val,  /* in: value to write */

       mtr_t*     mtr) /* in: mini-transaction handle */

{

       byte*      log_ptr;

 

       if (ptr < buf_pool->frame_zero || ptr >= buf_pool->high_end) {

              fprintf(stderr,

       "InnoDB: Error: trying to write to a stray memory location %p/n", ptr);

              ut_error;

       }

 

       ut_ad(ptr && mtr);

 

       mach_write_to_8(ptr, val);

 

    //分配空间

       log_ptr = mlog_open(mtr, 11 + 2 + 9);

      

       /* If no logging is requested, we may return now */

       if (log_ptr == NULL) {

 

              return;

       }

 

       log_ptr = mlog_write_initial_log_record_fast(ptr, MLOG_8BYTES,

                                                 log_ptr, mtr);

 

       mach_write_to_2(log_ptr, ptr - buf_frame_align(ptr));

       log_ptr += 2;

      

       log_ptr += mach_dulint_write_compressed(log_ptr, val);

 

    //重设used值

       mlog_close(mtr, log_ptr);

}

 

  动态数组的主要内容就是这些,还有一些关于获取长度、元素等的函数,建议参考源代码进行理解。

抱歉!评论已关闭.