跳转至

Tutorials

Cuda Optimize

Outline

  1. General optimization guidance
    1. Coalescing memory operations
    2. Occupancy and latency hiding
    3. Using shared memory
  2. Example 1: transpose
    1. Coalescing and bank conflict avoidance
  3. Example 2: efficient parallel reductions
    1. Using peak performance metrics to guide optimization
    2. Avoiding SIMD divergence & bank conflicts
    3. Loop unrolling
    4. Using template parameters to write general-yet-optimized code
    5. Algorithmic strategy: Cost efficiency

CUDA 优化策略

基础

  1. 最大化并行独立性
  2. 最大化计算密度

减少数据传输

  1. 数据可以直接在GPU生成。
  2. 一次大传输也比分开的小批次快
zerocopy

如果我们数据只会在 GPU 产生和使用,我们不需要来回进行拷贝。

https://migocpp.wordpress.com/2018/06/08/cuda-memory-access-global-zero-copy-unified/

简而言之,在 host 使用命令:cudaHostRegisterMapped 之后用 cudaHostGetDevicePointer 进行映射 最后解除绑定 cudaHostUnregister

即,

// First, pin the memory (or cudaHostAlloc instead)
cudaHostRegister(h_a, , cudaHostRegisterMapped);
cudaHostRegister(h_b, , cudaHostRegisterMapped);
cudaHostRegister(h_c, , cudaHostRegisterMapped);

cudaHostGetDevicePointer(&a, h_a, 0);
cudaHostGetDevicePointer(&b, h_b, 0);
cudaHostGetDevicePointer(&c, h_c, 0);

kernel<<<...>>>(a, b, c);
cudaDeviceSynchronize();

// unpin/release host memory
cudaHostUnregister(h_a);
cudaHostUnregister(h_b);
cudaHostUnregister(h_c);
cuda warp shuffle

只要两个thread在 同一个warp中,允许thread直接读其他thread的寄存器值,这种比通过shared Memory进行thread间的通讯效果更好,latency更低,同时也不消耗额外的内存资源来执行数据交换。ref

访存连续性

  1. Optimize for spatial locality in cached texture memory ???
  2. 避免bank conflict: 如果没有bank冲突的话,共享内存的访存速度将会非常的快,大约比全局内存的访问延迟低100多倍,但是速度没有寄存器快。然而,如果在使用共享内存时发生了bank冲突的话,性能将会降低很多很多。
Global Memory:coalesced access

对齐(Starting address for a region must be a multiple of region size)集体访问,有数量级的差异Coalesced

利用好每个block里的thread,全部每个线程各自读取自己对齐(Starting address for a region must be a multiple of region size 不一定是自己用的)数据到shared memory开辟的总空间。由于需要的数据全部合力读取进来了,计算时正常使用需要的读入的数据。

特别是对于结构体使用SoA(structure of arrays)而不是AoS(array of structures), 如果结构体实在不能对齐, 可以使用 __align(X), where X = 4, 8, or 16.强制对齐。

对齐读取 float3 code

对于small Kernel和访存瓶颈的Kernel影响很大

由于需要对齐读取,3float是12字节,所以只能拆成三份。

有无采用对齐shared读取,有10倍的加速。

利用好Shared Memory

  1. 比globalMemory快百倍
  2. 可以来避免 non-Coalesced access
  3. SM的线程可以共享
  4. Use one / a few threads to load / compute data shared by all threads

