linux power

介绍

在现代机器上,应用程序性能评估可能是一项复杂的任务。 通用工具几乎无法处理所有性能变量。 每个工作负载所强调的是不同的计算机子系统。 测量和调整CPU绑定程序与调整IO绑定或内存绑定程序有很大不同。 在本文中,我们重点介绍编译语言环境(C,C ++和其他语言)中与CPU绑定和与内存绑定的程序。 我们演示如何:

  • 查找程序热点 (程序中执行的指令所占比例很高的区域,功能,方法)
  • 使用处理器上可用的硬件性能计数器来测量程序在POWER7上的行为
  • 确定用于Linux上性能评估的工具。

POWER7的CPI模型

了解应用程序性能分析始于CPI指标的讨论。 每条指令的周期数(CPI)度量是完成一条指令所需的处理器周期数。 每条指令被分解为多个阶段:经典的RISC管道将具有指令获取阶段,随后是指令解码/寄存器获取,执行,可选的内存访问以及最后的回写。 CPU可以通过利用指令级并行性来提高其CPI度量(以较低的CPI值衡量):每个阶段将在不同阶段处理不同的指令。 优化时,请尝试最小化CPI值以最大化系统利用率。 图1显示了流水线​​处理器中的最佳指令流。

图1.流水线处理器的最佳指令流

有时一个阶段不能完全独立于其他阶段,或者它发出具有依赖性的指令,迫使处理器在继续执行之前满足该需求。 例如,紧随算术指令的存储器负载使处理器首先仅将数据提取到高速缓存或存储器中,然后发出算术指令。 发生这种情况时,据说处理器管道会遇到停顿,从而使管道停顿。 图2显示了停滞的管道可能是什么样子。

图2.带有停顿的流水线处理器

在图1和图2的示例中,请考虑在完全填充的流水线(每个周期完成一条指令)的11个操作周期内,处理器可以执行8条指令。 但是,当发生三个周期的停顿时,只有五个指令以相同的周期数执行。 性能损失约为40%。 根据算法的不同,某些停顿是不可避免的。 但是,仔细的分析可以提供有关如何重写或调整某些代码段以避免此类停顿的提示和建议。 在文章“现代微处理器-90分钟指南”中找到了关于现代CPU流水线和指令级并行性的更完整和更生动的解释(请参阅参考资料 )。

CPI细分模型(CBM)将功能处理器级与性能计数器相关联,以显示哪个CPU功能单元正在产生停顿。 CBM取决于CPU架构和处理器模型。 电源架构和英特尔架构具有完全不同的CBM。 尽管POWER5 CBM相似,但与POWER7 CBM不同。 图3显示了POWER7 CBM的一部分。 (请参阅此信息的文本版本 。)

图3.部分POWER 7 CBM

在Power Architecture中,硬件性能计数器是一组专用寄存器,当处理器中发生特定事件时,其内容将更新。 POWER7处理器具有一个内置的性能监视单元(PMU),每个PMU具有六个线程级性能计数器监视程序(PCM)。 其中四个是可编程的,这意味着可以同时监视四个事件,并且可能有500多个性能事件。 POWER7性能计数器由组定义,并且PMU一次只能监视同一组的事件。 图3显示了用于定义POWER7 CBM的性能计数器的子集。 图3中的计数器后面有一个配置文件,用于指示哪个CPU功能单元导致处理器停顿,并提供有关如何调整算法以消除它们的可能提示。

在图3中 ,白框是在概要文件中监视的特定POWER7 PCM。 根据它们的值,计算出灰色框[每个框都标有星号(*)](这些指标没有特定的硬件计数器)。

注:请在纸上,“综合PMU事件参考POWER7”(见POWER7一个全面的PMU参考相关主题 )。

Linux上的工具

如何使用POWER7处理器中的PCM? 尽管您可以在POWER上使用各种分析方法,例如硬件中断,代码检测(例如gprof),操作系统挂钩(systemtap); PCM提供了一组广泛的计数器,这些计数器可直接与处理器功能一起使用。 PCM探查器使用操作系统中断以固定的时间间隔不断采样处理器寄存器的值。 尽管样本分析可能导致比指令跟踪结果更不精确的数字结果,但是它对整体系统性能的影响较小,并允许目标基准几乎以全速运行。 结果数据不准确; 它是误差容限的近似值。

对于PCM剖析Linux上的两个最常用的工具是OProfileperf (参见相关主题 )。 尽管两者都使用相同的原理,并沿着工作负载的回溯不断采样特殊的硬件寄存器(通过syscall),但它们的配置和使用方式却不同。

OProfile工具是适用于Linux系统的系统范围的探查器,能够以低开销分析所有正在运行的代码。 它由一个用于收集示例数据的内核驱动程序和守护程序以及一些用于将数据转换为信息的后概要分析工具组成。 除非您需要带注释的源,否则不需要调试符号(gcc的-g选项)。 使用最新的Linux 2.6内核, OProfile可以提供gprof样式的调用图分析信息。 OProfile的典型开销为1-8%,具体取决于采样频率和工作负载。

在POWER上, OProfile通过观察性能硬件计数器和性能计数器的组来工作,尽管不同的组不能一起使用。 这意味着要从同一工作负载获取不同的性能计数器,需要使用不同的OProfile事件配置多次运行它。 这也意味着您不能同时观看整个POWER7 CBM。 可用的组在前面提到的“ POWER7 PMY详细事件描述”文档中定义,或者通过运行清单1中的命令来定义:

清单1. OProfile组清单
# opcontrol -l

清单2中的命令演示了一个简单的OProfile配置和调用:

清单2. OProfile POWER7 CPU周期配置
# opcontrol -l
# opcontrol -–no-vmlinux
# opcontrol -e PM_CYC_GRP1:500000 -e PM_INST_CMPL_GRP1:500000 -e PM_RUN_CYC_GRP1:500000
-e PM_RUN_INST_CMPL_GRP1:500000
# opcontrol --start

运行清单3中的工作负载。

清单3. OProfile运行命令序列
# opcontrol --dump
# opcontrol –-stop
# opcontrol --shutdown

要获取性能计数器报告,请发出清单4中的命令:

清单4. OProfile报告生成
# opreport -l > workload_report

注:请综合指南OProfile developerWorks文章“识别性能瓶颈的OProfile适用于Linux on POWER”(见(虽然没有更新的POWER7) 相关主题 )。

