OpenMP中For Construct对dynamic的调度方式详解

目录
  • 前言
  • 前置知识
  • dynamic 调度方式分析
  • 实例分析
  • 总结

前言

在本篇文章当中主要给大家介绍 OpenMp for construct 的实现原理,以及与他相关的动态库函数分析,与 for construct 非常相关的是循环的调度方式,在 OpenMP 当中一共有四种调调方式,auto, dynamic, guided, runtime, 在本篇文章当中主要是对 dynamic 的调度方式进行分析。

前置知识

在介绍 for construct 的实现原理之前,我们首先需要了解一下编译器是如何处理函数参数传递的(本文基于 x86_64 ISA),我们来看一下下面的代码在编译之后函数参数的传递情况。

在前面的文章当中我们已经谈到过了,在 x86 当中参数传递的规约,具体的内容如下所示:

寄存器 含义
rdi 第一个参数
rsi 第二个参数
rdx 第三个参数
rcx 第四个参数
r8 第五个参数
r9 第六个参数

我们现在使用下面的代码来分析一下具体的情况(因为前面使用寄存器只能够传递 6 个参数,而在后面我们要分析的动态库函数当中会传递 7 个参数,因此这里我们使用 8 个参数来测试一下具体的参数传递情况):

#include "stdio.h"

void echo(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8)
{
  printf("%d %d %d %d %d %d %d %d\n", a8, a7, a1, a2, a3, a4, a5, a6);
}

int main()
{
  echo(1, 2, 3, 4 ,5 ,6, 7, 8);
  return 0;
}

上面的程序的反汇编结果如下所示:

000000000040053d <echo>:
  40053d:       55                      push   %rbp
  40053e:       48 89 e5                mov    %rsp,%rbp
  400541:       48 83 ec 30             sub    $0x30,%rsp
  400545:       89 7d fc                mov    %edi,-0x4(%rbp)
  400548:       89 75 f8                mov    %esi,-0x8(%rbp)
  40054b:       89 55 f4                mov    %edx,-0xc(%rbp)
  40054e:       89 4d f0                mov    %ecx,-0x10(%rbp)
  400551:       44 89 45 ec             mov    %r8d,-0x14(%rbp)
  400555:       44 89 4d e8             mov    %r9d,-0x18(%rbp)
  400559:       8b 7d f4                mov    -0xc(%rbp),%edi
  40055c:       8b 75 f8                mov    -0x8(%rbp),%esi
  40055f:       8b 55 fc                mov    -0x4(%rbp),%edx
  400562:       8b 45 18                mov    0x18(%rbp),%eax # a8
  400565:       8b 4d e8                mov    -0x18(%rbp),%ecx
  400568:       89 4c 24 10             mov    %ecx,0x10(%rsp)
  40056c:       8b 4d ec                mov    -0x14(%rbp),%ecx
  40056f:       89 4c 24 08             mov    %ecx,0x8(%rsp)
  400573:       8b 4d f0                mov    -0x10(%rbp),%ecx
  400576:       89 0c 24                mov    %ecx,(%rsp)
  400579:       41 89 f9                mov    %edi,%r9d
  40057c:       41 89 f0                mov    %esi,%r8d
  40057f:       89 d1                   mov    %edx,%ecx
  400581:       8b 55 10                mov    0x10(%rbp),%edx # a7
  400584:       89 c6                   mov    %eax,%esi # a8
  400586:       bf 64 06 40 00          mov    $0x400664,%edi
  40058b:       b8 00 00 00 00          mov    $0x0,%eax
  400590:       e8 8b fe ff ff          callq  400420 <printf@plt>
  400595:       c9                      leaveq 
 
