Windows系统怎样开启多核处理器

Windows系统怎样开启多核处理器,第1张

动态变量和静态变量的区别:

1、存储位置

动态变量:存储在内存出栈数据区

静态变量:存储在全局数据区(静态数据区)

2、生命期

动态变量:根据你定义的位置确定,比如你在一个函数中定义的,那么超出该函数范围变量将失效

静态变量:程序结束时才释放

3、作用域

动态变量:同样的要根据你定义的位置才能确定,和第二点的一样

静态变量:当前文件中有效

堆和栈的区分:

分配方式:堆都是动态分配的,没有静态分配的堆。

栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca() ,alloca的函数返回的时候, 它分配的内存会自动释放,但是栈的动态分配和堆是不同的,堆需要手动释放。

堆是通过new,malloc, calloc, realloc等来分配的内存,需要手动析构,栈由系统管理,无需人为处理。

与单核处理器相比,多核处理器在体系结构、软件、功耗和安全性设计等方面面临着巨大的挑战,但也蕴含着巨大的潜能。 CMP和SMT一样,致力于发掘计算的粗粒度并行性。CMP可以看做是随着大规模集成电路技术的发展,在芯片容量足够大时,就可以将大规模并行处理机结构中的SMP(对称多处理机)或DSM(分布共享处理机)节点集成到同一芯片内,各个处理器并行执行不同的线程或进程。在基于SMP结构的单芯片多处理机中,处理器之间通过片外Cache或者是片外的共享存储器来进行通信。而基于DSM结构的单芯片多处理器中,处理器间通过连接分布式存储器的片内高速交叉开关网络进行通信。由于SMP和DSM已经是非常成熟的技术了,CMP结构设计比较容易,只是后端设计和芯片制造工艺的要求较高而已。正因为这样,CMP成为了最先被应用于商用CPU的“未来”高性能处理器结构。

虽然多核能利用集成度提高带来的诸多好处,让芯片的性能成倍地增加,但很明显的是原来系统级的一些问题便引入到了处理器内部。

1 核结构研究: 同构还是异构

CMP的构成分成同构和异构两类,同构是指内部核的结构是相同的,而异构是指内部的核结构是不同的。为此,面对不同的应用研究核结构的实现对未来微处理器的性能至关重要。核本身的结构,关系到整个芯片的面积、功耗和性能。怎样继承和发展传统处理器的成果,直接影响多核的性能和实现周期。同时,根据Amdahl定理,程序的加速比决定于串行部分的性能,所以,从理论上来看似乎异构微处理器的结构具有更好的性能。

核所用的指令系统对系统的实现也是很重要的,采用多核之间采用相同的指令系统还是不同的指令系统,能否运行 *** 作系统等,也将是研究的内容之一。

2 程序执行模型

多核处理器设计的首要问题是选择程序执行模型。程序执行模型的适用性决定多核处理器能否以最低的代价提供最高的性能。程序执行模型是编译器设计人员与系统实现人员之间的接口。编译器设计人员决定如何将一种高级语言程序按一种程序执行模型转换成一种目标机器语言程序; 系统实现人员则决定该程序执行模型在具体目标机器上的有效实现。当目标机器是多核体系结构时,产生的问题是: 多核体系结构如何支持重要的程序执行模型?是否有其他的程序执行模型更适于多核的体系结构?这些程序执行模型能多大程度上满足应用的需要并为用户所接受?

3 Cache设计: 多级Cache设计与一致性问题

处理器和主存间的速度差距对CMP来说是个突出的矛盾,因此必须使用多级Cache来缓解。目前有共享一级Cache的CMP、共享二级Cache的CMP以及共享主存的CMP。通常,CMP采用共享二级Cache的CMP结构,即每个处理器核心拥有私有的一级Cache,且所有处理器核心共享二级Cache。

Cache自身的体系结构设计也直接关系到系统整体性能。但是在CMP结构中,共享Cache或独有Cache孰优孰劣、需不需要在一块芯片上建立多级Cache,以及建立几级Cache等等,由于对整个芯片的尺寸、功耗、布局、性能以及运行效率等都有很大的影响,因而这些都是需要认真研究和探讨的问题。

另一方面,多级Cache又引发一致性问题。采用何种Cache一致性模型和机制都将对CMP整体性能产生重要影响。在传统多处理器系统结构中广泛采用的Cache一致性模型有: 顺序一致性模型、弱一致性模型、释放一致性模型等。与之相关的Cache一致性机制主要有总线的侦听协议和基于目录的目录协议。目前的CMP系统大多采用基于总线的侦听协议。