隐藏延迟的方法

  1. 增加SM上线程数量,
  2. block数> SM数,这样所有的multiprocessors至少有一个block执行
  3. threads/block>128 。原因:机器上一般有最多4个Warp调度器=4*32=128
  4. threadsInblock=N*WarpSize=N*32
  5. 在 SM 上的 TB 越多越好,让 Thread Block 不停的跑我们的利用率就会高。
  6. 但是如果 Thread Block 太多,我们每一个 SM 能分配的寄存器就会变少,所以就会发生 Register Spill, 使用更高级的 L1、L2 Cache 去代替 Registers。所以 TB 不能太多,需要减少 Register Spill 的次数。
    1. 资源占用率不要太高(最多一半?
  7. 多使用 __syncthreads
  8. 最好的参数需要self-tuning出来

占用率高不一定是好事

占用率是指每个多处理器(Streaming Multiprocessor,SM)的实际的活动warps数量与最大理论的warps数量的比率。 高的占用率不一定能提升性能,因为这一般意味着每个线程分配的寄存器和shared memory变少。但低的占用率会导致内存延迟无法隐藏。

实际需要计算每个线程大概需要的shared memory和register数量

实际例子测试-待研究

https://www.cnblogs.com/1024incn/p/4541313.html

https://www.cnblogs.com/1024incn/p/4545265.html

优化实例1 - 矩阵转置

通过SMEM实现coalescing access

原本代码

_global__ void transpose_naive(float *odata, float *idata, int width, int height)
{
   unsigned int xIndex = blockDim.x * blockIdx.x + threadIdx.x;
   unsigned int yIndex = blockDim.y * blockIdx.y + threadIdx.y;
   if (xIndex < width && yIndex < height)
   {
      unsigned int index_in = xIndex + width * yIndex;
      unsigned int index_out = yIndex + height * xIndex;
      odata[index_out] = idata[index_in]; 
   }
}

思想:将大矩阵划分成方块,并且存储在SMEM里。不仅SMEM速度更快,而且每行元素个数变少,跨行访问的间距变小,局部性增强。而且对于大矩阵加速效果会更明显。

__global__ void transpose(float *odata, float *idata, int width, int height)
{
   __shared__ float block[BLOCK_DIM*BLOCK_DIM];
   unsigned int xBlock = blockDim.x * blockIdx.x;
   unsigned int yBlock = blockDim.y * blockIdx.y;
   unsigned int xIndex = xBlock + threadIdx.x;
   unsigned int yIndex = yBlock + threadIdx.y;
   unsigned int index_out, index_transpose;
   if (xIndex < width && yIndex < height)
   {
      unsigned int index_in = width * yIndex + xIndex;
      unsigned int index_block = threadIdx.y * BLOCK_DIM + threadIdx.x;
      block[index_block] = idata[index_in];
      index_transpose = threadIdx.x * BLOCK_DIM + threadIdx.y;
      index_out = height * (xBlock + threadIdx.y) + yBlock + threadIdx.x;
   }
   __syncthreads();
   if (xIndex < width && yIndex < height)
      odata[index_out] = block[index_transpose]
}

coalescing access

when Block/tile dimensions are multiples of 16 ???

关于bank conflict

https://developer.nvidia.com/blog/efficient-matrix-transpose-cuda-cc/

对于一个32 × 32个元素的共享内存块,一列数据中的所有元素都映射到相同的SMEM bank ,导致bank conflict 的最坏情况:读取一列数据会导致32路的存储库冲突。

幸运的是,只需要将tile的元素宽度改为33,而不是32就行。

优化实例2 - 数据归约

具体问题:将长数组的所有元素,归约求和为一个结果。12

总体思路

为了避免全局同步的巨大开销,采取分级归约

由于归约的计算密度低 1 flop per element loaded (bandwidth-optimal)

所以优化目标是将访存带宽用满。

384-bit memory interface, 900 MHz DDR
384 * 1800 / 8 = 86.4 GB/s

step0 : baseline - Interleaved Addressing 交错/间隔寻址

__global__ void reduce0(int *g_idata, int *g_odata) {
   extern __shared__ int sdata[];

   // each thread loads one element from global to shared mem
   unsigned int tid = threadIdx.x;
   unsigned int i = blockIdx.x*blockDim.x + threadIdx.x;
   sdata[tid] = g_idata[i];
   __syncthreads();

   // do reduction in shared mem
   for(unsigned int s=1; s < blockDim.x; s *= 2) {
      if (tid % (s) == 0) {
         sdata[tid] += sdata[tid + s];
      }
      __syncthreads();
   }

   // write result for this block to global mem
   if (tid == 0) g_odata[blockIdx.x] = sdata[0];
}

工作的线程越来越少。一开始是全部,最后一次只有thread0.

Step1 : 使用连续的index

Just replace divergent branch With strided index and non-divergent branch,但是会带来bank conflict。

原理和Warp发射有关,假如在这里每个Warp并行的线程是2。一个Warp运行耗时为T.

Step0: 4+4+2+1=11T

Step1: 4+2+1+1=8T

for (unsigned int s=1; s < blockDim.x; s *= 2) {
   int index = 2 * s * tid;
   if (index < blockDim.x) {
      sdata[index] += sdata[index + s];
   }
   __syncthreads();
}

Step2: 连续寻址

for (unsigned int s=blockDim.x/2; s>0; s>>=1) {
   if (tid < s) {
      sdata[tid] += sdata[tid + s];
   }
   __syncthreads();
}

原本寻址

现在寻址有一边连续了

Step3 : 弥补浪费的线程

方法: 在load SMEM的时候提前做一次规约加法,通过减少一半的block数,将原本两个block里的值load+add存储在sum里。

// perform first level of reduction,
// reading from global memory, writing to shared memory
unsigned int tid = threadIdx.x;
unsigned int i = blockIdx.x*(blockDim.x*2) + threadIdx.x;
sdata[tid] = g_idata[i] + g_idata[i+blockDim.x];
__syncthreads();

step4 : Unrolling the Last Warp

当s< 32的时候,就只有一个Warp工作了。

使用warp的SIMD还省去了__syncthreads()的麻烦

for (unsigned int s=blockDim.x/2; s>32; s>>=1) 
{
   if (tid < s)
      sdata[tid] += sdata[tid + s];
   __syncthreads();
}
if (tid < 32)
{
   sdata[tid] += sdata[tid + 32]; 
   sdata[tid] += sdata[tid + 16]; 
   sdata[tid] += sdata[tid + 8]; 
   sdata[tid] += sdata[tid + 4]; 
   sdata[tid] += sdata[tid + 2]; 
   sdata[tid] += sdata[tid + 1]; 
}

为了保持整洁,最后一个if还做了无效的计算。eg, Warp里的最后一个线程只有第一句命令有用。

Step5 : 根据blockSize完全展开for和去除代码

由于for循环里是二分的,而且小于32的单独处理了,导致for循环里实际运行代码最多就3句。

利用代码模板和编译器的自动优化实现:

template <unsigned int blockSize>
__global__ void reduce5(int *g_idata, int *g_odata)

红色代码会在编译时自动优化。

step6 :归并算法优化

加速级联??

Cost= processors × time complexity

我们知道N个元素直接二叉树归约是O(log N) 时间 Cost=N*O(log N).

但是假如只有P个线程先做N/P的串行加法, 然后是log(P)的归约。 总cost=P(N/P+log(P))

当P=N/log(N), cost=O(N)

each thread should sum O(log n) elements来设置

比如,1024 or 2048 elements per block vs. 256 线程。每个sum n=4个元素。 具体参数要perf

unsigned int tid = threadIdx.x;
unsigned int i = blockIdx.x*(blockSize*2) + threadIdx.x;
unsigned int gridSize = blockSize*2*gridDim.x;
sdata[tid] = 0;
while (i < n) {
   sdata[tid] += g_idata[i] + g_idata[i+blockSize];
   i += gridSize;
}
__syncthreads();

final code

template <unsigned int blockSize>
__global__ void reduce6(int *g_idata, int *g_odata, unsigned int n)
{
   extern __shared__ int sdata[];

   unsigned int tid = threadIdx.x;
   unsigned int i = blockIdx.x*(blockSize*2) + tid;
   unsigned int gridSize = blockSize*2*gridDim.x;
   sdata[tid] = 0;

   do { sdata[tid] += g_idata[i] + g_idata[i+blockSize]; i += gridSize; } while (i < n);
   __syncthreads();

   if (blockSize >= 512) { if (tid < 256) { sdata[tid] += sdata[tid + 256]; } __syncthreads(); }
   if (blockSize >= 256) { if (tid < 128) { sdata[tid] += sdata[tid + 128]; } __syncthreads(); }
   if (blockSize >= 128) { if (tid < 64) { sdata[tid] += sdata[tid + 64]; } __syncthreads(); }

   if (tid < 32) {
      if (blockSize >= 64) sdata[tid] += sdata[tid + 32];
      if (blockSize >= 32) sdata[tid] += sdata[tid + 16];
      if (blockSize >= 16) sdata[tid] += sdata[tid + 8];
      if (blockSize >= 8) sdata[tid] += sdata[tid + 4];
      if (blockSize >= 4) sdata[tid] += sdata[tid + 2];
      if (blockSize >= 2) sdata[tid] += sdata[tid + 1];
   }
   if (tid == 0) g_odata[blockIdx.x] = sdata[0];
}

关于if语句的补充

有if语句是没问题的,只要运行的时候全部执行if或者else就行。不要有些执行if,有些执行else,这才会等待。

说不定也不是全部执行if或者else就行,只需要连续32个Thread Index,是相同的执行就行。(猜想,需要测试。

关于延迟隐藏

通过增加block里的线程数,并且同时读取来隐藏延迟。 不仅可以隐藏Global Memory的延迟,还可以隐藏写后读的延迟

线程资源查看

线程太多会导致分配到每一个的寄存器和SMEM变少

通过编译时加-cubin选项,.cubin文件前几行会显示

architecture {sm_10}
abiversion {0}
modname {cubin}
code {
   name = BlackScholesGPU
   lmem = 0    # per thread local memory
   smem = 68   # per thread block shared memory
   reg = 20    # per thread registers

参考文献

Nvprof

安装

$ which nvprof 
/usr/local/cuda/bin/nvprof

基本使用

摘要模式

命令行直接运行

nvprof ./myApp

跟踪API

nvprof --print-gpu-trace ./myApp

保存在log里

sudo /usr/local/cuda/bin/nvprof --log-file a.log --metrics achieved_occupancy /staff/shaojiemike/github/cutests/22-commonstencil/common

可视化

  1. nsight可以直接在远程机器上运行
  2. ssh -X host
  3. .ssh/config
    1. add
    2. XAuthLocation /opt/X11/bin/xauth #for macbookAir
    3. ForwardX11Trusted yes
    4. ForwardX11 yes
  4. Visual Profiler也可以ssh直接连接远程机器
  5. 或者导出分析结果以便可视化, 在Visual Profiler使用
nvprof --export-profile timeline.prof <app> <app args>
nvprof --analysis-metrics -o  nbody-analysis.nvprof ./myApp

profile kernel

sudo /usr/local/cuda/bin/ncu -k stencil_kernel -s 0 -c 1 /staff/shaojiemike/github/cutests/22-commonstencil/best

ncu-ui是可视化界面,但是没弄懂

带宽profile

上限测量

# shaojiemike @ snode0 in ~/github/cuda-samples-11.0 [16:02:08]                                                                                                                                                                      $ ./bin/x86_64/linux/release/bandwidthTest                                                                                                                                                                                           [CUDA Bandwidth Test] - Starting...                                                                                                                                                                                                  Running on...                                                                                                                                                                                                                                                                                                                                                                                                                                                              Device 0: Tesla P40                                                                                                                                                                                                                  Quick Mode                                                                                                                                                                                                                                                                                                                                                                                                                                                                Host to Device Bandwidth, 1 Device(s)                                                                                                                                                                                                PINNED Memory Transfers                                                                                                                                                                                                                Transfer Size (Bytes)        Bandwidth(GB/s)                                                                                                                                                                                         32000000                     11.8                                                                                                                                                                                                                                                                                                                                                                                                                                       Device to Host Bandwidth, 1 Device(s)                                                                                                                                                                                                PINNED Memory Transfers                                                                                                                                                                                                                Transfer Size (Bytes)        Bandwidth(GB/s)                                                                                                                                                                                         32000000                     13.0                                                                                                                                                                                                                                                                                                                                                                                                                                       Device to Device Bandwidth, 1 Device(s)                                                                                                                                                                                              PINNED Memory Transfers                                                                                                                                                                                                                Transfer Size (Bytes)        Bandwidth(GB/s)                                                                                                                                                                                         32000000                     244.3                                                                                                                                                                                                                                                                                                                                                                                                                                     Result = PASS                                                                                                                                                                                                                                                                                                                                                                                                                                                             NOTE: The CUDA Samples are not meant for performance measurements. Results may vary when GPU Boost is enabled.                                                                                                                                                                                       # shaojiemike @ snode0 in ~/github/cuda-samples-11.0 [16:03:24]                                                                                                                                                                      $ ./bin/x86_64/linux/release/p2pBandwidthLatencyTest        

实际值

nvprof通过指定与dram,L1或者L2 的metrics来实现。具体解释可以参考官网

在 Maxwell 和之后的架构中 L1 和 SMEM 合并

Metric Name 解释
achieved_occupancy 活跃cycle是 Warps 活跃的比例
dram_read_throughput
dram_utilization 在0到10的范围内,相对于峰值利用率,设备内存的利用率水平
shared_load_throughput
shared_utilization
l2_utilization

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

Hugo

  1. Hugo is a Go-based static site generator known for its speed and flexibility in 2013.
  2. Hugo has set itself apart by being fast. More precisely, it has set itself apart by being much faster than Jekyll.
  3. Jekyll uses Liquid as its templating language. Hugo uses Go templating. Most people seem to agree that it is a little bit easier to learn Jekyll’s syntax than Hugo’s.1

Servers

通过IPMI芯片的静态IP远程重启和配置机器

https://cloud.tencent.com/developer/article/1448642

Group

当前组

shaojiemike@snode6:~$ groups shaojiemike
shaojiemike : staff sudo

所有组

cat /etc/group

User

whoami

一般用户位置

/etc/passwd

LDAP教程

如果发现自己不在/etc/passwd里,很可能使用了ldap 集中身份认证。可以在多台机器上实现分布式账号登录,用同一个账号。

 getent passwd 

first reboot server

ctrl + alt + F3     #jump into command line
login
su - {user-name}
sudo -s
sudo -i
# If invoked without a user name, su defaults to becoming the superuser
ip a |less          #check ip address fjw弄了静态IP就没这个问题了

限制当前shell用户爆内存

宕机一般是爆内存,进程分配肯定会注意不超过物理核个数。

在zshrc里写入 25*1024*1024 = 25GB的内存上限

ulimit -v 26214400
当前shell程序超内存,会输出Memory Error结束。

测试读取200GB大文件到内存

with open("/home/shaojiemike/test/DynamoRIO/OpenBLASRawAssembly/openblas_utest.log", 'r') as f:
    data= f.readlines()
    print(len(data))

有文章说Linux有些版本内核会失效

OpenLDAP

OpenLDAP

分布式、多平台集成认证系统

ibug在实验室机器整活还行

https://ibug.io/blog/2022/03/linux-openldap-server/

https://harrychen.xyz/2021/01/17/openldap-linux-auth/

https://www.cnblogs.com/dufeixiang/p/11624210.html

改shell

复杂还有bug,我还是改profile吧

https://ibug.io/blog/2022/03/linux-openldap-server/#user-chsh

挂载

挂在同一个地方,肯定是一样的

# shaojiemike @ snode2 in ~ [20:18:20]
$ df -h .
Filesystem       Size  Used Avail Use% Mounted on
10.1.13.1:/home   15T   11T  3.1T  78% /staff

# shaojiemike @ snode0 in ~ [20:25:51]
$ mount|grep staff
10.1.13.1:/home on /staff type nfs4 (rw,relatime,vers=4.2,rsize=1048576,wsize=1048576,namlen=255,soft,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=10.1.13.50,local_lock=none,addr=10.1.13.1)

tmpfs是磁盘里的虚拟内存的意思。

设置

具体设置要登录到中央机器上去

# shaojiemike @ hades1 in ~ [20:41:06]
$ cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 hades1
# 222.195.72.30 hades0
# 202.38.72.64 hades1
# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

114.214.198.26  synology
10.1.13.1       acsa-nfs
10.1.13.6       discovery
10.1.13.50      snode0
10.1.13.51      snode1
10.1.13.52      snode2
10.1.13.53      snode3
10.1.13.54      snode4
10.1.13.55      snode5
10.1.13.56      snode6
10.1.13.114     swabl
10.1.13.119     node19
10.1.13.102     node2
10.1.13.58      hades0
10.1.13.57      hades1
# shaojiemike @ snode0 in ~ [20:36:26]
$ sudo cat /etc/nslcd.conf
# /etc/nslcd.conf
# nslcd configuration file. See nslcd.conf(5)
# for details.

# The user and group nslcd should run as.
uid nslcd
gid nslcd

# The location at which the LDAP server(s) should be reachable.
uri ldaps://ldap.swangeese.fun

需要进一步的研究学习

  1. 总共涉及几台机器

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

Python MPI

全局解释器锁(GIL,Global Interpreter Lock)

Python代码的执行由Python虚拟机(解释器)来控制。

对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同时只有一个线程在运行。所以就会出现尽管你设置了多线程的任务,但是只能跑一个的情况。

但是I/O密集的程序(爬虫)相对好一点,因为I/O操作会调用内建的操作系统C代码,所以这时会释放GIL锁,达到部分多线程的效果。

通常我们用的解释器是官方实现的CPython,要真正利用多核,除非重写一个不带GIL的解释器。

Python 鸡肋的多线程

from threading import Thread

Python 多进程正常实现

通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。

from multiprocessing import Process

子进程调用实例

def TIMEOUT_COMMAND(command, timeout=10):
    """call shell-command and either return its output or kill it
    if it doesn't normally exit within timeout seconds and return None"""
    import subprocess, datetime, os, time, signal
    cmd = command.split(" ")
    start = datetime.datetime.now()
    process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,encoding="utf-8",preexec_fn=os.setsid) #让 Popen 成立自己的进程组
    # https://www.cnblogs.com/gracefulb/p/6893166.html
    # 因此利用这个特性,就可以通过 preexec_fn 参数让 Popen 成立自己的进程组, 然后再向进程组发送 SIGTERM 或 SIGKILL,中止 subprocess.Popen 所启动进程的子子孙孙。当然,前提是这些子子孙孙中没有进程再调用 setsid 分裂自立门户。
    ic("BHive-noUseOSACA-before",process.pid,process.poll())
    while process.poll() != 208: # poll()(好像BHive208是正常结束)返回0 正常结束, 1 sleep, 2 子进程不存在,-15 kill,None 在运行
        ic("BHive-noUseOSACA-During",process.pid,process.poll())
        time.sleep(0.2)
        now = datetime.datetime.now()
        if (now - start).seconds> timeout:
            # BHive有子进程,需要杀死进程组。但是需要新生成进程组,不然会把自己kill掉
            os.killpg(os.getpgid(process.pid), signal.SIGKILL)
            # os.killpg(process.pid, signal.SIGTERM) SIGTERM不一定会kill,可能会被忽略,要看代码实现
            # https://blog.csdn.net/zhupenghui176/article/details/109097737
            # os.waitpid(-1, os.WNOHANG)
            (killPid,killSig) = os.waitpid(process.pid, 0)
            if killPid != process.pid or killSig!=9:
                errorPrint("TIMEOUT_COMMAND kill failed! killPid %d process.pid %d killSig %d" % (killPid, process.pid, killSig))
            ic("Killed",process.pid,process.poll())
            return None
    ic("BHive-noUseOSACA-Finished",process.pid,process.poll())
    return process.stdout.readlines()

使用Queue或者Pipe通讯

https://github.com/Kirrito-k423/BHive-Prediction-Compare/blob/main/pythonTest/0326_newBar_qcjiang.py

多核的解决方法:调用C语言的链接库

把一些计算密集型任务用C语言编写,然后把.so链接库内容加载到Python中,因为执行C代码,GIL锁会释放,这样一来,就可以做到每个核都跑一个线程的目的!

进程池

from mpi4py import MPI

子进程实例

python的子程序实现有问题,运行中,会有bhive-reg遗留下来(多达20个,需要按照下面手动kill,这也是核数建议为总核数的1/3的原因
### check process create time
ps -eo pid,lstart,cmd |grep bhive
date
### kill all process by name
 sudo ps -ef | grep 'bhive-re' | grep -v grep | awk '{print $2}' | sudo xargs -r kill -9
### 以为的原因 subProcess.pool 返回程序状态的时候,除了运行和结束状态,还有休眠等其他状态。也就是程序在发射之后并不是直接进入运行状态的。判断程序是否超时不能通过判断是否运行,因为一开始while循环进不去
while process.poll() is None:
而应该是判断是否正常结束(208是BHive结束返回值,不同程序不同)
while process.poll() != 208:
### 继续分析 实际debug还是有 ![](https://pic.shaojiemike.top/img/20220625173740.png) 在debug输出里没有这些pid check了,输出的个数是符合的。 不懂了,我都没调用,这僵尸进程哪里来的?除非是BHive产生的。 ### 实际原因 调用的Bhive会产生子进程,原本的python实现不能杀死子进程的子进程。需要改用杀死进程组的实现 ### 杀死进程组 ![](https://pic.shaojiemike.top/img/20220625185611.png) 可能设定是timeout是20秒,但是htop程序运行了2分钟也没有kill。这是正常的,因为主程序挤占资源导致挂起了,导致无法及时判断和kill

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

Pip

发布自己的pip包

一、注册自己的 PyPi 帐号

也可以是自己搭的 PyPi私服仓库的账号

二、依据自己的项目目录,创建setup.py

如下图所示,agent_cli是我整体的项目,我想要将pip-test目录下的代码打包上传到Pypi仓库中;

在pip-test的同级目录,创建setup.py文件

三、 编写自己的setup.py文件

from setuptools import setup, find_packages

setup(
    name='tsjPython',  # 打包后的包文件名
    version='0.1',  # 版本号
    keywords=("tsj"),    # 关键字
    description='personal code for tsj',  # 说明
    long_description="none",  # 详细说明
    license="MIT Licence",  # 许可
    url='http://home.ustc.edu.cn/~shaojiemike',
    author='Shaojie Tan',
    author_email='[email protected]',
    # packages=find_packages(),     #这个参数是导入目录下的所有__init__.py包
    include_package_data=True,
    platforms="any",
    install_requires=['termcolor',
                      'plotille'],    # 引用到的第三方库
    # py_modules=['pip-test.DoRequest', 'pip-test.GetParams', 'pip-test.ServiceRequest',
    #             'pip-test.ts.constants', 'pip-test.ac.Agent2C',
    #             'pip-test.ts.ttypes', 'pip-test.ac.constants',
    #             'pip-test.__init__'],  # 你要打包的文件,这里用下面这个参数代替
    packages=['tsjPython']  # 这个参数是导入目录下的所有__init__.py包
)

四、打包自己的项目

执行下述两条命令

python setup.py build    #执行此命令后,会生成上面图片中build的目录,目录层级是  build/lib/pip-test,  pip-test目录下就是你打包文件解压后的结果,可以在此查看打包的代码是否完整

python setup.py sdist    # 执行此命令后,就会在dist目录下生成压缩包文件 .tar.gz

五、上传到PyPi服务器

在上传前,要建一个文件,$HOME/.pypirc$HOME目录在linux或者mac系统下就是~/目录。在这里建一个 .pypirc文件。里边的内容如下:

[distutils]
index-servers = pypi

[pypi]
username:你的PyPi用户名
password:你的PyPi密码

或者使用token.

注册好之后,生成一个 token:https://pypi.org/manage/account/#api-tokens。由于我们是要上传新项目,所以不要限制 scope 到特定的项目。

[pypi]
username = __token__
password = <换成你刚刚复制下来的 token, 包括 `pypi-` 前缀>

提前安装 py -m pip install --upgrade twine

执行此命令: twine upload dist/XXXXX-0.1.0.tar.gz 上传你刚刚打包好的压缩包

六、安装使用

使用 pip install XXX 就可以轻松使用

问题

requests.exceptions.ProxyError: HTTPSConnectionPool(host='upload.pypi.org', port=443): Max retries exceeded with url: /legacy/ (Caused by ProxyError('Cannot connect to proxy.', OSError(0, 'Error')))

不要挂代理

开发测试包

如果为了测试而上传代码,会占用版号。

可以通过先安装在本地来实现

pip install packageName
pip uninstall packageName
使用 setup.py 安装的包会被复制到当前环境的 site-packages 目录下,这意味着,一旦我们修改了包的源代码,就需要重新安装它。

这常常是集中开发过程中的一个问题,因为很容易忘记需要再次执行安装,这就是为什么 setuptools 提供了一个额外的 develop 命令的重要原因。

develop 命令的完整格式为Python setup.py develop,该命令允许我们在开发模式下安装包,它会在部署目录(site-packages)时创建一个指向项目源代码的特殊链接,而不是将整个包复制过去,从而实现在编辑包的源代码之后无需再重新安装,并且它在 sys.path 中可用,就像正常安装一样。

pip 也支持用这种模式来安装包,这个安装选项叫作可编辑模式,可以使用 install 命令的 -e 参数来启用,代码格式如下: pip install -e 包路径

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

https://www.cnblogs.com/weisunblog/p/13307174.html

OpenMP

线程绑定

OpenMP 4.0 提供 OMP_PLACESOMP_PROC_BIND 环境变量来指定程序中的 OpenMP 线程如何绑定到处理器。这两个环境变量通常结合使用。OMP_PLACES 用于指定线程将绑定到的计算机位置(硬件线程、核心或插槽)。OMP_PROC_BIND 用于指定绑定策略(线程关联性策略),这项策略指定如何将线程分配到位置。

除了 OMP_PLACESOMP_PROC_BIND 这两个环境变量外,OpenMP 4.0 还提供可在 parallel 指令中使用的 proc_bind 子句。proc_bind 子句用于指定如何将执行并行区域的线程组绑定到处理器。

SlURM MPI OpenMP绑定方法参考清华的文档

OMP_NUM_THREADS=28 OMP_PROC_BIND=true OMP_PLACES=cores:每个线程绑定到一个 core,使用默认的分布(线程 n 绑定到 core n);
OMP_NUM_THREADS=2 OMP_PROC_BIND=true OMP_PLACES=sockets:每个线程绑定到一个 socket;
OMP_NUM_THREADS=4 OMP_PROC_BIND=close OMP_PLACES=cores:每个线程绑定到一个 core,线程在 socket 上连续分布(分别绑定到 core 0,1,2,3;
OMP_NUM_THREADS=4 OMP_PROC_BIND=spread OMP_PLACES=cores:每个线程绑定到一个 core,线程在 socket 上尽量散开分布(分别绑定到 core 0,7,14,21;
lscpu结合htop观察
NUMA 节点0 CPU:                 0-15,32-47              
NUMA 节点1 CPU:                 16-31,48-63

编译制导格式

静态扩展 * 文本代码在一个编译制导语句之后,被封装到一个结构块中

孤立语句 * 一个OpenMP的编译制导语句不依赖于其它的语句

parallel

并行域中的代码被所有的线程执行

for

for语句指定紧随它的循环语句必须由线程组并行执行;

sections

sections编译制导语句指定内部的代码被划分给线程组中的各线程

不同的section由不同的线程执行

single

single编译制导语句指定内部代码只有线程组中的一个线程执行。

线程组中没有执行single语句的线程会一直等待代码块的结束,使用nowait子句除外

来自 https://ppc.cs.aalto.fi/ch3/nowait/

组合parallel for / parallel sections 编译制导语句

  1. Parallel for编译制导语句表明一个并行域包含一个独立的for语句
  2. parallel sections编译制导语句表明一个并行域包含单独的一个sections语句

同步结构

  1. master 制导语句
  2. 指定代码段只有主线程执行
  3. critical制导语句
  4. critical制导语句表明域中的代码一次只能执行一个线程,其他线程被阻塞在临界区
  5. 语句格式:#pragma omp critical [name] newline
  6. barrier制导语句
  7. 同步一个线程组中所有的线程,先到达的线程在此阻塞,等待其他线程
  8. atomic制导语句
  9. 指定特定的存储单元将被原子更新
  10. #pragma omp atomic x++;
  11. flush制导语句
  12. 标识一个同步点,用以确保所有的线程看到一致的存储器视图
  13. ordered制导语句
  14. 相对于critical,多了一个顺序
  15. 只能出现在for或者parallel for语句的动态范围中
  16. threadprivate语句使一个全局文件作用域的变量在并行域内变成每个线程私有
  17. 每个线程对该变量复制一份私有拷贝

critical vs atomic

The fastest way is neither critical nor atomic. Approximately, addition with critical section is 200 times more expensive than simple addition, atomic addition is 25 times more expensive then simple addition.(maybe no so much expensive, the atomic operation will have a few cycle overhead (synchronizing a cache line) on the cost of roughly a cycle. A critical section incurs the cost of a lock.)

The fastest option (not always applicable) is to give each thread its own counter and make reduce operation when you need total sum.

critical vs ordered

omp critical is for mutual exclusion(互斥), omp ordered refers to a specific loop and ensures that the region executes sequentually in the order of loop iterations. Therefore omp ordered is stronger than omp critical, but also only makes sense within a loop.

omp ordered has some other clauses, such as simd to enforce the use of a single SIMD lane only. You can also specify dependencies manually with the depend clause.

Note: Both omp critical and omp ordered regions have an implicit memory flush at the entry and the exit.

ordered example

vector<int> v;

#pragma omp parallel for ordered schedule(dynamic, anyChunkSizeGreaterThan1)
    for (int i = 0; i < n; ++i){
            ...
            ...
            ...
#pragma omp ordered
            v.push_back(i);
    }
tid  List of     Timeline
     iterations
0    0,1,2       ==o==o==o
1    3,4,5       ==.......o==o==o
2    6,7,8       ==..............o==o==o

= shows that the thread is executing code in parallel. o is when the thread is executing the ordered region. . is the thread being idle, waiting for its turn to execute the ordered region.

With schedule(static,1) the following would happen:

tid  List of     Timeline
     iterations
0    0,3,6       ==o==o==o
1    1,4,7       ==.o==o==o
2    2,5,8       ==..o==o==o

语句绑定与语句嵌套规则

Clauses 子句

见 https://docs.microsoft.com/en-us/cpp/parallel/openmp/reference/openmp-clauses?view=msvc-160

#pragma omp parallel for collapse(2)
for( int y = y1; y < y2; y++ )
{
    for( int x = x1; x < x2; x++ )
    {

schedule

------------------------------------------------
| static | static | dynamic | dynamic | guided |
|    1   |    5   |    1    |    5    |        |
------------------------------------------------
|    0   |    0   |    0    |    2    |    1   |
|    1   |    0   |    3    |    2    |    1   |
|    2   |    0   |    3    |    2    |    1   |
|    3   |    0   |    3    |    2    |    1   |
|    0   |    0   |    2    |    2    |    1   |
|    1   |    1   |    2    |    3    |    3   |
|    2   |    1   |    2    |    3    |    3   |
|    3   |    1   |    0    |    3    |    3   |
|    0   |    1   |    0    |    3    |    3   |
|    1   |    1   |    0    |    3    |    2   |
|    2   |    2   |    1    |    0    |    2   |
|    3   |    2   |    1    |    0    |    2   |
|    0   |    2   |    1    |    0    |    3   |
|    1   |    2   |    2    |    0    |    3   |
|    2   |    2   |    2    |    0    |    0   |
|    3   |    3   |    2    |    1    |    0   |
|    0   |    3   |    3    |    1    |    1   |
|    1   |    3   |    3    |    1    |    1   |
|    2   |    3   |    3    |    1    |    1   |
|    3   |    3   |    0    |    1    |    3   |
------------------------------------------------

private vs firstprivate vs lastprivate

private variables are not initialised, i.e. they start with random values like any other local automatic variable

firstprivate initial the value as the before value.

lastprivate save the value to the after region. 这个last的意思不是实际最后运行的一个线程,而是调度发射队列的最后一个线程。从另一个角度上说,如果你保存的值来自随机一个线程,这也是没有意义的。 firstprivate and lastprivate are just special cases of private

#pragma omp parallel
{
   #pragma omp for lastprivate(i)
      for (i=0; i<n-1; i++)
         a[i] = b[i] + b[i+1];
}
a[i]=b[i];

private vs threadprivate

A private variable is local to a region and will most of the time be placed on the stack. The lifetime of the variable's privacy is the duration defined of the data scoping clause. Every thread (including the master thread) makes a private copy of the original variable (the new variable is no longer storage-associated with the original variable).

A threadprivate variable on the other hand will be most likely placed in the heap or in the thread local storage (that can be seen as a global memory local to a thread). A threadprivate variable persist across regions (depending on some restrictions). The master thread uses the original variable, all other threads make a private copy of the original variable (the master variable is still storage-associated with the original variable).

task 指令

可以指定某一task任务在指定第几个thread运行吗?

section 命令 与 for 命令的区别

简单理解sections其实是for的展开形式,适合于少量的“任务”,并且适合于没有迭代关系的“任务”。每一个section被一个线程去执行。

常用函数

omp_get_thread_num() //获取线程的num,即ID。在并行区域外,获取的是master线程的ID,即为0。
omp_get_num_threads/omp_set_num_threads()  //设置/获取线程数量,用于覆盖OMP_NUM_THREADS环境变量的设置。omp_set_num_threads在串行区域调用才会有效,omp_get_num_threads获取当前线程组的线程数量,一般在并行区域调用,在串行区域调用返回为1。
omp_get_max_threads() //返回OpenMP当前环境下能创建线程的最大数量。

环境变量

OMP_SCHEDULE:只能用到for,parallel for中。它的值就是处理器中循环的次数
OMP_NUM_THREADS:定义执行中最大的线程数
OMP_DYNAMIC:通过设定变量值TRUE或FALSE,来确定是否动态设定并行域执行的线程数
OMP_NESTED:确定是否可以并行嵌套

例子

#include <omp.h>

int main(int argc, _TCHAR* argv[])  
{
    printf("ID: %d, Max threads: %d, Num threads: %d \n",omp_get_thread_num(), omp_get_max_threads(), omp_get_num_threads());
    omp_set_num_threads(5);
    printf("ID: %d, Max threads: %d, Num threads: %d \n",omp_get_thread_num(), omp_get_max_threads(), omp_get_num_threads());

#pragma omp parallel num_threads(5)
    {
        // omp_set_num_threads(6);  // Do not call it in parallel region
        printf("ID: %d, Max threads: %d, Num threads: %d \n",omp_get_thread_num(), omp_get_max_threads(), omp_get_num_threads());
    }

    printf("ID: %d, Max threads: %d, Num threads: %d \n",omp_get_thread_num(), omp_get_max_threads(), omp_get_num_threads());

    omp_set_num_threads(6);
    printf("ID: %d, Max threads: %d, Num threads: %d \n",omp_get_thread_num(), omp_get_max_threads(), omp_get_num_threads());

    return 0;  
}

OpenMP和pthread是常见的模型

♦OpenMP为循环级并行提供了方便的功能。线程由编译器根据用户指令创建和管理。

♦pthread提供了更复杂、更动态的方法。线程由用户显式创建和管理。

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

对子句和制导的关系不清楚

参考文献

https://blog.csdn.net/gengshenghong/article/details/7004594

https://docs.microsoft.com/en-us/cpp/parallel/openmp/reference/openmp-clauses?view=msvc-160