0000000000400597 <main>:
  400597:       55                      push   %rbp
  400598:       48 89 e5                mov    %rsp,%rbp
  40059b:       48 83 ec 10             sub    $0x10,%rsp
  40059f:       c7 44 24 08 08 00 00    movl   $0x8,0x8(%rsp) # 保存参数 8 
  4005a6:       00 
  4005a7:       c7 04 24 07 00 00 00    movl   $0x7,(%rsp) # 保存参数 7 
  4005ae:       41 b9 06 00 00 00       mov    $0x6,%r9d # 保存参数 6 
  4005b4:       41 b8 05 00 00 00       mov    $0x5,%r8d # 保存参数 5 
  4005ba:       b9 04 00 00 00          mov    $0x4,%ecx # 保存参数 4 
  4005bf:       ba 03 00 00 00          mov    $0x3,%edx # 保存参数 3 
  4005c4:       be 02 00 00 00          mov    $0x2,%esi # 保存参数 2 
  4005c9:       bf 01 00 00 00          mov    $0x1,%edi # 保存参数 1
  4005ce:       e8 6a ff ff ff          callq  40053d <echo>
  4005d3:       b8 00 00 00 00          mov    $0x0,%eax
  4005d8:       c9                      leaveq 
  4005d9:       c3                      retq   
  4005da:       66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)

从上面的汇编程序我们可以知道 1 - 6,这几个参数确实是通过寄存器传递的,对应的寄存器就是上文当中我们提到不同的参数对应的寄存器。但是参数 7 和参数 8 是保存在栈上的。根据上面的 main 函数的汇编程序分析,他对应的栈帧的内存布局如下所示:

我们在来分析一下 echo 函数当中 printf 函数参数的传递情况,第二个参数和第三个参数分别是 a8, a7,应该分别保存到寄存器 rsi/esi, rdx/edx 当中,在上面的汇编代码当中已经使用注释的方式进行标注出来了,从下往上进行分析可以看到 a8 保存在位置 0x18(%rbp),a7 保存在 0x10(%rbp),这个地址正是 main 函数保存 a7(当进入函数 echo 之后,a7,和 a8 的位置分别是 rsp + 0x10), a8(当进入函数 echo 之后,a7,和 a8 的位置分别是 rsp + 0x10 + 0x8) 的位置,具体可以结合上面的内存布局图进行分析。

dynamic 调度方式分析

我们使用下面的代码来分析一下动态调度的情况下整个程序的执行流程是怎么样的:

#pragma omp parallel for num_threads(t) schedule(dynamic, size)
for (i = lb; i <= ub; i++)
  body;

编译器会将上面的程序编译成下面的形式:

void subfunction (void *data)
{
  long _s0, _e0;
  while (GOMP_loop_dynamic_next (&_s0, &_e0))
  {
    long _e1 = _e0, i;
    for (i = _s0; i < _e1; i++)
      body;
  }
  // GOMP_loop_end_nowait 这个函数的主要作用就是释放数据的内存空间 在后文当中不进行分析
  GOMP_loop_end_nowait ();
}

GOMP_parallel_loop_dynamic_start (subfunction, NULL, t, lb, ub+1, 1, size);
subfunction (NULL);
// 这个函数在前面的很多文章已经分析过 本文也不在进行分析
GOMP_parallel_end ();
void
GOMP_parallel_loop_dynamic_start (void (*fn) (void *), void *data,
				  unsigned num_threads, long start, long end,
				  long incr, long chunk_size)
{
  gomp_parallel_loop_start (fn, data, num_threads, start, end, incr,
			    GFS_DYNAMIC, chunk_size);
}

static void
gomp_parallel_loop_start (void (*fn) (void *), void *data,
			  unsigned num_threads, long start, long end,
			  long incr, enum gomp_schedule_type sched,
			  long chunk_size)
{
  struct gomp_team *team;
  // 解析具体创建多少个线程
  num_threads = gomp_resolve_num_threads (num_threads, 0);
  // 创建一个含有 num_threads 个线程的线程组
  team = gomp_new_team (num_threads);
  // 对线程组的数据进行初始化操作
  gomp_loop_init (&team->work_shares[0], start, end, incr, sched, chunk_size);
  // 启动 num_threads 个线程执行函数 fn
  gomp_team_start (fn, data, num_threads, team);
}

enum gomp_schedule_type
{
  GFS_RUNTIME, // runtime 调度方式
  GFS_STATIC,	 // static  调度方式
  GFS_DYNAMIC, // dynamic 调度方式
  GFS_GUIDED,	 // guided  调度方式
  GFS_AUTO     // auto    调度方式
};
 