4 核间通信技术

CMP处理器的各CPU核心执行的程序之间有时需要进行数据共享与同步,因此其硬件结构必须支持核间通信。高效的通信机制是CMP处理器高性能的重要保障,目前比较主流的片上高效通信机制有两种,一种是基于总线共享的Cache结构,一种是基于片上的互连结构。

总线共享Cache结构是指每个CPU内核拥有共享的二级或三级Cache,用于保存比较常用的数据,并通过连接核心的总线进行通信。这种系统的优点是结构简单,通信速度高,缺点是基于总线的结构可扩展性较差。

基于片上互连的结构是指每个CPU核心具有独立的处理单元和Cache,各个CPU核心通过交叉开关或片上网络等方式连接在一起。各个CPU核心间通过消息通信。这种结构的优点是可扩展性好,数据带宽有保证; 缺点是硬件结构复杂,且软件改动较大。

也许这两者的竞争结果不是互相取代而是互相合作,例如在全局范围采用片上网络而局部采用总线方式,来达到性能与复杂性的平衡。

5 总线设计

传统微处理器中,Cache不命中或访存事件都会对CPU的执行效率产生负面影响,而总线接口单元(BIU)的工作效率会决定此影响的程度。当多个CPU核心同时要求访问内存或多个CPU核心内私有Cache同时出现Cache不命中事件时,BIU对这多个访问请求的仲裁机制以及对外存储访问的转换机制的效率决定了CMP系统的整体性能。因此寻找高效的多端口总线接口单元(BIU)结构,将多核心对主存的单字访问转为更为高效的猝发(burst)访问; 同时寻找对CMP处理器整体效率最佳的一次Burst访问字的数量模型以及高效多端口BIU访问的仲裁机制将是CMP处理器研究的重要内容。

6 *** 作系统设计: 任务调度、中断处理、同步互斥

对于多核CPU,优化 *** 作系统任务调度算法是保证效率的关键。一般任务调度算法有全局队列调度和局部队列调度。前者是指 *** 作系统维护一个全局的任务等待队列,当系统中有一个CPU核心空闲时, *** 作系统就从全局任务等待队列中选取就绪任务开始在此核心上执行。这种方法的优点是CPU核心利用率较高。后者是指 *** 作系统为每个CPU内核维护一个局部的任务等待队列,当系统中有一个CPU内核空闲时,便从该核心的任务等待队列中选取恰当的任务执行,这种方法的优点是任务基本上无需在多个CPU核心间切换,有利于提高CPU核心局部Cache命中率。目前多数多核CPU *** 作系统采用的是基于全局队列的任务调度算法。

多核的中断处理和单核有很大不同。多核的各处理器之间需要通过中断方式进行通信,所以多个处理器之间的本地中断控制器和负责仲裁各核之间中断分配的全局中断控制器也需要封装在芯片内部。

另外,多核CPU是一个多任务系统。由于不同任务会竞争共享资源,因此需要系统提供同步与互斥机制。而传统的用于单核的解决机制并不能满足多核,需要利用硬件提供的“读-修改-写”的原子 *** 作或其他同步互斥机制来保证。

7 低功耗设计

半导体工艺的迅速发展使微处理器的集成度越来越高,同时处理器表面温度也变得越来越高并呈指数级增长,每三年处理器的功耗密度就能翻一番。目前,低功耗和热优化设计已经成为微处理器研究中的核心问题。CMP的多核心结构决定了其相关的功耗研究是一个至关重要的课题。

低功耗设计是一个多层次问题,需要同时在 *** 作系统级、算法级、结构级、电路级等多个层次上进行研究。每个层次的低功耗设计方法实现的效果不同――抽象层次越高,功耗和温度降低的效果越明显。

8 存储器墙

为了使芯片内核充分地工作,最起码的要求是芯片能提供与芯片性能相匹配的存储器带宽,虽然内部Cache的容量能解决一些问题,但随着性能的进一步提高,必须有其他一些手段来提高存储器接口的带宽,如增加单个管脚带宽的DDR、DDR2、QDR、XDR等。同样,系统也必须有能提供高带宽的存储器。所以,芯片对封装的要求也越来越高,虽然封装的管脚数每年以20%的数目提升,但还不能完全解决问题,而且还带来了成本提高的问题,为此,怎样提供一个高带宽,低延迟的接口带宽,是必须解决的一个重要问题。