在Linux内核2.6.29中引入的perf工具可以分析硬件和软件级别的性能事件。 该perf工具具有面向像OProfile的被程序导向的优势,而不是系统。 它具有一些预设的性能计数器列表,例如“ cpu-cycles OR cycle”,“ branch-misses”或“ L1-icache-prefetch-misses”,并且能够复用PMU组以允许收集多个性能计数器来自不同组的样本,但同时会降低样本精度。

一个缺点是,尽管它允许直接收集硬件性能计数器,但perf不能识别POWER7 CBM表示的计数器名称。 它需要使用原始十六进制数字代替。 表1是OProfile事件到十六进制数字的映射,您可以将其与perf使用(使用记录原始事件选项)以将CBM用于POWER7。

表1. POWER7性能事件原始代码
计数器 原始码
PM_RUN_CYC 200f4
PM_CMPLU_STALL 4000A
PM_CMPLU_STALL_FXU 20014
PM_CMPLU_STALL_DIV 40014
PM_CMPLU_STALL_SCALAR 40012
PM_CMPLU_STALL_SCALAR_LONG 20018
PM_CMPLU_STALL_VECTOR 2001年
PM_CMPLU_STALL_VECTOR_LONG 4004a
PM_CMPLU_STALL_LSU 20012
PM_CMPLU_STALL_REJECT 40016
PM_CMPLU_STALL_ERAT_MISS 40018
PM_CMPLU_STALL_DCACHE_MISS 20016
PM_CMPLU_STALL_STORE 2004年
PM_CMPLU_STALL_THRD 1001c
PM_CMPLU_STALL_IFU 4004c
PM_CMPLU_STALL_BRU 4004e
PM_GCT_NOSLOT_CYC 100f8
PM_GCT_NOSLOT_IC_MISS 2001年
PM_GCT_NOSLOT_BR_MPRED 4001a
PM_GCT_NOSLOT_BR_MPRED_IC_MISS 4001c
PM_GRP_CMPL 30004
PM_1PLUS_PPC_CMPL 100f2

注意 :在IBM Wiki“在POWER7系统上使用perf”中找到有关perf的全面指南(尽管POWER7尚未更新)(请参阅参考资料 )。

你可以得到与使用的原料代码perf对应于中定义的POWER7事件OProfilelibpfm4项目(参见相关主题 ):它们在特定的POWER7头(LIB /事件/ power7_events.h)定义。 示例程序examples / showevtinfo还显示了事件名称和相应的原始十六进制代码。

为了获得计数器信息,分析是一种常见的方法。 通过概要分析,开发人员可以识别代码执行和数据访问中的热点,找到性能敏感的区域,了解内存访问模式等。 在开始介绍之前,有必要制定一项绩效评估策略。 该程序可能由各种模块和/或动态共享对象(DSO)组成,可能会大量使用内核,它可能更多地取决于数据模式访问(对L2或L3高速缓存访​​问的压力很大),或者可能专注于向量操作单位。 下一节将重点介绍可能的绩效评估策略。

绩效评估策略

初步的性能评估是通过检查CPU周期利用率计数器来找到程序热点 。 要在POWER7上执行此操作,请观看表2中列出的事件:

表2. POWER7 CPU周期利用率计数器
计数器 描述
PM_CYC 处理器周期
PM_INST_CMPL 完成的PowerPC指令数
PM_RUN_CYC 由运行锁存器控制的处理器周期。 操作系统使用运行锁存器指示何时进行有用的工作。 运行锁存器通常在OS空闲循环中清除。 通过运行锁存器进行选通可以滤除空闲环路。
PM_RUN_INST_CMPL 完成的运行指令数

运行带有这些事件的OProfile将显示处理器花费在符号上的总时间。 下面是一个例子轮廓输出用于从与IBM高级工具链5.0功率(见编译SPECcpu2006基准套件的403.gcc部件相关主题 )。 以下是命令opreport -l的输出。

清单5. 403.gcc基准测试的“ opreport-”输出(计数器PM_CYC_GRP1和PM_INST_CMPL_GRP1)
CPU: ppc64 POWER7, speed 3550 MHz (estimated)
Counted PM_CYC_GRP1 events ((Group 1 pm_utilization) Processor Cycles) with a unit
mask of 0x00 (No unit mask) count 500000
Counted PM_INST_CMPL_GRP1 events ((Group 1 pm_utilization) Number of PowerPC
Instructions that completed.) with a unit mask of 0x00 (No unit mask) count 500000 samples  %        samples  %        image name      app name        symbol name
204528    7.9112  32132     1.3848  gcc_base.none   gcc_base.none   reg_is_remote_cons\tant_p.isra.3.part.4
125218    4.8434  246710   10.6324  gcc_base.none   gcc_base.none   bitmap_operation
113190    4.3782  50950     2.1958  libc-2.13.so    libc-2.13.so    memset
90316     3.4934  22193     0.9564  gcc_base.none   gcc_base.none   compute_transp
89978     3.4804  11753     0.5065  vmlinux         vmlinux         .pseries_dedicated_\idle_sleep
88429     3.4204  130166    5.6097  gcc_base.none   gcc_base.none   bitmap_element_\allocate
67720     2.6194  41479     1.7876  gcc_base.none   gcc_base.none   ggc_set_mark
56613     2.1898  89418     3.8536  gcc_base.none   gcc_base.none   canon_rtx
53949     2.0868  6985      0.3010  gcc_base.none   gcc_base.none   delete_null_\pointer_checks
51587     1.9954  26000     1.1205  gcc_base.none   gcc_base.none   ggc_mark_rtx_\children_1
48050     1.8586  16086     0.6933  gcc_base.none   gcc_base.none   single_set_2
47115     1.8224  33772     1.4555  gcc_base.none   gcc_base.none   note_stores
清单6. 403.gcc基准测试的“ opreport-”输出(计数器PM_RUN_CYC_GRP1和PM_RUN_INST_CMPL_GRP1)
Counted PM_RUN_CYC_GRP1 events ((Group 1 pm_utilization) Processor Cycles gated by the
run latch.  Operating systems use the run latch to indicate when they are doing useful
work.  The run
latch is typically cleared in the OS idle loop.  Gating by the run latch filters out
the idle loop.) with a unit mask of 0x00 (No unit mask) count 500000
Counted PM_RUN_INST_CMPL_GRP1 events ((Group 1 pm_utilization) Number of run
instructions completed.) with a unit mask of 0x00 (No unit mask) count 500000 samples  %        samples  %        samples  %      app name        symbol name
204538    8.3658  32078     1.3965  gcc_base.none   gcc_base.none   reg_is_remote_consta\nt_p.isra.3.part.4
124596    5.0961  252227   10.9809  gcc_base.none   gcc_base.none   bitmap_operation
112326    4.5943  50890     2.2155  libc-2.13.so    libc-2.13.so    memset
90312     3.6939  21882     0.9527  gcc_base.none   gcc_base.none   compute_transp
0              0  0              0  vmlinux         vmlinux         .pseries_dedicated\_idle_sleep
88894     3.6359  124831    5.4346  gcc_base.none   gcc_base.none   bitmap_element_all\ocate
67995     2.7811  41331     1.7994  gcc_base.none   gcc_base.none   ggc_set_mark
56460     2.3093  89484     3.8958  gcc_base.none   gcc_base.none   canon_rtx
54076     2.2118  6965      0.3032  gcc_base.none   gcc_base.none   delete_null_pointer\_checks
51228     2.0953  26057     1.1344  gcc_base.none   gcc_base.none   ggc_mark_rtx_childr\en_1
48057     1.9656  16005     0.6968  gcc_base.none   gcc_base.none   single_set_2
47160     1.9289  33766     1.4700  gcc_base.none   gcc_base.none   note_stores