在上面的程序当中 GOMP_parallel_loop_dynamic_start,有 7 个参数,我们接下来仔细解释一下这七个参数的含义:

  • fn,函数指针也就是并行域被编译之后的函数。
  • data,指向共享或者私有的数据,在并行域当中可能会使用外部的一些变量。
  • num_threads,并行域当中指定启动线程的个数。
  • start,for 循环迭代的初始值,比如 for(int i = 0; 这个 start 就是 0 。
  • end,for 循环迭代的最终值,比如 for(int i = 0; i < 100; i++) 这个 end 就是 100 。
  • incr,这个值一般都是 1 或者 -1,如果是 for 循环是从小到达迭代这个值就是 1,反之就是 -1。
  • chunk_size,这个就是给一个线程划分块的时候一个块的大小,比如 schedule(dynamic, 1),这个 chunk_size 就等于 1 。

在函数 GOMP_parallel_loop_dynamic_start 当中会调用函数 gomp_parallel_loop_start ,这个函数的主要作用就是将整个循环的起始位置信息保存到线程组内部,那么就能够在函数 GOMP_loop_dynamic_next 当中直接使用这些信息进行不同线程的分块划分。GOMP_loop_dynamic_next 最终会调用函数 gomp_loop_dynamic_next ,其源代码如下所示:

static bool
gomp_loop_dynamic_next (long *istart, long *iend)
{
  bool ret;
  ret = gomp_iter_dynamic_next (istart, iend);
  return ret;
}

gomp_loop_dynamic_next 函数的返回值是一个布尔值:

  • 如果返回值为 true ,则说明还有剩余的分块需要执行。
  • 如果返回值为 false,则说明没有剩余的分块需要执行了,根据前面 dynamic 编译之后的结果,那么就会退出 while 循环。

gomp_iter_dynamic_next 是划分具体的分块,并且将分块的起始位置保存到变量 istart 和 iend 当中,因为传递的是指针,就能够使用 s0 和 e0 得到数据的值,下面是 gomp_iter_dynamic_next 的源代码,就是具体的划分算法了。

bool
gomp_iter_dynamic_next (long *pstart, long *pend)
{
  // 得到当前线程的指针
  struct gomp_thread *thr = gomp_thread ();
  // 得到线程组共享的数据
  struct gomp_work_share *ws = thr->ts.work_share;
  long start, end, nend, chunk, incr;

  // 保存迭代的最终值
  end = ws->end;
  // 这个值一般都是 1
  incr = ws->incr;
  // 保存分块的大小 chunk size
  chunk = ws->chunk_size;

  // ws->mode 在数据分块比较小的时候就是 1 在数据分块比较大的时候就是 0
  if (__builtin_expect (ws->mode, 1))
    {
    // __sync_fetch_and_add 函数是一个原子操作 ws->next 的初始值为 for 循环的起始位置值
    // 这个函数的返回值是 ws->next 的旧值 然后会将 ws->next 的值加上 chunk
    // 并且整个操作是原子的 是并发安全的
      long tmp = __sync_fetch_and_add (&ws->next, chunk);
    // 从小到大迭代
      if (incr > 0)
	{
	  if (tmp >= end)
	    return false;
    // 分块的最终位置
	  nend = tmp + chunk;
    // 溢出保护操作 分块的值需要小于最终的迭代位置
	  if (nend > end)
	    nend = end;
    // 将分块的值赋值给 pstart 和 pend 这样就能够在并行域当中得到这个分块的区间了
	  *pstart = tmp;
	  *pend = nend;
	  return true;
	}
      else
	{
    // 同样的原理不过是从大到小达迭代
	  if (tmp <= end)
	    return false;
	  nend = tmp + chunk;
	  if (nend < end)
	    nend = end;
	  *pstart = tmp;
	  *pend = nend;
	  return true;
	}
    }

  // 当数据分块比较大的时候执行下面的操作
  // 下面的整体的流程相对比较容易理解整个过程就是一个比较并交换的过程
  // 当比较并交换成功之后就返回结果 返回为 true 或者分块已经分完的话也进行返回
  start = ws->next;
  while (1)
    {
      long left = end - start;
      long tmp;
      // 如果分块已经完全分完 就直接返回 false
      if (start == end)
	return false;

      if (incr < 0)
	{
	  if (chunk < left)
	    chunk = left;
	}
      else
	{
	  if (chunk > left)
	    chunk = left;
	}
      nend = start + chunk;

      tmp = __sync_val_compare_and_swap (&ws->next, start, nend);
      if (__builtin_expect (tmp == start, 1))
	break;

      start = tmp;
    }

  *pstart = start;
  *pend = nend;
  return true;
}

gomp_iter_dynamic_next 函数当中有两种情况的划分方式:

  • 当数据块相对比较小的时候,说明划分的次数就会相对多一点,在这种情况下如果使用 CAS 的话成功的概率就会相对低,对应的就会降低程序执行的效率,因此选择 __sync_fetch_and_add 以减少多线程的竞争情况,降低 CPU 的消耗。
  • 当数据块比较大的时候,说明划分的次数相对比较小,就使用比较并交换的操作(CAS),这样多个线程在进行竞争的时候开销就比较小。

在上面的文章当中我们提到了,gomp_loop_init 函数是对线程共享数据 work_share 进行初始化操作,如果你对具体 work_share 中的数据初始化规则感兴趣,下面是对其初始化的程序:

static inline void
gomp_loop_init (struct gomp_work_share *ws, long start, long end, long incr,
		enum gomp_schedule_type sched, long chunk_size)
{
  ws->sched = sched;
  ws->chunk_size = chunk_size;
  /* Canonicalize loops that have zero iterations to ->next == ->end.  */
  ws->end = ((incr > 0 && start > end) || (incr < 0 && start < end))
	    ? start : end;
  ws->incr = incr;
  ws->next = start;
  if (sched == GFS_DYNAMIC)
    {
      ws->chunk_size *= incr;

#ifdef HAVE_SYNC_BUILTINS
      {
	/* For dynamic scheduling prepare things to make each iteration
	   faster.  */
	struct gomp_thread *thr = gomp_thread ();
	struct gomp_team *team = thr->ts.team;
	long nthreads = team ? team->nthreads : 1;

	if (__builtin_expect (incr > 0, 1))
	  {
	    /* Cheap overflow protection.  */
	    if (__builtin_expect ((nthreads | ws->chunk_size)
				  >= 1UL << (sizeof (long)
					     * __CHAR_BIT__ / 2 - 1), 0))
	      ws->mode = 0;
	    else
	      ws->mode = ws->end < (LONG_MAX
				    - (nthreads + 1) * ws->chunk_size);
	  }
	/* Cheap overflow protection.  */
	else if (__builtin_expect ((nthreads | -ws->chunk_size)
				   >= 1UL << (sizeof (long)
					      * __CHAR_BIT__ / 2 - 1), 0))
	  ws->mode = 0;
	else
	  ws->mode = ws->end > (nthreads + 1) * -ws->chunk_size - LONG_MAX;
      }
#endif
    }
}

实例分析

在本小节当中我们将使用一个实际的例子去分析上面我们所谈到的整个过程:

#include <stdio.h>
#include <omp.h>

int main()
{
#pragma omp parallel for num_threads(4) default(none) schedule(dynamic, 2)
  for(int i = 0; i < 12; ++i)
  {
    printf("i = %d tid = %d\n", i, omp_get_thread_num());
  }
  return 0;
}

上面的程序被编译之后的结果如下所示,具体的程序分析和注释都在下面的汇编程序当中:

000000000040073d <main>:
  40073d:       55                      push   %rbp
  40073e:       48 89 e5                mov    %rsp,%rbp
  400741:       48 83 ec 20             sub    $0x20,%rsp
  400745:       48 c7 04 24 02 00 00    movq   $0x2,(%rsp) # 这个就是 chunk size 符合上面的代码当中指定的 2
  40074c:       00 
  40074d:       41 b9 01 00 00 00       mov    $0x1,%r9d # 因为是从小到达 incr 这个参数是 1
  400753:       41 b8 0c 00 00 00       mov    $0xc,%r8d # 这个参数是 end 符合上面的程序 12
  400759:       b9 00 00 00 00          mov    $0x0,%ecx # 这个参数是 start 符合上面的程序 1
  40075e:       ba 04 00 00 00          mov    $0x4,%edx # num_threads(4) 线程的个数是 4
  400763:       be 00 00 00 00          mov    $0x0,%esi # 因为上面的代码当中并没有在并行域当中使用数据 因此这个数据为 0 也就是 NULL 
  400768:       bf 88 07 40 00          mov    $0x400788,%edi # 函数指针 main._omp_fn.0
  40076d:       e8 ce fe ff ff          callq  400640 <GOMP_parallel_loop_dynamic_start@plt>
  400772:       bf 00 00 00 00          mov    $0x0,%edi
  400777:       e8 0c 00 00 00          callq  400788 <main._omp_fn.0>
  40077c:       e8 5f fe ff ff          callq  4005e0 <GOMP_parallel_end@plt>
  400781:       b8 00 00 00 00          mov    $0x0,%eax
  400786:       c9                      leaveq 
  400787:       c3                      retq
  
0000000000400788 <main._omp_fn.0>:
  400788:       55                      push   %rbp
  400789:       48 89 e5                mov    %rsp,%rbp
  40078c:       53                      push   %rbx
  40078d:       48 83 ec 38             sub    $0x38,%rsp
  400791:       48 89 7d c8             mov    %rdi,-0x38(%rbp)
  400795:       c7 45 ec 00 00 00 00    movl   $0x0,-0x14(%rbp)
  40079c:       48 8d 55 e0             lea    -0x20(%rbp),%rdx
  4007a0:       48 8d 45 d8             lea    -0x28(%rbp),%rax
  4007a4:       48 89 d6                mov    %rdx,%rsi
  4007a7:       48 89 c7                mov    %rax,%rdi
  4007aa:       e8 21 fe ff ff          callq  4005d0 <GOMP_loop_dynamic_next@plt>
  4007af:       84 c0                   test   %al,%al # 如果 GOMP_loop_dynamic_next 返回值是 0 则跳转到 4007fb 执行函数 GOMP_loop_end_nowait
  4007b1:       74 48                   je     4007fb <main._omp_fn.0+0x73>
  4007b3:       48 8b 45 d8             mov    -0x28(%rbp),%rax
  4007b7:       89 45 ec                mov    %eax,-0x14(%rbp)
  4007ba:       48 8b 45 e0             mov    -0x20(%rbp),%rax
  4007be:       89 c3                   mov    %eax,%ebx
  # ===========================下面的代码就是执行循环和 body =================
  4007c0:       e8 2b fe ff ff          callq  4005f0 <omp_get_thread_num@plt>
  4007c5:       89 c2                   mov    %eax,%edx
  4007c7:       8b 45 ec                mov    -0x14(%rbp),%eax
  4007ca:       89 c6                   mov    %eax,%esi
  4007cc:       bf 94 08 40 00          mov    $0x400894,%edi
  4007d1:       b8 00 00 00 00          mov    $0x0,%eax
  4007d6:       e8 25 fe ff ff          callq  400600 <printf@plt>
  4007db:       83 45 ec 01             addl   $0x1,-0x14(%rbp)
  4007df:       39 5d ec                cmp    %ebx,-0x14(%rbp)
  4007e2:       7c dc                   jl     4007c0 <main._omp_fn.0+0x38>
  # ======================================================================
  # ============下面的代码主要是进行 while 循环查看循环是否执行完成==============
  4007e4:       48 8d 55 e0             lea    -0x20(%rbp),%rdx
  4007e8:       48 8d 45 d8             lea    -0x28(%rbp),%rax
  4007ec:       48 89 d6                mov    %rdx,%rsi
  4007ef:       48 89 c7                mov    %rax,%rdi
  4007f2:       e8 d9 fd ff ff          callq  4005d0 <GOMP_loop_dynamic_next@plt>
  4007f7:       84 c0                   test   %al,%al
  4007f9:       75 b8                   jne    4007b3 <main._omp_fn.0+0x2b>
  # ======================================================================
  4007fb:       e8 10 fe ff ff          callq  400610 <GOMP_loop_end_nowait@plt>
  400800:       48 83 c4 38             add    $0x38,%rsp
  400804:       5b                      pop    %rbx
  400805:       5d                      pop    %rbp
  400806:       c3                      retq   
  400807:       66 0f 1f 84 00 00 00    nopw   0x0(%rax,%rax,1)
  40080e:       00 00

总结

在本篇文章当中我们主要分析了 OpenMP 当中 for 循环动态调度方式的具体实现原理,以及动态库函数的分析。整个过程主要有两大部分,一个是编译角度,编译器会将 for construct 编译成什么样子,以及动态库函数具体是如何划分迭代分块的。在迭代分块当中主要分为两种方式,当分块数目多的时候不使用 CAS 因为这样线程之间竞争比较激烈,但是当分块数目比较小的时候就使用 CAS ,这种做法可以提高程序执行的效率。

以上就是OpenMP中For Construct对dynamic的调度方式详解的详细内容,更多关于OpenMP For Construct dynamic的资料请关注我们其它相关文章!

(0)

相关推荐

  • 详解OpenMP的线程同步机制

    目录 前言 自定义线程之间的同步 barrier 定义临界区 critical 深入理解 barrier master construct single construct ordered construct OpenMP 中的线程同步机制 Sections 使用 nowait Single 使用 nowait For 使用 nowait 总结 前言 在本篇文章当中主要给大家介绍 OpenMP 当中线程的同步和互斥机制,在 OpenMP 当中主要有三种不同的线程之间的互斥方式: 使用 criti

  • OpenMP Parallel Construct的实现原理详解

    目录 Parallel 分析——编译器角度 深入剖析 Parallel 动态库函数参数传递 动态库函数分析 参数传递分析 汇编程序分析 GOMP_parallel_start 详细参数分析 动态库函数源码分析 GOMP_parallel_start 源码分析 GOMP_parallel_end 分析 总结 Parallel 分析——编译器角度 在本小节当中我们将从编译器的角度去分析该如何处理 parallel construct .首先从词法分析和语法分析的角度来说这对编译器并不难,只需要加上一

  • OpenMP 共享内存的并行编程框架入门详解

    目录 简介 认识 openmp 的简单易用性 C 语言实现 C++ 实现 OpenMP 实现 opnemp 基本原理 积分例子 总结 简介 OpenMP 一个非常易用的共享内存的并行编程框架,它提供了一些非常简单易用的API,让编程人员从复杂的并发编程当中释放出来,专注于具体功能的实现.openmp 主要是通过编译指导语句以及他的动态运行时库实现,在本篇文章当中我们主要介绍 openmp 一些入门的简单指令的使用. 认识 openmp 的简单易用性 比如现在我们有一个任务,启动四个线程打印 he

  • 安装OpenMPI来配合C语言程序进行并行计算

    安装OPENMPI 由于是实验,也不进行多机的配置了,只在虚拟机里安装吧.多个机器的配置可以参考此文 最简单的方法,apt安装 sudo apt-get install libcr-dev mpich2 mpich2-doc 测试 hello.c /* C Example */ #include <mpi.h> #include <stdio.h> int main (int argc, char* argv[]) { int rank, size; MPI_Init (&

  • 深入剖析OpenMP锁的原理与实现

    目录 前言 深入分析 omp_lock_t omp_lock_t 源码分析 深入分析 omp_nest_lock_t omp_nest_lock_t 源码分析 源代码函数名称不同的原因揭秘 总结 前言 在本篇文章当中主要给大家介绍一下 OpenMP 当中经常使用到的锁并且仔细分析它其中的内部原理!在 OpenMP 当中主要有两种类型的锁,一个是 omp_lock_t 另外一个是 omp_nest_lock_t,这两个锁的主要区别就是后者是一个可重入锁,所谓可冲入锁就是一旦一个线程已经拿到这个锁了

  • OpenMP深入剖析reduction子句教程

    目录 前言 从并发求和开始 解决求和问题的各种办法 使用数组巧妙解决并发程序当中的数据竞争问题 reduction 子句 深入剖析 reduction 子句 加法+操作符 乘法*操作符 逻辑与&&操作符 或||操作符 MIN 最小值 MAX 最大值 & 按位与 |按位或 ^按位异或 总结 前言 在前面的教程OpenMP入门当中我们简要介绍了 OpenMP 的一些基础的使用方法,在本篇文章当中我们将从一些基础的问题开始,然后仔细介绍在 OpenMP 当中 reduction 子句的各

  • OpenMP中For Construct对dynamic的调度方式详解

    目录 前言 前置知识 dynamic 调度方式分析 实例分析 总结 前言 在本篇文章当中主要给大家介绍 OpenMp for construct 的实现原理,以及与他相关的动态库函数分析,与 for construct 非常相关的是循环的调度方式,在 OpenMP 当中一共有四种调调方式,auto, dynamic, guided, runtime, 在本篇文章当中主要是对 dynamic 的调度方式进行分析. 前置知识 在介绍 for construct 的实现原理之前,我们首先需要了解一下编

  • c# dynamic的使用详解

    dynamic是FrameWork4.0的新特性.dynamic的出现让C#具有了弱语言类型的特性.编译器在编译的时候不再对类型进行检查,编译期默认dynamic对象支持你想要的任何特性.比如,即使你对GetDynamicObject方法返回的对象一无所知,你也可以像如下那样进行代码的调用,编译器不会报错: dynamic dynamicObject = GetDynamicObject(); Console.WriteLine(dynamicObject.Name); Console.Writ

  • Spring-IOC容器中的常用注解与使用方法详解

    Spring是什么? Spring是一个轻量级Java开发框架,最早有Rod Johnson创建,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题.它是一个分层的JavaSE/JavaEE full-stack(一站式)轻量级开源框架,为开发Java应用程序提供全面的基础架构支持.Spring负责基础架构,因此Java开发者可以专注于应用程序的开发. 体系结构 核心容器(Core Container):Spring的核心容器是其他模块建立的基础,有Spring-core.Spring

  • C++ 中引用与指针的区别实例详解

    C++ 中引用与指针的区别实例详解 引用是从C++才引入的,在C中不存在.为了搞清楚引用的概念,得先搞明白变量的定义及引用与变量的区别,变量的要素一共有两个:名称与空间. 引用不是变量,它仅仅是变量的别名,没有自己独立的空间,它只符合变量的"名称"这个要素,而"空间"这个要素并不满足.换句话说,引用需要与它所引用的变量共享同一个内存空间,对引用所做的改变实际上是对所引用的变量做出修改.并且引用在定义的时候就必须被初始化.     参数传递的类型及相关要点: 1 按值

  • SQLserver中cube:多维数据集实例详解

    1.cube:生成多维数据集,包含各维度可能组合的交叉表格,使用with 关键字连接 with cube 根据需要使用union all 拼接 判断 某一列的null值来自源数据还是 cube 使用GROUPING关键字 GROUPING([档案号]) = 1 : null值来自cube(代表所有的档案号) GROUPING([档案号]) = 0 : null值来自源数据 举例: SELECT * INTO ##GET FROM (SELECT * FROM ( SELECT CASE WHEN

  • Angular中$cacheFactory的作用和用法实例详解

    先说下缓存: 一个缓存就是一个组件,它可以透明地储存数据,以便以后可以更快地服务于请求.多次重复地获取资源可能会导致数据重复,消耗时间.因此缓存适用于变化性不大的一些数据,缓存能够服务的请求越多,整体系统性能就能提升越多. $cacheFactory介绍: $cacheFactory是一个为Angular服务生产缓存对象的服务.要创建一个缓存对象,可以使用$cacheFactory通过一个ID和capacity.其中,ID是一个缓存对象的名称,capacity则是描述缓存键值对的最大数量. 1.

  • C++中new和delete的使用方法详解

    C++中new和delete的使用方法详解 new和delete运算符用于动态分配和撤销内存的运算符 new用法:           1.     开辟单变量地址空间 1)new int;  //开辟一个存放数组的存储空间,返回一个指向该存储空间的地址.int *a = new int 即为将一个int类型的地址赋值给整型指针a. 2)int *a = new int(5) 作用同上,但是同时将整数赋值为5           2.     开辟数组空间 一维: int *a = new in

  • C#中WPF ListView绑定数据的实例详解

    C#中WPF ListView绑定数据的实例详解 WPF中ListView用来显示数据十分方便, 我们可以将它分成几个列,每一个列用来显示一条数据,但是又是在一方之中. 那么怎样实现这样的效果的呢,这就要用绑定了. 我们先来看一看他的xmal代码 <ListView Name="receiveList" Grid.Row="0"> <ListView.View> <GridView> <GridView.Columns>

  • C++中构造函数的参数缺省的详解

    C++中构造函数的参数缺省的详解 前言: 构造函数中参数的值既可以通过实参传递,也可以指定为某些默认值,即如果用户不指定实参值,编译系统就使形参取默认值.在构造函数中也可以采用这样的方法来实现初始化. #include <iostream> using namespace std; class A { public : A(int aa=0,int bb=00); //在声明构造函数时指定默认参数 int volume( ); int a; int b; }; int main( ) { A

随机推荐