9 可靠性及安全性设计

随着技术革新的发展,处理器的应用渗透到现代社会的各个层面,但是在安全性方面却存在着很大的隐患。一方面,处理器结构自身的可靠性低下,由于超微细化与时钟设计的高速化、低电源电压化,设计上的安全系数越来越难以保证,故障的发生率逐渐走高。另一方面,来自第三方的恶意攻击越来越多,手段越来越先进,已成为具有普遍性的社会问题。现在,可靠性与安全性的提高在计算机体系结构研究领域备受注目。

今后,CMP这类处理器芯片内有多个进程同时执行的结构将成为主流,再加上硬件复杂性、设计时的失误增加,使得处理器芯片内部也未必是安全的,因此,安全与可靠性设计任重而道远。

当CPU 进入多核时代之后 软件的性能调优就不再是一件简单的事情 没有并行化的程序在新的硬件上可能会运行得比从前更慢 当 CPU 数目增加的时候 芯片制造商为了取得最佳的性能/功耗比 降低 CPU 的运行频率是一件非常明智的事情 相比 C/C++ 程序员而言 利用 Java 编写多线程应用已经简单了很多 然而 多线程程序想要达到高性能仍然不是一件容易的事情 对于软件开发人员而言 如果在测试时发现并行程序并不比串行程序快 那不是一件值得惊讶的事情 毕竟 在多核时代之前 受到广泛认可的并行软件开发准则通常过于简单和武断

在本文中 我们将介绍使提高Java 多线程应用性能的一般步骤 通过运用本文提供的一些简单规则 我们就能获得具有高性能的可扩展的应用程序

为什么性能没有增长?

多核能带来性能的大幅增长 这很容易通过简单的一些测试来观察到 如果我们写一个多线程程序 并在每个线程中对一个本地变量进行累加 我们可以很容易的看到多核和并行带来的成倍的性能提升 这非常容易做到 不是吗?在 参考资源 里我们给出了一个例子 然而 与我们的测试相反 我们很少在实际软件应用中看到这样完美的可扩展性 阻碍我们获得完美的可扩展性有两方面的因素存在 首先 我们面临着理论上的限制 其次软件开发过程中也经常出现实现上的问题 让我们看看 图 中的三条性能曲线

图 性能曲线

作为追求完美的软件工程师 我们希望看到随着线程数目的增长程序的性能获得线性的增长 也就是图 中的蓝色直线 而我们最不希望看到的是绿色的曲线 不管投入多少新的 CPU 性能也没有丝毫增长 (随着 CPU 增长而性能下降的曲线在实际项目中也存在) 而图中的红色线条则说明通常的 法则并不适用于可扩展性方面 假设程序中有 % 的计算只能串行进行 那么其扩展性曲线如红线所示 由图可见 当 % 的代码可以完美的并行时 在 个 CPU 存在的情况下 我们也只能获得大约 倍的性能 如果任务中具有无法并行的部分 那么在现实世界 我们的性能曲线大致上会位于图 中的灰 域

在这篇文章中 我们不会试图挑战理论极限 我们希望能解释一个 Java 程序员如何能够尽可能的接近极限 这已经不是一个容易的任务

是什么造成了糟糕的可扩展性?

可扩展性糟糕的原因有很多 其中最为显著的是锁的滥用 这没有办法 我们就是这样被教育的 想要多线程安全吗?那就加一个锁吧 想想 Python 中臭名昭著的 Global Intepreter Lock 还有 Java 中的 Collections synchronizedXXXX() 系列方法 跟随巨人的做法有什么不好吗?是的 用锁来保护关键区域非常方便 也较容易保证正确性 然而锁也意味着只有一个进程能进入关键区域 而其他的进程都在等待!如果观察到 CPU 空闲而软件执行缓慢 那么检察一下锁的使用是一个明智的做法

对于 Java 程序而言 Performance Inspector 中的 Java Lock Monitor 是一个不错的开源工具

[NextPage]

对一个多线程应用进行调优

下面 我们将提供一个例子程序并演示如何在多核平台上获得更好的可扩展性 这个例子程序演示了一个假想的日志服务器 它接收来自多个源的日志信息并将其统一保存到文件系统中 为了简单起见 我们的例子代码中不包含任何的网络相关代码 Main() 函数将启动多个线程来发送日志信息到日志服务器中 对于性急的读者 让我们先看看调优的结果