每个监视的事件在输出中由一对列表示。 第一列显示了从PCM中为指定事件收集的样本数,第二列显示了其在总样本数中所占的百分比。 从该报告中可以看出,符号reg_is_remote_constant_p是消耗大多数处理器周期的符号,并且是代码优化的理想选择。 此配置文件仅标识哪些符号消耗最多的CPU周期,而不标识是否充分利用了处理器管线。 您可以通过比较计数器结果来调查管道利用率。

考虑计数器PM_INST_CMPL_GRP1 (第二对列); 符号bitmap_operation显示的百分比高于reg_is_remote_constant_p符号。 对于每个完成的处理器指令,此性能计数器都会递增,而PM_CYC_GRP1仅表示已利用的CPU周期数。 如果不做进一步分析,这可能表明符号reg_is_remote_constant_p比符号bitmap_operation包含更多的CPU停顿,因为为符号reg_is_remote_constant_p完成的指令数量明显更少。 该配置文件提供了一个初步提示,指出哪个符号可用于后续的优化工作。

在开始研究并破解代码之前,明智的做法是了解工作负载是否受CPU或内存限制。 这很重要,因为每种工作负载类型的优化方法都大不相同。 例如,大多数内存访问来自高速缓存或主内存(与NUMA远程节点内存访问相反),而性能几乎完全取决于所使用的算法和数据结构。 要研究内存访问模式,请查看表3中的以下两个性能计数器:

表3. POWER7内存利用率计数器
计数器 描述
PM_MEM0_RQ_DISP 读取分配给主内存的请求
PM_MEM0_WQ_DISP 写分配给主内存的请求

这两个计数器可以指示存储器访问模式是否主要来自存储器读取,写入或两者。 使用与之前相同的基准(来自SPECcpu2006的403.gcc ),该配置文件显示:

清单7. 403.gcc基准测试的“ opreport-”输出(计数器PM_MEM0_RQ_DISP和PM_MEM0_WQ_DISP)
CPU: ppc64 POWER7, speed 3550 MHz (estimated)
Counted PM_MEM0_RQ_DISP_GRP59 events ((Group 59 pm_nest2)  Nest events (MC0/MC1/PB/GX),
Pair0 Bit1) with a unit mask of 0x00 (No unit mask) count 1000
Counted PM_MEM0_WQ_DISP_GRP59 events ((Group 59 pm_nest2)  Nest events (MC0/MC1/PB/GX),
Pair3 Bit1) with a unit mask of 0x00 (No unit mask) count 1000
samples  %        samples  %        app name                 symbol name
225841   25.8000  289       0.4086  gcc_base.none            reg_is_remote_constant_p.\isra.3.part.4
90068    10.2893  2183      3.0862  gcc_base.none            compute_transp
54038     6.1733  308       0.4354  gcc_base.none            single_set_2
32660     3.7311  2006      2.8359  gcc_base.none            delete_null_pointer_checks
26352     3.0104  1498      2.1178  gcc_base.none            note_stores
21306     2.4340  1950      2.7568  vmlinux                  .pseries_dedicated_idle_sl\eep
18059     2.0631  9186     12.9865  libc-2.13.so             memset
15867     1.8126  659       0.9316  gcc_base.none            init_alias_analysis

要观察的另一套有趣的性能计数器是L2和L3对缓存的访问压力。 下面的示例使用perf来剖析SPECcpu2006 483.xalancbmk组件(见相关主题 )正在使用RHEL6.2 Linux系统GCC构建的。 该组件大量使用内存分配例程,因此会给内存子系统带来很大压力。 为此,请使用OProfile监视表4中的以下计数器:

表4. POWER7高速缓存/内存访问计数器
计数器 描述
PM_DATA_FROM_L2 由于需求负载,已从本地L2重新加载了处理器的数据缓存
PM_DATA_FROM_L3 由于需求负载,已从本地L3重新加载了处理器的数据缓存
PM_DATA_FROM_LMEM 处理器的数据缓存已从与该处理器位于同一模块上的内存中重新加载
PM_DATA_FROM_RMEM 处理器的数据缓存是从与该处理器位于的模块不同的内存中重新加载的

概要文件输出显示以下内容:

