Begin_Again

Begin_Again

Winter Lv4

计算机科学中的自然原理

  • 前言

    感谢过去一年中给予我启发的诸多事物,无论是一本书如CSAPP,一堂课如数字电路,或者一个人如Prof.Strang,所有这些人事都是促使我更加严肃地审视计算机科学与其背后丰富而美丽的思想。所以,我希望继承那些前辈,那群充满热情与想象力的先驱的工作,在这里对CSAPP中的美妙理论进行简答而又深刻的阐释(有点自大哈哈哈哈),希望在有限的时间与文字中,探寻科学与自然的美。

  • 你好!世界

    萨冈和她的你好忧愁,我和我的你好世界。Hello World,梦开始的地方,我们就从一个hello world.c 程序的生命开始进行一场快速的计算机世界漫游。

    • 程序是怎么编写的呢,首先我们会需要一个文本编辑器,也就是我们常用的devc++或者是vscode,vim,emacs。文本编辑器就是编辑文本文件的,我们缩写的源文件也属于文本文件。文本文件就是只含有阿斯克码的文件,其余的文件都是二进制文件。编写后,我们就可以通过一系列指令来使程序运行。对于一个.c文件而言。我们可以用gcc 命令来生成可执行文件。gcc就似乎编译驱动程序 这是nux终端的命令。当启用后,首先运行的是预处理器,对于含#的指令,如#include预处理器会将头文件全部插入到源文件中,同时完成宏的拓展。这是纯粹的文本替换,其他什么都没有发生。然后就是编译器,编译器将.c文件转换为汇编语言格式,可以理解为机械码的助记符,这是程序员可以阅读和编写的。然用汇编器汇编为二进制,此时是一个可重定位的可执行文件,此时通过ld将引用的库一起链接形成一个可执行文件保存在内存中。调用时,通过加载器加载到cpu进行执行。
    • 几个关键概念。首先,计算机的硬件组成。CPU,内存空间极其缓存结构和虚拟地址,网络与I/O 进程与线程。这里我们能慢慢接触到抽象与设计的感觉。
    • 一切皆文件 ,linux将设备文件都以同一种方式进行处理,让建立一种广泛而统一的接口成为可能
  • 从理论到实践

    在学习每一个章节的过程中,我们会逐渐感觉到与一些相似的内容串联了起来。这种知识路径形成环,环闭合的感觉非常类似于我在高中看科幻小说时形成的想法。哈哈哈哈哈。这里就列举所有对应的知识群,然后随着不断学习深入,持续补充~~(突然想到也许我得去补充几个emoji和颜文字)

    • 二进制族群——数字电路的设计非常优美简洁;CPU流水线;
    • 程序机器级表示——优化技巧
    • 链接——Makefile脚本和Cmake 的使用 以及Vscode 相关json文件的配置
  • FAQ

    • buffer overflow 字符串溢出覆盖栈上内容读取字符串造成
    • 为什么重载函数不能返回区别,因为编译器重整符号时只会考虑函数名字和参数类型
  • 数据表示

    • 浮点数与整数两种格式,拥有不同的表示方法,所以进行类型转换时要注意。整型通过补码表示。公式是唯一的-2^n + 源码的二进制。浮点数是一中近似的表示,对于太多小数位,进行加减时可能出现差。还有就是有符号与无符号的区别以及对应的溢出问题,截断问题。最后就是其实很多函数%d 并不关心真正的类型也不会检查,这只是告诉函数将以一个整型的方式进行内存寻找。
    • big-end && small -end 大小端 注意只存在于多个字节的数据的问题 例如0x12345678 小端机就是 78 56 34 12 就是地位在小地址,注意在网络编程获取主机名于端口时可能有影响,需要调用相应修改转换函数。
    • 计算机处理加法乘法都远远快于除法。同时可以尽量写位运算,当然编译器可能也帮你优化。数字的表示与实现都很精妙 前辈的只会佩服。
  • 汇编简介

    • 首先我们要知道,计算机只认识01,01 构成了整个世界,在数字电路的学习中我们也能有这样的体会。事实上,我们所写的程序最终会转换成01的机器代码,所有的文件不论是文本视频图片文件最终都是01串。而汇编代码就是位于机器代码的一种助记符
    • gcc -Og O1 O2 O3 通常而言 O1 分析 O2 可接受优化 Word因特尔的字就是 16bit 寄存器 6个参数寄存器 rdi rsi rdx rcx r8 r9 然后返回 rax 栈 rsp 计数器PC rip 然后就是被调用者寄存器 我们来看一下机械逻辑是怎么形成过程的。栈,核心,栈帧栈的空间。然后就是三个部分,传递控制,传递数据,分配和释放内存。控制传递依赖两个命令与rip call 会将放回地址即下一条弹入到栈中,然后rip变为label的地址。ret就会压栈然后rip回到返回地址。 数据控制就是通过栈存储多余参数调用,被调用者保存寄存器数据,局部变量存储完成的。
    • 指针与数组,数组就是转化为i指针运算。通过改变内存寻址来实现c语言中的指针类型。结构而言,字段就是基地址偏移量。引入重要的对齐概念:一句话任何大小为K字节的数据类型的首地址都要为K的倍数,intel不强行对齐。因此就可能会补全。buffer overflow 就是字符串溢出覆盖栈上数据,然后对应防范有栈随机化和金丝雀技术。 alloca 栈上分配空间。
    • 浮点数,单独一组寄存器 ymm 256 64bytes,xmm 32bytes. SSE,AVX架构。包含头文件可以使用。
  • 优化

    • 优化面对的挑战。memory aliasing 就是同一个地址由多个指针变量使用,一些激进优化可能导致问题。函数调用对全局状态的改变。
    • CPE,周期每元素一个度量单位。延迟,就是严格顺序执行时一个操作需要的实践。发射时间就是两个相同命令执行所需的间隔,为1就是完全流水化时间。吞吐量,就是发射时间的倒数乘以功能单位。优化方向有减少循环内部计算。减少函数调用,合理内联。减少同一值的反复求值过程。循环展开。多路合并,也就是累计变量。写出简短便于求值使用数据传递控制的条件判断。然后就是使用向量操作。减少内存引用,使用局部变量存储。然后就是重新结合提高指令级并行度。 
    • 关键路径,就是分析优化的一种指令级维度的技术。将汇编代码进行分析,观察其中的循环寄存器以及相关数据链,然后数据链对应就是一个关键路径,看关键路径有无线性缩短。乱序处理,流水线,分支预测,投机执行。CPU的技术。程序分析。unix 上的gprof ,linux上的valgrind, intel的vtune,以及nvidia的nsight 好多profiler。其中gprof 使用需要加一个-gp 选项在gcc中
    • Amdahl’s law 就是想要加速一个系统,其加速比取决于加速部分时间占据整个系统的比重以及加速程度。
  • 内存

    • DRAM,SRAM,ROM,memory ,cache .等名词分清楚。SRAM 构成高速缓存的物理媒介快贵。DRAM构成主存的媒介也就是运存。ROM一般指磁盘一类的flash disk ,CD , ssd 都是不同的存储。
    • DRAM 芯片结构。单个芯片有超单元矩阵构成,每个超单元一般有一个byte.然后2位地址引脚,8位数据引脚掌控数据的传输和行列索引。将芯片封装乘模块。叠8个,一次64位。通过内存控制器广播来讲相同索引的8byte数据聚合成一团数据。改进 DDR SDRAM 双倍数据速率同步DRAM 就是两倍的时钟上升沿
    • ROM是一类统称(非易失性存储),目前最主流的是flash memory。然后还有最新的SSD。ROM 上存储的一些程序叫做固体firmware 例如bios.通过总线进行访存。不过典型计算机使用的是磁盘技术。 连接设备i/o 桥 ,系统总线与内存总线。io设备诸如键鼠GPU都是通过io总线尤其是PCI(外围设备互联总线)连接的,io总线比系统和内存总线慢,但是功能更加多样。
    • DMA磁盘。 磁盘直接内存访问,将磁盘内容发送到主存后再发送一个intercurpt型号。SSD使用闪存技术趋势,存储器尤其是DRAM 渐渐跟不上CPU的发展,差距越来愈大。随着单核性能趋近饱和,多核处理器出现,吞吐量成为另一个限制条件,而不再是延迟。
    • locality 。深刻而间接的原理——局部性。时间局部就是重复变量,空间就是步长越小的引用
    • memory hierarchy缓存实现的基本原理。通常将k+1层的数据c化为连续hunk成为block,与k层的cache交换都是以block作为一个传输单元。然后缓存命中就是k层中找到要访问数据,反之就是不命中。不命中有很多原因种类。冷不命中就是缓存开始时空的。然后由于很难做到随机任意替换的策略,也许是某种倍数一一映射替换策略可能导致冲突不命中。然后如果我们访问的工作集超过缓存的大小会发生容量不命中。寄存器文件由编译器管理,l1 l2 L3 缓存由硬件直接管理可以做到任意替换策略。主存由os 即虚拟内存。磁盘可能由分布式内存软件管理。 TLB 翻译后备缓存器由 硬件MMU管理。
    • 存储器结构实现 地址查询的实现本质是一种简单的哈希查询。S 组数,整个地址空间划分成组,每组有许多行,每行有一个有效位,标记为以及块,块中有好几位。 BxSxE ,依据每个组中的行数分三类,E=1 直接映射高速缓存 组选择,行匹配,字抽取
    • 理解划分,t,s,e 首先 s 划分组划分的是cache的组数 会比+1层的组数少,所以也许存在倍数映射,这是靠t标记位实现的,所以t+s真的唯一确定k+1内存块的位置。所以判断的 时候不仅要看有效位还要看t标识位。所以有时候会存在内存抖动问题就是冲突不命中刚好同一行反复驱逐
    • 组相联高速缓存 ,区别就是需要扫描标记位和有效位,因为同一组的所有行都有可能拥有有效数据,同样的在驱逐行时,也需要一定驱逐的策略。最近最少使用.eg
    • 全相连高速缓存 只有一个组,所以关键是在扫描标记位和有效位 需要并行搜索标记,所以一般用于小的 eg TLB
    • 写操作,两个类型。在写命中时,就是cache中有需要被写的对象时,直写就是直接改变内存和cache,写回就是改变cache,维护一个改变位,推迟改变内存。 在写不命中时也有两个,写分配就是写内存的同时把其加载到cache,非写分配就是不加载。通常我们考虑写回加写分配。 i-cache缓存指令只读,d-cache 缓存数据。总结上面的影响,cache大小。越大命中率越高,但访问时间越长。块大小,越大,空间局部性越好,时间局部性越差,因为行数越少越容易被替换,相连度,越大越复杂,命中使=时间越长,但是不命中处罚越低,
    • 应用 矩阵乘法优化。。
  • 链接

    • 编译驱动程序gcc 一套流水作业-v查看注意最后执行的时候会调用一个系统的加载器来复制到内存并转移控制ld 静态链接器在详细认识ld前要一下基本认识。ld两大基本任务。符号解析和重定位。symbol resolution and relocation .符号解析式将符号引用与符号定义关联。重定位是将符号定义与内存关联。大部分指令都有汇编器编译器确定好,ld仅仅奉命完成
    • 可执行文件,三种可重定位,可执行,共享(特殊的可重定位),linux是ELF,windows是PE,可重定位目标文件的格式。ELF头然后中间很多节(.session)然后是节头部表。.text 代码 .rodata .data .bss .symtab .rel.text .rel.data几个比较重要的 .bss 未初始化的全局静态 以及初始化为0的 节省空间函数就在.text里面显然
    • 符号表 符号:三种:模块m定义的全局符号(函数与全局变量),模块m引用的全局符号,m定义的局部符号(static函数和只被m引用的全局以及静态变量)注意所有局部变量都没有条目(栈)因此可以使用static 隐藏模块的函数(模块就是一个可执行二进制文件)。symtab 包含一个条目的结构数组对应可看书P469 readelf 程序可以看二进制文件
    • 符号解析,对于局部变量编译器确保唯一。编译器处理全局符号。对于全局符号,划分强弱,以处理重命名情况。 一,不允许有多个同名强符号。二,一个强多个弱同名,选择强,三,多个弱同名任意选择。函数与初始化了的全局变量为强,未初始化的全局变量为弱。 -fno-common 来使得不能生成common二唯一变量。我们使用了静态库技术。模块打包成库文件。对于实际链接时,加载器只会复制库文件中实际引用的部分,减少内存浪费。静态库的格式是archive.  .a 后缀 AR工具自己创建 。 –static 参数告诉驱动程序gcc生成一个完全链接的可执行文件,也就是说可以直接执行了,不需要动态链接。
    • 重定位 两步,将所有输入模块的节聚合然后分配到具体的运行时内存,唯一绝对的地址。(定义)。将引用是其指向正确的地址。重定位符号引用:PC相对引用和绝对引用两种。可执行文件,格式多了.init 入口点 组织成片,对齐。加载execve函数可以调用加载器。 运行时内存映像。0x400000开始本质是fork了一个子进程
    • 动态链接,动态链接器.so  DLL -shared 参数指示创建一个共享文件,-fpic 创建位置无关代码。共享库就是不复制模板引用的节,而是一些用于定位到 信息,等到实际执行的时候,再利用信息重定位。运行时动态链接dlopen接口打开,dlsym引用一个符号 dlclose 关闭 java也是类似使用c接口的
  • 进程

    • 进程就是一个运行的程序的实例。可以从功能上理解就是一个具体完成我们程序的过程。从组成上就是包括一系列的物理资源。包括内存,寄存器文件,控制器,内核栈等信息。总之就是上下文。

    • 异常,,就是逻辑控制流发生改变的情况,event事件也是如此。 异常发生在内核模式下。异常有中断interrupt 陷阱 trap 故障 终止。 终端就是硬件上,异步发生的。 trap 就是系统调用 system call  对于异常有三种情况 Icur 返回 Icur Inext 或者直接俄abort。 

    • shell 执行一个命令就是fork 了一个新的进程并发 就是多任务 多进程 很多错误都是有定义的 有硬件设计者或者是内核维护 同样的异常处理程序也是。 可以通过 errno来查看 函数就是 stderror(error)

    • 进程

      1
      2
      3
      4
      5
      6
      7
      8
      9
      #include<sys/types.h>
      #include<unistd.h>
      pid_t getpid(void);
      pid_t getppid(void);
      pid_t fork(void); // 子进程得到的是副本
      int execve(...);
      unsighed int sleep(ui);
      int pause(void);
      // 还有可以设置环境变量的sysm call env

      进程三个状态 运行 停止 终止 停止就是挂起 可以接受信号后继续运行 其中终止三种 接受到一个信号,主程序返回,exit函数

      理解子进程共享状态 关键: 副本虚拟内存的副本。仔细研究就是 相同的执行程序,内存,data等节。然后相同的用户栈信息,共享文件描述符,共享共享库也就是相同的工具 最大不同就是PID 子进程是0 父进程是子进程的PID返回对于fork 一次调用两次返回 注意这是系统调用 从内核返回到用户模式 并发执行父子进程 

      进程操作,主要是理解不用背。 未回收的进程就是僵尸进程交给init 进程 PID=1 所有进程的祖先 waitpid wait 可以等待并且有一定的行为可以操作   fork 和 exevce 就是实现 sh的关键 大概就是 fork 一个子进程然后进行 execve 执行 对应的命令

    • 信号 linux 信号 可能来自内核检测到一个系统事件(event) 也可能来自其他程序的kill发送 接受三种 忽略 终止 或者信号处理程序

    • strace 命令 可以使用-static 编译后查看程序中所有系统调用的轨迹 pmap 显示进程的内存映射 /proc 内核提供的一个可以在用户模式下查看系统信息的文件 还有/sys   execve 在内存里拥有一个内存栈 存储有argv 和 env 以及初始化函数的栈帧 非常的good

    • 信号

      信号是操作系统提供的内核。接受信号的物理实现 内核为每一个进程维护两个位向量,pending & blocked.规则:一个类型至多一个待处理信号,多余发送直接丢弃。进程可以 选择性阻塞信号,这样的信号不会被接收,但是能够发送,只要传送信号,pending就会置为,只要接受,pending就会清除,这里接受可以理解为成功捕获并响应。更准确的说是trap 陷入那一刻起就恢复,因此可以执行handler K 时 捕获k
      进程组 一般子进程会具有父进程的组pid。 job 一个前台job 很多个后台job job 就是一个进程组
      signal 函数可以修改信号默认行为 每个信号都有默认行为 sigkill sigstop 不可修改 
      编写信号处理程序的忠告 处理程序简单 调用异步安全函数sprintf 之类不安全。 保存errno 对全局变量访问期间要暂时阻塞信号 volatile声明防止缓存不一致 sig_atomic_t 声明变量 单变量原子操作

      Richard Stevens
      我们每次一个命令执行完后都会return main 所以当前进程会终止 然后被也许 init 或者父进程回收 子进程也会继承信号阻塞的信息向量 信号许多时候会造成竞争,所以我们也需要禁止某种操作,所以我们需要阻塞某些信号。 最后一个 sigsuspend 函数用于等待操作 详细请见 我们的P527 讨论
      非本地跳转setjump longjump 函数实现的 可以超过一般的处理程序调用的规则进行调用

  • 虚拟内存

    • 虚拟内存针对核心对象的是主存
    • 虚拟内存的核心思想就是把磁盘的所有字节划分为一个连续的数组构成一个虚拟地址空间。然后将主存划分为一个物理地址空间,将磁盘的内容缓存在主存上,类似于SRAM缓存体系,这个就是DRAM 缓存体系,很多地方是相同的。我们有操作系统维护一个于虚拟页数(就是缓存里面的块)相同的一个PT(page table 页表) PTE一个条目里可以简化为一个有效位标志是否被缓存,以及一个物理地址字段对应其被缓存在主存上的实际地址。一个page 有三个状态(未分配就相当于磁盘未被使用;分配未缓存,已经被分配例如使用了malloc,但是还没有被加载到主存中;已分配已缓存,此时有效位置位)。现在你直到了malloc 的实际用法。还有就是因为巨大的不命中处罚,所以一个虚拟页较大,并且全相联。同时按需调度,到最后一刻才真正加载到主存。 getrusage 函数可以查看缺页情况。

    虚拟内存如何实现一个统一的内存图景。这样想,我们任何一个程序的开始都一样,这个是虚拟地址,因为内核为每一个进程都维护了一个完整相同的独立的虚拟地址页表,并且虚拟地址要翻译为物理地址,所以啦,我们只需要在虚拟地址向物理地址的映射做一点手脚,我们认为控制这个映射就可以实现任意内存分配 简化加载,我们可以划出虚拟页表指向目标文件中的内容,这就是文件内存的映射,mmap 可以在应用层控制。 简化内存分配 malloc 实际的物理地址可以散落各处 实现内存保护。有几位标识读,写,执行权限。CPU 每次产生一个地址都需要查看PTE 所以在翻译的硬件MMU 中有一个缓存 TLB了解就好 翻译过程可以感兴趣再看看。还有就是使用了一个叫做多级页表的方式减少内存占用。

    图景的实现原理。内核为每一个进程都维护了一个虚拟地址空间。并将虚拟内存组织成区域也叫做段。例如数据段,代码段,共享库段。实际过程中内核为每一个进程记录一个task_struct 的结构数组,里面包含着进程上下文信息。其中mm_struct 记录了有关虚拟地址的信息。在这个结构中存在一个链表每个node 对应一个段的相关信息。 缺页处理,首先判断是否位于虚拟内存内,如果不在就触发一个段错误。1 .然后判断权限是否对应,如果错误 2.然后开始替换,然后返回到除法信号的指令再次翻译地址。注意虚拟内存上的分配与磁盘上的存储是两个不同概念。只有当前执行的时候我们才加载,而加载的实际含义语义其实是让它具有虚拟地址。让其映射到虚拟地址空间!!!!!!!!!好好理解。抽象成一个空间集合!!!!简洁而有效。

    memory mapping。 定义由上面其实页明白就是将一个虚拟内存区域与一个磁盘的对象关联起来,然后初始化这段虚拟内存的内容。映射磁盘对象的时候,有两种。对于Linux普通文件,将文件区分成片,映射到对应的虚拟内存页面。实际上并没有进入物理内存,而是使用按需调度机制。还有就是匿名文件,其实就是选择物理内存上一个牺牲页然后覆盖为0,实际上也没有磁盘的流量。 现在我们特殊讨论一下我们的共享对象。也就是映射的实际情况。首先,对于我们的这个共享对象,我们只需要一个物理内存中的副本,每一个进程可以在自己各自的不同的虚拟内存段上映射,同时每一个进程对于该共享对象的修改都是公开的。 同时还有私有对象,即每一个都是独立的。这种独立的实际事项方式是,先共享映射不过我们设置只读,当正在有探测到写入的时候,我们在复制该对象给写入的进程,尽量推迟从而提高效率。这就是写时复制技术。这也是fork 保持独立的实现原理。私有的,写时复制。execve函数就是一个删除现存虚拟内存重新映射的一个过程 然后mmap 函数可以让我们要求内核开辟虚拟内空间然后把我想要映射的对象关联起来。至于malloc 显式分配器的实践就是一个应用,可以参看书P587 .

  • 系统级I/O

    • 一切皆文件的终极体现。就是将所有的i/o设备抽象成文件,提供统一的文件接口。 打开文件得到一个唯一的文件描述符。0 stdin 1 stdout 2 stderr. k seek记录文件位置。读文件,读过大小限制后会自己触发一个EOF 而非有这个东西。文件。普通文件,套接字文件,目录。还有一些其他的。每个进程都会记录当前的工作目录,可以通过 cd 命令改变

    • #include<sys/types.h>
      #include<sys/stats.h>
      #include<fcntl.h>
      
      int open();
      int close();
      int read();
      int write();
      // Richard Stevens 和他的RIO包 R.I.P 缓存的使用减少系统trap
      // 针对不同的应用情景选择不同的i/o 自己写包装更好的尤其是网络编程
      //
      
    • 共享文件的实现,维护三个表,针对进程也有一些东西。同时fork的时候也是这样的。

  • 并行编程

  • 线程,信号量是实现对全局变量的访问。防止竞争。竞争可以使用进程图来直观表示。同时要防止死锁。还有很多,好几个基于线程进程,i/o复用的编程模型

  • Open MPI 和 mpch 似是两个不同的mpi 实现 我看的教程似乎是 mpich ,但是一般而言都可以如果只是学习的话 我不过上次我似乎安装的是 openmpi

  • MPI

    这里就把mpi的使用在这里写了。先补充一点前置知识。冯诺依曼体系。cpu主存分离。导致大多时钟时间去访存。进程就是一个程序的实例可以看成一个综合体包括I/o设备即一组文件描述符表,然后主存,前两者共同由虚拟地址实现。此为被处理器表现为独享。多任务即并发。每一个执行时间片。上下文切换。因此硬件计算的优化集中在对冯诺依曼体系的优化大致有如下几个。
    Cache 在主存与寄存器之间设置三层高速缓存,SRAM,利用局部性原理
    虚拟地址,可以看作讲主存作为磁盘文件的Cache.同时还有很多好处,如简化加载链接,提供更安全的地址守护等。
    指令级并行
    线程级并行TLP 细粒度多线程就是一个线程每执行他的一条指令就切换。粗粒度就是在遇到需要较长时间的指令才切换
    SMT 同步多线程
    SISD 单指令流 单数据流 SIMD 单数据多指令流 处理向量运算 大型简单计算GPU就是 处理图像 大量线程 具体可以在学习CUDA后补充
    MIMD 两种常见类型 注意有多个处理单元即多个处理器 是异步的没有全局时钟 一个是共享内存系统,多个核共享一个内存系统,分布式内存系统,多个核——内存对。第一类具体有两个
    分布式 最常见的就是cluster集群 以太网连接的一组PC就是 而每一台本身可能是共享内存所以称为混合系统
    互联网络,可以理解为连接结点的结构。性能依赖于信息读取传输,而这有由硬件的互联网络决定。
    共享内存系统中有两个 总线bus和交叉开关矩阵crossbar容易理解总线结构简单固定 ,小规模时高效,当结点增多可能出现阻塞,抢夺,因为大小是固定的,无法调整。
    CrossBar
    上图结构保证了不会出现信息覆盖
    分布式网络互联结构 其实就是互联网本身的一些结构了
    带宽是衡量网络传输速度的,宽度就是讲网络划分为两部分最少的同时通信数量
    延迟和带宽 两个指标
    共享一致性问题以及解决方案 首先由于Cache 当一个x的内存值改变时,另一个核中cache里缓存的值可能没变。两种方法解决,监听总线和用目录记录。伪共享问题与cache 命中有关 尤其与cache大小有关 当一个核的工作区恰好覆盖一个缓存时,那么就会发生进程间跳跃地对缓存覆盖,最终其实没有共享,反而增加不命中率。
    对于共享内存系统我们通常派生多线程,分布式我们派生多进程
    SPMD 单程序多数据流 if(thread0/process 0){}elif(1/1){}的结构
    共享内存中的问题:线程不确定性 通过 mutex 和 信号量来互斥实现 同时对于可重入函数的使用 对应的许多拥有static变量的函数就是线程不安全函数当多个线程调用时可能发生问题,解决方法可以是自己上锁或者调用对应库中的线程安全函数
    分布式中的问题:最多的API就是解决消息传递的。而且其也可以在共享内存中使用,原理就是逻辑上讲共享空间分割为多个独立空间有点像虚拟空间的操作 通常这样的API包含一个send 一个recv函数 然后rank来唯一表示进程 然后缓冲区区分 然后0对应stdout 以及一些广播和归约函数,最常见API MPI message passing interface
    输入输出问题 输入输出问题常常因为异步而具有不确定性这里有一些规范convention

    习惯总结而言就是没有任何两个文件标识符在实际输入输出时交叉,各自分组线程独自管理可扩展性 增加规模与同时增加核数线程数。效率不变
    计时通常指程序开始到结束的时间

    并行程序设计步骤Foster方法注意就是平均分配 同时要注意凝聚如果下一个依赖于上一个就可以凝聚为一个任务

    • MPI详解

      • MPI集群的搭建方法 感觉mpi 也逐渐成为其他更高层计算框架实现的底层原理
      • 通信子,通信子可以看作一组可以互相通信的进程,初始时有MPI创建了一组WORLD,可以创建多组。可以调用函数得知对应大小以及每一个的rank。
      • Recv 与 Send函数的语义。各自有自己的缓冲区其实就是指定的存储区。tag用于互相匹配。有status结构来实际获取。匹配包括:同一个communicator,rank匹配。tag匹配。传输信息type匹配。接受去内存大于发送区。对于接受函数有两个宏量。MPI_ANY_SOURCE MPI_ANY_TAG 字面意思就是可以任意接受。发送没有 且一定要指定好comm
      • MPI_Status参数获取实际传送的字节数。MPI_STATUS_IGNORE
      • Send语义,可以阻塞,此时不返回。可以缓冲,放入内部存储器,然后返回。返回时并不知道是否成功发送。实际是如果发送信息小于默认的截止大小就缓存,否则就阻塞。Recv一定阻塞。可能出现悬挂
      • 前面的是点对点 还有可以广播的集合通有内置的操作和我们自定义的operator 同样的还有信息的传播 API
      • 线程可以理解为轻量级进程 一个正在运行的程序在一个处理器上的实例 编译器的话需要一个-lpthread 参数
      • 注意我们的这个全局变量需要定义在所有的函数外面然后 main 就是一个主线程
      • 目前看来我们需要做到就是明白原理,然后学下具体的API  
  • CUDA

    • CPU 芯片,其实L3缓存占占据了最大的位置
      重要的任务就是判断是任务间是否独立 如果独立可能才可以分离task 就是指的一些指令和数据的集合
      task parallelism 关注多核上的函数并行 data parallelism 关注多核上的数据并行 cuda、主要解决data parallelism
      核利用率的问题
  • Post title:Begin_Again
  • Post author:Winter
  • Create time:2023-10-15 11:21:50
  • Post link:https://spikeihg.github.io/2023/10/15/Begin-Again/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.
if (hexo-config('comment.enable') == true && hexo-config('comment.use') != "") { if (hexo-config('comment.use') == "waline") { @require "./waline.styl" } else if (hexo-config('comment.use') == "gitalk") { @require "./gitalk.styl" } else if (hexo-config('comment.use') == "twikoo") { @require "./twikoo.styl" } } .comments-container { display inline-block margin-top $spacing-unit width 100% #comment-anchor { width 100% height 10px } .comment-area-title { width 100% margin 10px 0 font-size 1.38rem color var(--default-text-color) font-family "Noto Sans", "Noto Sans SC",sans-serif font-weight bold i { color var(--default-text-color) } +redefine-tablet() { margin 5px 0 font-size 1.2rem } } }