图 日至服务器调优结果

在上图中 蓝色的曲线是一个基于 Lock 的老式日志服务器 而绿色的曲线是我们进行了性能调优之后的日志服务器 可以看到 LogServerBad 的性能随线程数目的增加变化很小 而 LogServerGood 的性能则随着线程数目的增加而线性增长 如果不介意使用第三方的库的话 那么来自 Project KunMing 的 LockFreeQueue 可以进一步提供更好的可扩展性

图 使用 Lock free 的数据结构

在上图中 第三条曲线表示用 LockFreeQueue 替换标准库中的 ConcurrentLinkedQueue 之后的性能曲线 可以看到 如果线程数目较少时 两条曲线差别不大 但是单线程数目增大到一定程度之后 Lock Free 的数据结构具有明显的优势

在下文中 将介绍在上述例子中使用的可以帮助我们创建高可扩展 Java 应用的工具和技巧

[NextPage]

使用 JLM 分析应用程序

JLM 提供了 Java 应用和 JVM 中锁持有时间和冲突统计 具体提供以下功能

对冲突的锁进行计数

成功获得锁的次数

递归锁的次数

申请锁的线程被阻塞等待的次数

锁被持有的累计时间 对于支持 Tier Spin Locking 的平台 还可以获得以下信息 :

请求线程在内层(spin loop)请求锁的次数

请求线程在外层(thread yield loop)请求锁的次数

使用 rtdriver 工具收集更详细的信息

jlmlitestart 仅收集计数器

jlmstart 仅收集计数器和持有时间统计

jlmstop 停止数据收集

jlmdump 打印数据收集并继续收集过程

从锁持有时间中去除垃圾收集(Garbage Collection GC)的时间

GC 时间从 GC 周期中所有被持有的锁的持有时间中去除

使用 AtomicInteger 进行计数

通常 在我们实现多线程使用的计数器或随机数生成器时 会使用锁来保护共享变量 这样做的弊端是如果锁竞争的太厉害 会损害吞吐量 因为竞争的同步非常昂贵

volatile 变量虽然可以使用比同步更低的成本存储共享变量 但它只可以保证其他线程能够立即看到对 volatile 变量的写入 无法保证读 修改 写的原子性 因此 volatile 变量无法用来实现正确的计数器和随机数生成器

从 JDK 开始 ncurrent atomic 包中引入了原子变量 包括 AtomicInteger AtomicLong AtomicBoolean 以及数组 AtomicIntergerArray AtomicLongArray 原子变量保证了 ++ —— += = 等 *** 作的原子性 利用这些数据结构 您可以实现更高效的计数器和随机数生成器

加入轻量级的线程池—— Executor

大多数并发应用程序是以执行任务(task)为基本单位进行管理的 通常情况下 我们会为每个任务单独创建一个线程来执行 这样会带来两个问题 一 大量的线程(> )会消耗系统资源 使线程调度的开销变大 引起性能下降 二 对于生命周期短暂的任务 频繁地创建和消亡线程并不是明智的选择 因为创建和消亡线程的开销可能会大于使用多线程带来的性能好处

一种更加合理的使用多线程的方法是使用线程池(Thread Pool) ncurrent 提供了一个灵活的线程池实现 Executor 框架 这个框架可以用于异步任务执行 而且支持很多不同类型的任务执行策略 它还为任务提交和任务执行之间的解耦提供了标准的方法 为使用 Runnable 描述任务提供了通用的方式 Executor 的实现还提供了对生命周期的支持和 hook 函数 可以添加如统计收集 应用程序管理机制和监视器等扩展

在线程池中执行任务线程 可以重用已存在的线程 免除创建新的线程 这样可以在处理多个任务时减少线程创建 消亡的开销 同时 在任务到达时 工作线程通常已经存在 用于创建线程的等待时间不会延迟任务的执行 因此提高了响应性 通过适当的调整线程池的大小 在得到足够多的线程以保持处理器忙碌的同时 还可以防止过多的线程相互竞争资源 导致应用程序在线程管理上耗费过多的资源

Executor 默认提供了一些有用的预设线程池 可以通过调用 Executors 的静态工厂方法来创建