清单8. 489.Xalancbmk基准测试的“ opreport-”输出(计数器PM_DATA_FROM_L2_GRP91和PM_DATA_FROM_L3_GRP91)
CPU: ppc64 POWER7, speed 3550 MHz (estimated)
Counted PM_DATA_FROM_L2_GRP91 events ((Group 91 pm_dsource1) The processor's Data Cache
was reloaded from the local L2 due to a demand load.) with a unit mask of 0x00 (No unitmask) count 1000
Counted PM_DATA_FROM_L3_GRP91 events ((Group 91 pm_dsource1) The processor's Data Cachewas reloaded from the local L3 due to a demand load.) with a unit mask of 0x00 (No unitmask) count 1000
samples  %        samples  %        image name     app name       symbol name
767827   25.5750  7581      0.2525  gcc_base.none  gcc_base.none  bitmap_element_allocate
377138   12.5618  8341      0.2778  gcc_base.none  gcc_base.none  bitmap_operation
93334     3.1088  3160      0.1052  gcc_base.none  gcc_base.none  bitmap_bit_p
70278     2.3408  5913      0.1969  libc-2.13.so   libc-2.13.so   _int_free
56851     1.8936  22874     0.7618  oprofile       oprofile       /oprofile
47570     1.5845  2881      0.0959  gcc_base.none  gcc_base.none  rehash_using_reg
41441     1.3803  8532      0.2841  libc-2.13.so   libc-2.13.so   _int_malloc
清单9. 489.Xalancbmk基准测试的“ opreport-”输出(计数器PM_DATA_FROM_LMEM_GRP91和PM_DATA_FROM_RMEM_GRP91)
Counted PM_DATA_FROM_LMEM_GRP91 events ((Group 91 pm_dsource1) The processor's Data Cache
was reloaded from memory attached to the same module this proccessor is located on.) witha unit mask of 0x00 (No unit mask) count 1000
Counted PM_DATA_FROM_RMEM_GRP91 events ((Group 91 pm_dsource1) The processor's Data Cachewas reloaded from memory attached to a different module than this proccessor is located
on.) with a unit mask of 0x00 (No unit mask) count 1000
samples  %        samples  %        image name     app name       symbol name
1605      0.3344  0              0  gcc_base.none  gcc_base.none  bitmap_element_allocate
1778      0.3704  0              0  gcc_base.none  gcc_base.none  bitmap_operation
1231      0.2564  0              0  gcc_base.none  gcc_base.none  bitmap_bit_p
205       0.0427  0              0  libc-2.13.so   libc-2.13.so   _int_free
583       0.1215  327      100.000  oprofile       oprofile       /oprofile
0              0  0              0  gcc_base.none  gcc_base.none  rehash_using_reg
225       0.0469  0              0  libc-2.13.so   libc-2.13.so   _int_malloc

解释概要文件输出表明,大多数高速缓存压力来自L2访问,几乎没有需求的L3重载,因为L2访问的总和相对计数器样本值( PM_DATA_FROM_L2 )远远高于L3需求重载( PM_DATA_FROM_L3 )。 您只能通过更全面的分析(通过观察更多的计数器)来获取更多信息,例如L2访问是否由于高速缓存未命中而导致CPU停顿。 从此示例配置文件可以得出的结论是,与高速缓存访​​问相比,主存储器访问( PM_DATA_FROM_LMEM事件)非常低,并且没有远程访问(事件PM_DATA_FROM_RMEM ),表明没有远程NUMA节点存储器访问。 热点和内存访问模式的分析可以为优化工作提供指导; 在这种情况下,需要进行进一步分析以找出真正导致CPU停顿的原因,因为简单地识别工作负载热点和内存访问模式不足以正确地识别CPU停顿。

为了提出更好的性能优化策略,需要使用perf工具而不是OProfile进行进一步分析,因为需要同时监视许多POWER7 CBM计数器( 图3中所示的22个计数器),并提供更好的性能优化策略。 其中许多事件属于不同的组,这意味着使用OProfile需要多次运行相同的工作负载。 该perf工具将复用硬件计数器的观看时指定的柜台都在不止一个组。 尽管这会导致结果的准确性降低,但总体结果往往与预期的结果非常相似,其优点是花了更少的分析时间。

以下示例使用perf来分析相同的SPECcpu2006 483.xalancbmk组件。 要分析此组件,请发出清单10中的命令:

清单10.用于生成POWER7 CBM的perf命令
$ /usr/bin/perf stat -C 0 -e r100f2,r4001a,r100f8,r4001c,r2001a,r200f4,r2004a,r4004a,
r4004e,r4004c,r20016,r40018,r20012,r40016,r40012,r20018,r4000a,r2001c,r1001c,r20014,
r40014,r30004 taskset -c 0 ./Xalan_base.none -v t5.xml xalanc.xsl > power7_cbm.dat

此命令将使perf监视-c指定的CPU上-e参数定义的原始事件。 任务集调用可确保组件仅在0号CPU上运行。工作负载./Xalan_base.none -v t5.xml xalanc.xsl可以由另一个应用程序替换以进行概要分析。 配置文件完成后,perf命令将输出一个简单表,其中包含每个原始事件的总计数以及经过的秒数:

清单11. 489.Xalancbmk基准的“性能统计”输出
Performance counter stats for 'taskset -c 0 ./Xalan_base.none -v t5.xml xalanc.xsl': 366,860,486,404 r100f2                                                       [18.15%] 8,090,500,758 r4001a                                                       [13.65%] 50,655,176,004 r100f8                                                       [ 9.13%] 11,358,043,420 r4001c                                                       [ 9.11%] 10,318,533,758 r2001a                                                       [13.68%] 1,301,183,175,870 r200f4                                                       [18.22%] 2,150,935,303 r2004a                                                       [ 9.10%] 0 r4004a                                                       [13.65%] 211,224,577,427 r4004e                                                       [ 4.54%] 212,033,138,844 r4004c                                                       [ 4.54%] 264,721,636,705 r20016                                                       [ 9.09%] 22,176,093,590 r40018                                                       [ 9.11%] 510,728,741,936 r20012                                                       [ 9.10%] 39,823,575,049 r40016                                                       [ 9.07%] 7,219,335,816 r40012                                                       [ 4.54%] 1,585,358 r20018                                                       [ 9.08%] 882,639,601,431 r4000a                                                       [ 9.08%] 1,219,039,175 r2001c                                                       [ 9.08%] 3,107,304 r1001c                                                       [13.62%] 120,319,547,023 r20014                                                       [ 9.09%] 50,684,413,751 r40014                                                       [13.62%] 366,940,826,307 r30004                                                       [18.16%] 461.057870036 seconds time elapsed

为了分析perf靠在POWER7 CBM,提供了一种Python脚本输出(检查在power7_cbm.zip 可下载资源 ),其构成从所收集的虚拟和硬件计数器的计数器度量。 要创建报告,请发出清单12中的命令:

清单12. POWER7 CBM python脚本调用
$ power7_cbm.py power7_cbm.dat

将输出类似于清单13的输出:

清单13. 489.Xalancbmk基准测试的“ power7_cbm.py”输出
CPI Breakdown Model (Complete) Metric                         :            Value :    Percent
PM_CMPLU_STALL_DIV             :    49802421337.0 :        0.0
PM_CMPLU_STALL_FXU_OTHER       :    67578558649.0 :        5.2
PM_CMPLU_STALL_SCALAR_LONG     :        2011413.0 :        0.0
PM_CMPLU_STALL_SCALAR_OTHER    :     7195240404.0 :        0.6
PM_CMPLU_STALL_VECTOR_LONG     :              0.0 :        0.0
PM_CMPLU_STALL_VECTOR_OTHER    :     1209603592.0 :        0.1
PM_CMPLU_STALL_ERAT_MISS       :    22193968056.0 :        1.7
PM_CMPLU_STALL_REJECT_OTHER    :    18190293594.0 :        1.4
PM_CMPLU_STALL_DCACHE_MISS     :   261865838255.0 :       20.3
PM_CMPLU_STALL_STORE           :     2001544985.0 :        0.2
PM_CMPLU_STALL_LSU_OTHER       :   202313206181.0 :       15.7
PM_CMPLU_STALL_THRD            :        2025705.0 :        0.0
PM_CMPLU_STALL_BRU             :   208356542821.0 :       16.2
PM_CMPLU_STALL_IFU_OTHER       :     2171796336.0 :        0.2
PM_CMPLU_STALL_OTHER           :    30895294057.0 :        2.4
PM_GCT_NOSLOT_IC_MISS          :     9805421042.0 :        0.8
PM_GCT_NOSLOT_BR_MPRED         :     7823508357.0 :        0.6
PM_GCT_NOSLOT_BR_MPRED_IC_MISS :    11059314150.0 :        0.9
PM_GCT_EMPTY_OTHER             :    20292049774.0 :        1.6
PM_1PLUS_PPC_CMPL              :   365158978504.0 :       28.3
OVERHEAD_EXPANSION             :      590057044.0 :        0.0
Total                                             :       96.1

该报告基于误差范围内的统计值,因此最终百分比并不完全准确。 即使具有很高的错误余量,也有大约20%的CPU停顿是由于数据高速缓存未命中( PM_CMPLU_STALL_DCACHE_MISS )。 最终指令完成百分比( PM_1PLUS_PPC_CMPL )约为28%。

未来的优化应该尝试通过减少CPU停顿和/或GCT(全局完成表)百分比来最大化此数目。 根据此报告,另一种分析途径是识别发生停顿的代码。 为此,请使用perf record命令。 它将跟踪原始计数器的性能,并创建一个带有进程回溯的映射,从而可以识别哪个符号产生了最多的硬件事件。 这类似于OProfile工作方式。 在此示例中,要跟踪PM_CMPLU_STALL_DCACHE_MISS事件,请发出清单14中的命令:

清单14. PM_CMPLU_STALL_DCACHE_MISS事件的性能记录
$ /usr/bin/perf record -C 0 -e r20016 taskset -c 0 ./Xalan_base.none -v t5.xml xalanc.xsl

perf命令将使用结果创建一个数据文件(通常为“ perf.dat”)。 可以使用perf report命令以交互方式读取它,如清单15所示 :

清单15. 489.Xalancbmk基准的“性能报告”的输出
Events: 192  raw 0x2001639.58%  Xalan_base.none  Xalan_base.none  [.] xercesc_2_5::ValueStore::contains 11.46%  Xalan_base.none  Xalan_base.none  [.] xalanc_1_8::XStringCachedAllocator9.90%  Xalan_base.none  Xalan_base.none  [.] xalanc_1_8::XStringCachedAllocator7.29%  Xalan_base.none  Xalan_base.none  [.] xercesc_2_5::ValueStore::isDuplica5.21%  Xalan_base.none  libc-2.13.so     [.] _int_malloc 5.21%  Xalan_base.none  Xalan_base.none  [.] __gnu_cxx::__normal_iterator<xa4.17%  Xalan_base.none  libc-2.13.so     [.] __GI___libc_malloc 2.08%  Xalan_base.none  libc-2.13.so     [.] malloc_consolidate.part.4 1.56%  Xalan_base.none  Xalan_base.none  [.] xalanc_1_8::ReusableArenaBlock<xa1.56%  Xalan_base.none  Xalan_base.none  [.] xalanc_1_8::ReusableArenaBlock<xa1.04%  Xalan_base.none  libc-2.13.so     [.] __free
[...]

通过使用POWER7 CBM计数器和性能报告工具进行的分析,您的优化工作可能会集中在优化符号xercesc_2_5 :: ValueStore :: contains(xercesc_2_5 :: FieldValueMap const *)上的内存和缓存访问上。

此示例只是可能分析的一部分。 POWER7 CBM向您显示,尽管显示数据高速缓存停顿是造成CPU停顿的主要原因,但装入和存储单元( PM_CMPLU_STALL_LSU )和分支单元( PM_CMPLU_STALL_BRU )都是停顿的来源。 进一步的分析可以解决这些问题。

案例分析

以下案例研究应用了这些性能评估策略来分析三角数学函数的实现。 根据分析结果,将确定优化机会。 本案例研究中使用的函数是ISO C hypot函数,定义为直角三角形的斜边的长度。 该功能由C99 POSIX.1-2001定义为:

double hypot(double x, double y);

hypot()函数返回sqrt(x * x + y * y)。 成功时,此函数返回边长为x和y的直角三角形的长度。 如果x或y是无穷大,则返回正无穷大。 如果x或y是NaN,而另一个参数不是无穷大,则返回NaN。 如果结果溢出,则会发生范围错误,并且这些函数分别返回HUGE_VAL,HUGE_VALF或HUGE_VALL。 如果两个参数都属于非正规变量,并且结果均为非正规变量,则将发生范围错误,并返回正确的结果。

尽管该算法看起来很简单,但是Infinity和NaN的浮点(FP)参数处理以及与FP操作相关的上溢/下溢给性能带来了一些挑战。 GNU C库(参见相关主题 )提供了位于源树在sysdeps / IEEE754 / DBL-64 / e_hypot.c hypot将的实现:

注意 :此代码示例的许可证信息包含在附录中 。

清单16.默认的GLIBC hypot源代码
double __ieee754_hypot(double x, double y)
{ double a,b,t1,t2,y1,y2,w; int32_t j,k,ha,hb; GET_HIGH_WORD(ha,x); ha &= 0x7fffffff; GET_HIGH_WORD(hb,y); hb &= 0x7fffffff; if(hb > ha) {a=y;b=x;j=ha; ha=hb;hb=j;} else {a=x;b=y;} SET_HIGH_WORD(a,ha);    /* a <- |a| */ SET_HIGH_WORD(b,hb);    /* b <- |b| */ if((ha-hb)>0x3c00000) {return a+b;} /* x/y > 2**60 */ k=0; if(ha > 0x5f300000) {   /* a>2**500 */ if(ha >= 0x7ff00000) {       /* Inf or NaN */ u_int32_t low; w = a+b;                 /* for sNaN */ GET_LOW_WORD(low,a); if(((ha&0xfffff)|low)==0) w = a; GET_LOW_WORD(low,b); if(((hb^0x7ff00000)|low)==0) w = b; return w; } /* scale a and b by 2**-600 */ ha -= 0x25800000; hb -= 0x25800000;  k += 600; SET_HIGH_WORD(a,ha); SET_HIGH_WORD(b,hb); } if(hb < 0x20b00000) {   /* b < 2**-500 */ if(hb <= 0x000fffff) {      /* subnormal b or 0 */ u_int32_t low; GET_LOW_WORD(low,b); if((hb|low)==0) return a; t1=0; SET_HIGH_WORD(t1,0x7fd00000);   /* t1=2^1022 */ b *= t1; a *= t1; k -= 1022; } else {            /* scale a and b by 2^600 */ ha += 0x25800000;       /* a *= 2^600 */ hb += 0x25800000;       /* b *= 2^600 */ k -= 600; SET_HIGH_WORD(a,ha); SET_HIGH_WORD(b,hb); } } /* medium size a and b */ w = a-b; if (w>b) { t1 = 0; SET_HIGH_WORD(t1,ha); t2 = a-t1; w  = __ieee754_sqrt(t1*t1-(b*(-b)-t2*(a+t1))); } else { a  = a+a; y1 = 0; SET_HIGH_WORD(y1,hb); y2 = b - y1; t1 = 0; SET_HIGH_WORD(t1,ha+0x00100000); t2 = a - t1; w  = __ieee754_sqrt(t1*y1-(w*(-w)-(t1*y2+t2*b))); } if(k!=0) { u_int32_t high; t1 = 1.0; GET_HIGH_WORD(high,t1); SET_HIGH_WORD(t1,high+(k<<20)); return t1*w; } else return w;
}

此实现非常复杂,主要是因为该算法执行了许多逐位FP到INT的转换。 假定使用浮点指令比使用定点指令时某些FP操作(例如比较和乘法)的开销更大。 在某些架构上确实如此,但在Power Architecture上却不是。

评估此实现的第一步是创建一个可配置的基准。 在这种情况下,由于它只是一个带有两个参数的函数和一个简单的算法(没有内部函数调用或其他路径),因此可以创建一个简单的基准来对其进行评估(请参阅可下载资源中的hypot_bench.tar.gz)。 基准是绩效评估的一部分; 优化应加快利用总工作负载性能的算法或算法的关键部分。 像这样的综合基准应该代表该功能的正常使用。 由于优化工作往往是资源和时间的浪费,因此需要将精力集中在最常见的使用情况或预期的行为上。 尝试优化表示总程序使用率较低的代码往往会浪费资源。

由于这是对单个功能的性能分析,因此您可以跳过热点分析,而专注于CBM分析。 使用hypot_bench.c中的基准以及perf , 清单17中的CBM信息:

清单17.“ hypot”基准测试的“ power7_cbm.py”输出
CPI Breakdown Model (Complete) Metric                         :            Value :    Percent
PM_CMPLU_STALL_DIV             :        8921688.0 :        8.7
PM_CMPLU_STALL_FXU_OTHER       :    13953382275.0 :        5.0
PM_CMPLU_STALL_SCALAR_LONG     :    24380128688.0 :        8.7
PM_CMPLU_STALL_SCALAR_OTHER    :    33862492798.0 :       12.0
PM_CMPLU_STALL_VECTOR_LONG     :              0.0 :        0.0
PM_CMPLU_STALL_VECTOR_OTHER    :      275057010.0 :        0.1
PM_CMPLU_STALL_ERAT_MISS       :         173439.0 :        0.0
PM_CMPLU_STALL_REJECT_OTHER    :         902838.0 :        0.0
PM_CMPLU_STALL_DCACHE_MISS     :       15200163.0 :        0.0
PM_CMPLU_STALL_STORE           :        1837414.0 :        0.0
PM_CMPLU_STALL_LSU_OTHER       :    94866270200.0 :       33.7
PM_CMPLU_STALL_THRD            :         569036.0 :        0.0
PM_CMPLU_STALL_BRU             :    10470012464.0 :        3.7
PM_CMPLU_STALL_IFU_OTHER       :      -73357562.0 :        0.0
PM_CMPLU_STALL_OTHER           :     7140295432.0 :        2.5
PM_GCT_NOSLOT_IC_MISS          :        3586554.0 :        0.0
PM_GCT_NOSLOT_BR_MPRED         :     1008950510.0 :        0.4
PM_GCT_NOSLOT_BR_MPRED_IC_MISS :         795943.0 :        0.0
PM_GCT_EMPTY_OTHER             :    42488384303.0 :       15.1
PM_1PLUS_PPC_CMPL              :    53138626513.0 :       18.9
OVERHEAD_EXPANSION             :       30852715.0 :        0.0
Total                                             :      108.7

概要分析表明,大多数CPU停止运行,因此性能损失来自加载和存储单元( LSU-计数器PM_CMPLU_STALL_LSU_OTHER )。 LSU具有与之关联的各种计数器,但是在CPU停顿分析期间,您的重点是与性能下降相关的计数器。 那些显示POWER上的性能下降的原因与“加载-命中存储(LHS)”危害相关。 当CPU将数据写入一个地址,然后尝试过快地再次加载该数据时,会发生很大的停顿。 下一步是通过首先检查事件PM_LSU_REJECT_LHS (原始代码“ rc8ac”)来检查此特定算法是否发生这种情况,如清单18所示。

清单18. PM_LSU_REJECT_LHS POWER7事件的性能记录
$ perf record -C 0 -e rc8ac taskset -c 0 ./hypot_bench_glibc
$ perf report
Events: 14K raw 0xc8ac79.19%  hypot_bench_gli  libm-2.12.so       [.] __ieee754_hypot10.38%  hypot_bench_gli  libm-2.12.so       [.] __hypot6.34%  hypot_bench_gli  libm-2.12.so       [.] __GI___finite

概要文件输出显示符号__ieee754_hypot是生成大多数PM_LSU_REJECT_LHS事件的符号。 研究由编译器生成的汇编代码,以识别哪些指令正在生成事件。 通过在性能perf report屏幕上进行迭代并选择__ieee754_hypot符号,展开符号__ieee754_hypot来对程序集进行注释,这将显示清单19的输出。

清单19. PM_LSU_REJECT_LHS POWER7事件的性能报告
:        00000080fc38b730 <.__ieee754_hypot>:0.00 :          80fc38b730:   7c 08 02 a6     mflr    r00.00 :          80fc38b734:   fb c1 ff f0     std     r30,-16(r1)0.00 :          80fc38b738:   fb e1 ff f8     std     r31,-8(r1)13.62 :          80fc38b73c:   f8 01 00 10     std     r0,16(r1)0.00 :          80fc38b740:   f8 21 ff 71     stdu    r1,-144(r1)10.82 :          80fc38b744:   d8 21 00 70     stfd    f1,112(r1)0.23 :          80fc38b748:   e9 21 00 70     ld      r9,112(r1)17.54 :          80fc38b74c:   d8 41 00 70     stfd    f2,112(r1)0.00 :          80fc38b750:   79 29 00 62     rldicl  r9,r9,32,330.00 :          80fc38b754:   e9 61 00 70     ld      r11,112(r1)0.00 :          80fc38b758:   e8 01 00 70     ld      r0,112(r1)8.46 :          80fc38b75c:   d8 21 00 70     stfd    f1,112(r1)
[...]

在代码的早期,实现使用宏GET_HIGH_WORD将浮点数转换为整数 ,以进行后继按位运算。 GLIBC的math / math_private.h使用清单20中的代码定义了宏。

清单20. GET_HIGH_WORD宏定义
#define GET_HIGH_WORD(i,d)                                      \
do {                                                            \ieee_double_shape_type gh_u;                                  \gh_u.value = (d);                                             \(i) = gh_u.parts.msw;                                         \
} while (0)

导致LHS停顿的可能原因是该操作读取float的内部值属性,然后将其读取到变量i 。 POWER7处理器没有本机指令将浮点寄存器的内容逐位移动到定点寄存器。 在POWER上完成此操作的方法是使用存储操作将FP编号存储在浮点寄存器中的存储器中,然后将相同的存储器位置加载到定点(通用)中。 由于内存访问比寄存器操作慢(即使在访问L1数据高速缓存时),因此在存储过程中CPU停顿以完成后续加载。

:文件, “POWER ISA 2.06(POWER7)”(见相关信息 ),包含更多的信息。

大多数情况下,性能计数器事件会触发中断,从而使指令的PC地址保存在与执行指令接近的位置。 这可能导致装配注释不完全准确。 为了减轻这种现象,POWER4和更高版本具有一组有限的性能计数器,它们的名称为marked 。 标记的指令将在每个时间范围内产生较少的事件; 但是,PC指令将准确无误,从而产生准确的装配注释。 标记的事件在由opcontrol -l获取的OProfile计数器列表中具有PM_MRK前缀。

要仔细检查分析,请观看PM_MRK_LSU_REJECT_LHS计数器。 PM_MRK_LSU_REJECT_LHS和PM_LSU_REJECT_LHS这两个计数器都监视同一性能事件。 但是,标记的计数器( PM_MRK_LSU_REJECT_LHS )将在每个时间范围内生成较少的事件,但具有更准确的程序集注释。 (请参见清单21。 )

清单21. PM_MRK_LSU_REJECT_LHS POWER7事件的性能记录
$ perf record -C 0 -e rd082 taskset -c 0 ./hypot_bench_glibc
$ perf report
Events: 256K raw 0xd08264.61%  hypot_bench_gli  libm-2.12.so       [.] __ieee754_hypot35.33%  hypot_bench_gli  libm-2.12.so       [.] __GI___finite

这将在清单22中生成程序集批注。

清单22. PM_MRK_LSU_REJECT_LHS POWER7事件的性能报告
:        00000080fc38b730 <.__ieee754_hypot>:
[...]1.23 :          80fc38b7a8:   c9 a1 00 70     lfd     f13,112(r1)0.00 :          80fc38b7ac:   f8 01 00 70     std     r0,112(r1)32.66 :          80fc38b7b0:   c8 01 00 70     lfd     f0,112(r1)
[...]0.00 :          80fc38b954:   f8 01 00 70     std     r0,112(r1)0.00 :          80fc38b958:   e8 0b 00 00     ld      r0,0(r11)0.00 :          80fc38b95c:   79 00 00 0e     rldimi  r0,r8,32,061.72 :          80fc38b960:   c9 61 00 70     lfd     f11,112(r1
[...]

清单23中 ,另一个符号显示了35%的具有类似行为的已生成事件。

清单23.性能报告的更多亮点
:        00000080fc3a2610 <.__finitel>>0.00 :          80fc3a2610:   d8 21 ff f0     stfd    f1,-16(r1)100.00 :          80fc3a2614:   e8 01 ff f0     ld      r0,-16(r1)

根据此信息,您的优化工作可能是通过除去FP到INT的转换来消除这些停顿。 POWER处理器具有快速高效的浮点执行单元,因此无需使用定点指令执行这些计算。 POWER当前在GLIBC中使用的算法(sysdeps / powerpc / fpu / e_hypot.c)仅通过使用FP操作已删除了所有LHS停顿。 结果是清单24中更简单的算法。

清单24. PowerPC GLIBC hypot源代码
double
__ieee754_hypot (double x, double y)
{x = fabs (x);y = fabs (y);TEST_INF_NAN (x, y);if (y > x){double t = x;x = y;y = t;}if (y == 0.0 || (x / y) > two60){return x + y;}if (x > two500){x *= twoM600;y *= twoM600;return __ieee754_sqrt (x * x + y * y) / twoM600;}if (y < twoM500){if (y <= pdnum){x *= two1022;y *= two1022;return __ieee754_sqrt (x * x + y * y) / two1022;}else{x *= two600;y *= two600;return __ieee754_sqrt (x * x + y * y) / two600;}}return __ieee754_sqrt (x * x + y * y);
}

TEST_INF_NAN宏是另一个较小的优化,它在开始进一步的FP操作之前测试数字是NaN还是INFINITY(这是由于对NaN和INFINITY进行的操作会引发FP异常,并且函数规范不允许这样做)。 在POWER7上, isinfisnan函数调用已由编译器优化为FP指令,并且不会生成额外的函数调用,而在较旧的处理器(POWER6和较旧的处理器)上,它将生成对相应函数的调用。 优化基本上是相同的实现,但是内联以避免函数调用。

最后,比较这两种实现,执行以下简单测试。 使用和不使用新算法重新编译GLIBC,并比较每次基准测试的总时间。 清单25是默认的GLIBC实现结果:

清单25.带有默认GLIBC假设的基准
$ /usr/bin/time ./hypot_bench_glibc
INF_CASE       : elapsed time: 14:994339
NAN_CASE       : elapsed time: 14:707085
TWO60_CASE     : elapsed time: 12:983906
TWO500_CASE    : elapsed time: 10:589746
TWOM500_CASE   : elapsed time: 11:215079
NORMAL_CASE    : elapsed time: 15:325237
79.80user 0.01system 1:19.81elapsed 99%CPU (0avgtext+0avgdata 151552maxresident)k
0inputs+0outputs (0major+48minor)pagefaults 0swaps

优化的版本结果如清单26所示 :

清单26.优化的GLIBC假设基准
$ /usr/bin/time ./hypot_bench_glibc
INF_CASE       : elapsed time: 4:667043
NAN_CASE       : elapsed time: 5:100940
TWO60_CASE     : elapsed time: 6:245313
TWO500_CASE    : elapsed time: 4:838627
TWOM500_CASE   : elapsed time: 8:946053
NORMAL_CASE    : elapsed time: 6:245218
36.03user 0.00system 0:36.04elapsed 99%CPU (0avgtext+0avgdata 163840maxresident)k
0inputs+0outputs (0major+50minor)pagefaults 0swaps

这是最终性能提高了100%以上,将基准时间缩短了一半。

结论

带有硬件计数器配置文件的性能评估是一种功能强大的工具,可以了解工作负载在特定处理器上的行为方式,并提示在哪里进行性能优化。 最新的POWER7处理器具有数百个可用的性能计数器,因此我们提出了一个简单的模型,该模型将工作负载映射到CPU停顿。 了解POWER7 CBM有点复杂,因此我们还介绍了简化它的Linux工具。 性能评估的策略集中在如何查找热点 ,如何理解应用程序的内存模式以及如何使用POWER7 CBM。 最后,我们使用了最近对GLIBC内的三角函数进行的优化来解释用于生成优化代码的性能分析。

附录

根据GNU自由文档许可版本1.3,已授予复制,分发和/或修改本文档的权限; 没有不变的部分,没有前封面文字,也没有后封面文字。


翻译自: https://www.ibm.com/developerworks/opensource/library/l-evaluatelinuxonpower/index.html

linux power

linux power_评估Linux on POWER的性能相关推荐

  1. 提高linux上socket的性能(linux优化),提高 Linux 上 socket 性能

    使用 Sockets API,我们可以开发客户机和服务器应用程序,它们可以在本地网络上进行通信,也可以通过 Internet 在全球范围内进行通信.与其他 API 一样,您可以通过一些方法使用 Soc ...

  2. linux pti性能影响,Linux修正内核:Intel打补丁性能狂降、AMD不受影响

    Linux修正内核:Intel打补丁性能狂降.AMD不受影响 由于Meltdown和Spectre两个严重内核级漏洞造成的安全事件愈演愈烈,其中不可否认的是,搭载Intel处理器的Linux服务器.数 ...

  3. Linux服务器上监控网络带宽与监控性能命令大全

    [51CTO精选译文]本文介绍了一些可以用来监控网络使用情况的Linux命令行工具.这些工具可以监控通过网络接口传输的数据,并测量目前哪些数据所传输的速度.入站流量和出站流量分开来显示. 一些命令可以 ...

  4. flayber正文 再谈如何学习Linux,一线Linux专家学习经验谈

    记得最早接触linux是在2000年,那个时候,还在上大学,一个同学从荷兰回来,带回来了一个Linux的拷贝版,记得版本还是Redhat6.2.曾经为安装一个系统让我们忘记疲劳,挑灯夜战,不亦乐乎.那 ...

  5. linux 复制包括子目录_【Linux分享】Linux常用命令+教程分享

    今天分享分为两部分 :)PART01 Linux常用命令分享/PART02 关于BD面试经验分享    30mins  Linux Command:PART 1 你本可以张口就来.....本篇内容分享 ...

  6. linux 计算机概论 Linux介绍

    CPU: CPU内部可以分为两个主要单元:算数逻辑单元和控制单元. 算数逻辑单元主要用于程序运算和逻辑判断,控制单元主要用于协调各个组件和各单元的工作. CPU基本可以分为两种: 精简指令集和复杂指令 ...

  7. 再谈如何学习Linux,一线Linux专家学习经验谈

    →点击领取阿里云限量红包 记得最早接触linux是在2000年,那个时候,还在上大学,一个同学从荷兰回来,带回来了一个Linux的拷贝版,记得版本还是Redhat6.2.曾经为安装一个系统让我们忘记疲 ...

  8. 循序渐进学Linux,一线Linux专家南非蚂蚁学习经验谈

    记得最早接触linux是在2000年,那个时候,还在上大学,一个同学从荷兰回来,带回来了一个Linux的拷贝版,记得版本还是Redhat6.2.曾经为安装一个系统让我们忘记疲劳,挑灯夜战,不亦乐乎.那 ...

  9. Linux专家谈如何学习Linux,以及Linux的职业发展

    记得最早接触linux是在2002年,那个时候,还在上大学,曾经为安装一个系统让我们忘记疲劳,挑灯夜战,不亦乐乎.那时如果会安装一个Linux系统也是可以走进不少女生宿舍,哈哈.那时Linux的学习资 ...

最新文章

  1. 收集一些好的技术文档
  2. java虚拟机——JVM
  3. PHP函数-判断字符是否在于指定的字符串中
  4. 实现三联tab切换特效
  5. 方舟无限资源服务器,方舟生存进化怎么无限资源
  6. vscode access denied to unins000.exe
  7. 协议和代理模式的概念
  8. 德国华人餐饮外卖的春天?
  9. 轩小陌的Python笔记-day03 Python基础
  10. 30. 人类将如何变革--走出金字塔模型(下)
  11. MCS51 数据存储器(RAM)
  12. 给在读研究生+未来要读研同学们的一封受益匪浅的信
  13. 4T移动硬盘 分区_手机连接移动硬盘教程
  14. 【某网页在手机上可以打开,但用电脑上所有浏览器都打不开】的解决办法
  15. 青你3倒奶,蒙牛装小白花?
  16. js将数组转化成json
  17. Tkinter-疫苗信息管理系统
  18. ISO14443 Type B类型卡的防碰撞过程以及命令解析
  19. 关于Typora的markdown编辑器行间距过大、不一致的问题
  20. easy-table-vue+Vue、SpringBoot+Mybatis实现MVVM模型前后台数据交互

热门文章

  1. (转载)来一本奥运版圣经?
  2. 【新书推荐】头脑聪明,思维活跃,但极有可能是一名低质量学习者!
  3. 滚珠丝杠总长设计计算
  4. 距离度量 —— 曼哈顿距离(Manhattan Distance)
  5. 小程序模板消息点击后弹开发版过期
  6. 服务器换cpu要重装系统吗,更换cpu需要重装系统吗_换cpu是否需要重装系统
  7. android端 edge内核,乔北峰:安卓版Edge已是基于Chromium构建
  8. 量变确实可以引起质变
  9. linux基础管道命令参数用法
  10. 武大数理金融学计算机吗,武汉大学50名数理经济与数理金融学子被国内外知名高校录取...