newFixedThreadPool 提供一个具有最大线程个数限制的线程池 newCachedThreadPool 提供一个没有最大线程个数限制的线程池 newSingleThreadExecutor 提供一个单线程的线程池 保证任务按照任务队列说规定的顺序(FIFO LIFO 优先级)执行 newScheduledThreadPool 提供一个具有最大线程个数限制线程池 并支持定时以及周期性的任务执行

使用并发数据结构

Collection 框架曾为 Java 程序员带来了很多方便 但在多核时代 Collection 框架变得有些不大适应 多线程之间的共享数据总是存放在数据结构之中 如 Map Stack Queue List Set 等 Collection 框架中的这些数据结构在默认情况下并不是多线程安全的 也就是说这些数据结构并不能安全地被多个线程同时访问 JDK 通过提供 SynchronizedCollection 为这些类提供一层线程安全的接口 它是用 synchronized 关键字实现的 相当于为整个数据结构加上一把全局锁保证线程安全

ncurrent 中提供了更加高效 collection 如 ConcurrentHashMap/Set ConcurrentLinkedQueue ConcurrentSkipListMap/Set CopyOnWriteArrayList/Set 这些数据结构是为多线程并发访问而设计的 使用了细粒度的锁和新的 Lock free 算法 除了在多线程条件下具有更高的性能 还提供了如 put if absent 这样适合并发应用的原子函数

[NextPage]

其他一些需要考虑的因素

不要给内存系统太大的压力

如果线程执行过程中需要分配内存 这在 Java 中通常不会造成问题 现代的 JVM 是高度优化的 它通常为每个线程保留一块 Buffer 这样在分配内存时 只要 buffer 没有用光 那么就不需要和全局的堆打交道 而本地 buffer 分配完毕之后 JVM 将不得不到全局堆中分配内存 这样通常会带来严重的可扩展性的降低 另外 给 GC 带来的压力也会进一步降低程序的可扩展性 尽管我们有并行的 GC 但其可扩展性通常并不理想 如果一个循环执行的程序在每次执行中都需要分配临时对象 那么我们可以考虑利用 ThreadLocal 和 SoftReference 这样的技术来减少内存的分配

使用 ThreadLocal

ThreadLocal 类能够被用来保存线程私有的状态信息 对于某些应用非常方便 通常来讲 它对可扩展性有正面的影响 它能为各个线程提供一个线程私有的变量 因而多个线程之间无须同步 需要注意的是在 JDK 之前 ThreadLocal 有着相当低效的实现 如果需要在 JDK 或更老的版本上使用 ThreadLocal 需要慎重评估其对性能的影响 类似的 目前 JDK 中的 ReentrantReadWriteLock 的实现也相当低效 如果想利用读锁之间不互斥的特性来提高可扩展性 同样需要进行 profile 来确认其适用程度

锁的粒度很重要

粗粒度的全局锁在保证线程安全的同时 也会损害应用的性能 仔细考虑锁的粒度在构建高可扩展 Java 应用时非常重要 当 CPU 个数和线程数较少时 全局锁并不会引起激烈的竞争 因此获得一个锁的代价很小(JVM 对这种情况进行了优化) 随着 CPU 个数和线程数增多 对全局锁的竞争越来越激烈 除了一个获得锁的 CPU 可以继续工作外 其他试图获得该锁的 CPU 都只能闲置等待 导致整个系统的 CPU 利用率过低 系统性能不能得到充分利用 当我们遇到一个竞争激烈的全局锁时 可以尝试将锁划分为多个细粒度锁 每一个细粒度锁保护一部分共享资源 通过减小锁的粒度 可以降低该锁的竞争程度 ncurrent ConcurrentHashMap 就通过使用细粒度锁 提高 HashMap 在多线程应用中的性能 在 ConcurrentHashMap 中 默认构造函数使用 个锁保护整个 Hash Map 用户可以通过参数设定使用上千个锁 这样相当于将整个 Hash Map 划分为上千个碎片 每个碎片使用一个锁进行保护

结论

通过选择一种合适的 profile 工具 检查 profile 结果中的热点区域 使用适合多线程访问的数据结构 线程池 细粒度锁减小热点区域 并重复此过程不断提高应用的可扩展性

lishixinzhi/Article/program/Java/gj/201311/27639

以上就是关于Windows系统怎样开启多核处理器全部的内容,包括:Windows系统怎样开启多核处理器、【多核处理器的九大关键技术】多核的关键技术、使用Java构造高可扩展应用等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zz/10105198.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-05
下一篇 2023-05-05

发表评论

登录后才能评论

评论列表(0条)

保存