跳转至

2022

SSHForward

导言

服务器没网,姜师兄说可以ssh转发网络请求到本地windows

四类ssh转发

SSH 端口转发自然需要 SSH 连接,而SSH 连接是有方向的,从 SSH Client 到 SSH Server 。

而我们所要访问的应用也是有方向的,应用连接的方向也是从应用的 Client 端连接到应用的 Server 端。比如需要我们要访问Internet上的Web站点时,Http应用的方向就是从我们自己这台主机(Client)到远处的Web Server。

本地转发Local Forward

如果SSH连接和应用的连接这两个连接的方向一致,那我们就说它是本地转发。

ssh -L [bind_address:]port:host:hostport <SSH hostname>
ssh -L 3333:127.0.0.1:2333 -vN -f -l shaojiemike 222.195.72.218
debug1: Local connections to LOCALHOST:3333 forwarded to remote address(222.195.72.218) 127.0.0.1:2333

本地转发在本地这台机器上监听一个端口,然后所有访问这个端口的数据都会通过ssh 隧道传输到远端的对应端口上。命令中的 host 和 <SSH hostname> 可以是不同的主机。

远程转发Remote Forward

如果SSH连接和应用的连接这两个连接的方向不同,那我们就说它是远程转发。

ssh -R [bind_address:]port:host:hostport <SSH hostname>

远程转发与本地转发正好相反,打开ssh隧道以后,在远端服务器监听一个端口,所有访问远端服务器指定端口都会通过隧道传输到本地的对应端口上,下面是例子。

动态转发

  • TODO

X转发

  • TODO

实用参数

-C:压缩数据传输。
-f :后台认证用户/密码,通常和-N连用,不用登录到远程主机。
-N :不执行脚本或命令,通常与-f连用。
-g :在-L/-R/-D参数中,允许远程主机连接到建立的转发的端口,如果不加这个参数,只允许本地主机建立连接。
-f      Requests ssh to go to background just before command execution.  This is useful if ssh is going to ask for passwords or
             passphrases, but the user wants it in the background.  This implies -n.  The recommended way to start X11 programs at a remote
             site is with something like ssh -f host xterm.

             If the ExitOnForwardFailure configuration option is set to "yes", then a client started with -f will wait for all remote port
             forwards to be successfully established before placing itself in the background.

常见例子

将发往本机的80端口访问转发到174.139.9.66的8080端口

ssh -C -f -N -g -L 80:174.139.9.66:8080 [email protected]

将发往174.139.9.66的8080访问转发到本机的80端口

ssh -C -f -N -g -R 80:174.139.9.66:8080 [email protected]

使用远程管理服务器上的MySQL

ssh -C -f -N -g -L 80:174.139.9.66:8080 [email protected]

一次同时映射多个端口

ssh -L 8888:www.host.com:80 -L 110:mail.host.com:110 -L    25:mail.host.com:25 user@host -N  

反向隧道技术:节假日需要回公司加班。但是公司是内网,使用NAT,所以没办法连回去。

  1. 先在公司机器(LAN_ip)上执行
    ssh -NfR 2222:localhost:22 home_ip
    
    -R : 建立反向连接 将 home_ip port转发
  2. 然后到home_ip上面 ssh localhost -p 2222

端口转发:本机不允许访问www.xxx.com这个网站,但是远程主机(remote_ip)可以。

ssh -f -N -L 31609:www.xxx.com:80 user@remote_ip

现在我们就可以在本地打开 http://localhost:31609 访问www.xxx.com了。

SOCKS代理:本机不允许访问某些网站,但是远程主机(remote_ip)可以,并且公司没有组织你连接remote_ip。

ssh -NfD 8888 user@remote_ip

现在在浏览器socks 5 proxy设置为localhost:8888,所有之前无法访问的网站现在都可以访问了。

假设本地主机A提供了HTTP服务,主机B无网络

ssh -fNgR 80:localhost:80 root@host-B

通过访问 http://host-B 来访问主机A上的HTTP服务了。

如果没有主机B的root账号,则只能远程转发到1024以后的端口号

ssh -fNgR 8080:localhost:80 lige@host-B

通过访问http://host-B:8080 来访问主机A上的HTTP服务

假设本地主机A无网络,主机B提供了HTTP服务

但是由于怕防火墙屏蔽,而不想直接访问

ssh -fNgL 80:localhost:80 root@host-B

ssh_config设置技巧

客户端

.ssh/config修改

Host *
    ControlPersist yes
    ControlMaster auto
    ControlPath /tmp/sshcontrol-%C
    ControlPersist 1d
    # 以上四条配合使用,实现多条ssh连接共享,而且保持1天内ssh存在。再次执行ssh命令几乎秒连
    TCPKeepAlive=yes
    # 发送空TCP包来保持连接,但是可能被防火墙过滤
    ServerAliveInterval 30
    # 表示每隔多少秒(30秒),从客户端向服务器发送一次心跳(alive检测)
    # 心跳具体格式: debug1: client_input_global_request: rtype [email protected] want_reply 1
    ServerAliveCountMax 240 
    # 表示服务端多少次(240次)心跳无响应后, 客户端才会认为与服务器到SSH链接已经断开,然后断开连接。

    Port 443

Host *
   ForwardAgent yes
   # 可以讓本地的 SSH Key 在遠端 Server 上進行轉送,也就是经过跳板机Server1,使用本地key访问Server2,不用把key传到Server1上导致泄露
   # 虽然Server1不会获得key,但是可以使用key。所以该选项不宜用于Host *,应该只添加您信任的服务器以及打算用于代理转发的服务器。
   # 注意跳板机需要设置允许代理转发, /etc/ssh/sshd_config 将AllowAgentForwarding的值设置为yes, 并重启服务
   AddKeysToAgent yes
   ForwardX11 yes
   ForwardX11Trusted yes
   Compression yes
   # 压缩,加快数据传输速度

服务器端

更改ssh服务器的配置文件/etc/ssh/sshd_config

ClientAliveInterval 60
# 默认是0,不发送
ClientAliveCountMax 3

原理同上 重启ssh服务以使配置生效

systemctl restart sshd

服务器端如何将端口绑定到外部地址上

我们可以把这个映射的端口绑定在0.0.0.0的接口上,方法是加上参数-b 0.0.0.0。

同时修改SSH服务器端 /etc/sshd_configGatewayPorts noGatewayPorts yes来打开它。

自动重连/保持长时间连接

Host *
  ServerAliveInterval 60

检查隧道状态

  1. netstat
  2. ps
  3. autossh

实践

服务器网站端口转发到本地

ssh -L 6006:127.0.0.1:6006 -N -f -l acsacom snode6.swangeese.fun

本地网络代理到服务器

ssh -fNgR 7333:127.0.0.1:7890 [email protected]
ssh -fNgR 7333:127.0.0.1:80 [email protected]
  • 7333数字不要太小,以免冲突。
  • 7890是本地clash端口,80也可以。
服务器git下载设置代理端口
export http_proxy=http://127.0.0.1:7333
# wget 正常使用
export all_proxy=socks5://127.0.0.1:7333
# git 正常使用
unset http_proxy
unset all_proxy

特定软件也需要设置代理

git config --global https.proxy http://127.0.0.1:1080
git config --global https.proxy https://127.0.0.1:1080
git config --global http.proxy 'socks5://127.0.0.1:1080'
git config --global https.proxy 'socks5://127.0.0.1:1080'

git config --global --unset http.proxy
git config --global --unset https.proxy
mac上转发失败

首先看看Windows上的输出

Mac的错误

在完成windows所有输出后,

  1. 首先调用了shell
  2. 错误
debug1: Remote: Forwarding listen address "localhost" overridden by server GatewayPorts
debug1: remote forward failure for: listen 7890, connect 127.0.0.1:7890
Warning: remote port forwarding failed for listen port 7890

猜测应该是已经端口占用了

netstat -nat |grep -i '7233'

解决办法一:换端口

首先,可以选择换端口,换成 7233

sudo lsof -i TCP:7233
1. 进程验证 2. wget验证 3. curl验证(原本会走WLT)

redirecting to http://wlt.ustc.edu.cn

解决办法二:Kill掉相关进程
sudo lsof -i TCP:7890
sudo kill -9 process_id_1 process_id_2 process_id_3
sudo ps -ef | grep 'nc -X' | grep -v grep | awk '{print $2}' | sudo xargs -r kill -9
sudo lsof -i TCP:7233 |grep shaojiemike| awk '{print $2}'|sudo xargs -r kill -9

  • FD: File Descriptor number of
  • 不敢kill师兄的,然后发现并没有用,寄。kill掉师兄的之后就行了,嘻嘻~~ 师兄不要怪我

参考文献

http://blog.sina.com.cn/s/blog_704836f40100lwxh.html

https://blog.csdn.net/xyyangkun/article/details/7025854?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.no_search_link&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.no_search_link

RAM

RAM

RAM (random access memory), 中文名叫随机存储器, 随机是什么意思呢? 意思是, 给定一个地址, 可以立即访问到数据(访问时间和位置无关)

而不像咱们熟悉的磁带, 知道最后一首歌在最后的位置, 却没法直接一下子跳到磁带的最后部门, 所以磁带不是随机存储器, 而是顺序存储器。

SRAM vs DRAM

SRAM (Static Random Access Memory) and DRAM (Dynamic Random Access Memory)

BASIS FOR COMPARISON SRAM DRAM
Speed Faster Slower
Size Small Large
Cost Expensive Cheap
Used in Cache memory Main memory
Density Less dense Highly dense
Construction Complex and uses transistors and latches. Simple and uses capacitors and very few transistors.
Single block of memory requires 6 transistors Only one transistor.
Charge leakage property Not present Present hence require power refresh circuitry
Power consumption Low High

基本电路实现

左边的是静态的,右边的是dynamic的。

SRAM,保存一个bit需要6个晶体管。

DRAM 存储一个bit的DRAM只需要一个电容和一个晶体管。 DRAM的数据实际上是存在于电容里面的, 电容会有电的泄露, 损失状态, 故需要对电容状态进行保持和刷新处理, 以维持持久状态, 而这是需要时间的, 所以就慢了。而且很耗电。

DRAM内存实现的存储是通过晶体管实现的一个电路 门控D锁存器,其更简化的形式是 SR锁存器,电路结构如下图:

但是bank矩阵的一个点(基本存储单元, 寻址能力, 内存颗粒(Chip)的位宽)一般是8bit.

8个 门控D锁存器 组成内存的基本(最小)存储单元,他们共用一个行/列 地址线。在一次寻址中每个内存颗粒返回 8 bit的数据 8个内存可以同时寻址 最终得到的是 8 * 8(8个chip) = 64 bit 的连续数据 也就是说 内存一次寻址可以读取 8 Byte 的数据,这里也能说明在C语言中的内存不齐的原因(减少寻址次数)。

SDRAM

现在的DRAM一般都是SDRAM,即Synchronous Dynamic Random Access Memory,同步且能自由指定地址进行数据读写。其结构一般由许多个bank组成并利用以达到自由寻址。

chip的多 Bank 的设计允许向每个Bank 发出不同的命令。同一时刻,不同的bank可以处理不同的行地址。当然,不可能同时读取或者写入多个 Bank,因为读写通道只有 1 个,当时可以在 1 个 Bank 读写时,向另一个 Bank 发出 Precharge 或者 Active 命令。

DRAM基本术语

名词 解释
dual inline memory modules (DIMMs). 每个channel可以连接多个DIMM,每个DIMM与多个DRAM chip相联
Cell: 颗粒中的一个数据存储单元叫做一个Cell,由一个电容和一个N沟道MOSFET组成。
chip: 一个颗粒叫做一个chip。一根内存的内存带宽是64bit,如果是单面就是8个8bit颗粒,如果是双面,那就是16个4bit的颗粒分别在两面,不算ECC颗粒(Error Checking and Correcting错误校验芯片)。
Bank 每个chip有4~8个bank,每个bank可以看作一个行列矩阵,每个点存储4~16bit的信息。
Rank: 内存PCB的一面所有颗粒叫做一个rank,目前在Unbuffered台式机内存上,通常一面是8个颗粒,所以单面内存就是1个rank,8个chip
寻址空间 是指内存总共可以存储多少个地址,比如一个2G DDR3内存 ,每个Rank是2/1=1G ,每个内存颗粒是1/8=128M 每个Bank是 128/8=16M 16M = 2^4 * 2^10 = 2^14 也就是地址线需要14根 正对应地址线的 A0-A13

Overview

CRC Error Detection

DDR4 chip 内bank & bank group设计

每個DRAM裏有4個bank選取位元可用來選取多達16個bank單元:兩個bank位址位元(BA0、BA1),和兩個bank群組位元(BG0、BG1)。當在同一個bank群組中存取不同的bank單元時會有另外的時間限制;在不同的bank群組中,存取一個bank比以往的更快。

另外,3個晶片層選取信號(C0、C1、C2),允許最多8個堆疊式晶片層封裝於一塊DRAM封裝上。這可以更有效地充當3個以上的bank單元選取位元,使選取總數達到7(可以定位128個bank單元)。

内存控制器(Memory Controller)

我们知道cache的存在导致访存是按照cache line(32或者64字节)来进行的,但是内存一般只会处理连续64bits数据,导致需要控制器和总线分多周期(memory burst概念)来实现cache的更新。

SNB CPU的内存控制器可以实现和处理:

  • 对读写操作命令进行有效地重新分配,以使得行地址激活命中率最大化(如果重复激活一个已经处于激活状态的行地址,那就是RAS激活命令未命中)
  • 比如说open page policy情况下,row hit就不用发activate命令,直接发column就可以了,
  • 比如说两个地址连续mem_read命令,中间插有其他命令的时候是不是要乱序执行

reduction in DRAM row buffer conflicts

[^1]

CPU集成内存控制器技术

AMD公司提高CPU与内存性能的一项技术,将北桥的内存控制器集成到CPU,使得原来CPU-北桥-内存三方传输数据的过程简化成CPU与内存之间的单向传输技术,降低了延迟。

DRAM 寻址模式

  • 列数一般是1024,主要是因为功耗的原因

以2GB DDR3为例子,编码如上,

  1. 确定好rank面后
  2. 对该rank面的所有内存颗粒(chip),使用相同的Bank层 、行地址 、列地址 这些选址信息后,各自产生8bits数据,总共64bits
  3. 单个 Bank 只有一个 Sense Amps,只能缓存单个行的内容。因此在激活某行后,访问同一 Bank 不同行之前,需要使用 PRECHARGE 命令关闭(de-activate)当前激活行。PRECHARGE 命令好比关上当前打开的文件柜抽屉,命令发出后当前 Sense Amps 中缓存的行会被写回原地址。

Burst

DDR中的Burst(突发长度)指的是,当收到了一个读请求和地址后,会连续取出这个地址周围几个连续地址上的数据,具体取几个就叫BL(Burst Length),是可以随地址信号配置的。(原因是:次次等待Address和Enable信号再读写有些浪费时间)

Burst的实现是通过Prefetch完成的,Prefetch就是一次从Array上取出多bit的过程,而Burst则是根据规则发送这些预取的数据的过程。

Burst Length(BL)是可以配置的,比如8Bit预取可以支持BL8的Burst或者BC4(Burst Length Chopped)的Burst。

Prefetch (Request Pipelining)

Prefetch数量也是前几代DDR的主要区别。

红框标出的DRAM的核心频率基本不变,传输速度的提高是通过增加prefetch的位数(黄框)来做到的。

DDR 有两项主要的技术 2n-prefetch (2 倍预取),和 DLL (延迟锁相环)。这在之后历代 DDR 协议中都是一脉相承的。所谓 2 倍预取,即在一个时钟的上升边沿读取当前地址单元的数据,并同时读取下一个地址单元的数据。

例如同样是100MHz的核心频率
  1. SDRAM一周期取一次,它和内存控制器的速度是100MT/s(这里的T是传输的意思);
  2. DDR上升沿下降沿各取一次,相当于2次prefetch,Bus速度变成200;
  3. DDR2变成4n prefetch,Bus speed变成400;
  4. DDR3,照此办理,8n带来了800。DDR3/4 采用的是 8 倍预取,8n-prefetch,同时也设计有 DLL。

DDR3

DDRx的核心频率一直维持在100Mhz到266MHz的水平上,每代速度的提升都是靠倍增Prefetch的个数来达到的。

DDR4

DDR4和DDR3一样,只有8n的prefetch,但为了提升前端Front End的总线速度,不得不在核心频率上动起了手脚:

核心频率不在徘徊在100~266HMz,直接200起跳,到400Mhz。因为核心频率提高,8bit的prefetch不变,总线速度才得以提升。

除此之外,引入了Bank Group。DDR4 新增了4 個Bank Group 資料組的設計,各個Bank Group具備獨立啟動操作讀、寫等動作特性,Bank Group 資料組可套用多工的觀念來想像,亦可解釋為DDR4 在同一時脈工作周期內,至多可以處理4 筆資料,效率明顯好過於DDR3。

为什么DDR4不能进一步提高prefetch到16n的问题

我们都知道memory控制器实际上很大程度受cache操纵。X86 cache line 64B,而每次操作是64bit。所以一个cache line刷新是通过联系8个读操作实现的,这8个操作不是分别完成,而是一次burst操作,所以BL(burst line)是8。BL8的64B cache line只需要64个Bytes,如果prefetch是16,DIMM那边所有chip会准备

64 X 16 = 128 Byte

的数据。多出来的数据就变成了垃圾数据,空耗能而对速度帮助不大,所以DDR4到16 prefetch。

DDR5 为啥变成16n prefetch呢?

是不是CPU的cache line变长了呢?并不是,CPU的cache line还是64B,变化的是DIMM端增加了个新东西:Sub Channel。

Sub Channel,顾名思义,就是子通道,它是把DDR5 DIMM的72bit位宽(包括64bit数据+8bit ECC码)拆分成两个40bit的sub Channel。包括32bit的数据,+8bit的ECC:

这两个sub channel是相互独立的,既可以独立使用,也可以如前面合并使用。所以prefetch就可以提高到16n,当然也支持8n。

聪明的设计让DDR5在同样3200MT/s的传输率上,可以提高带宽1.36倍。再加上可以支持更高的频率,才能保证DDR5的传输速度。

DDR5的prefetch是16,那么怎么解决我们前面提到的cache line大小的问题呢?DDR5采取的方式是减少DIMM data lane的数量,从64个data lane降低到32个data lane,从而继续保持64 Byte的cache line大小。

访存时序知识

CL-tRCD-tRP-tRAS-CR

名词 解释
CL(CAS Latency) 列信号延迟: 在读取命令发出后到数据读出到IO接口的间隔时间(时钟周期数)
tCAS(tCL?) 实际延迟时间tCAS(ns)=(CAS*2000)/内存等效频率
tRAS(Row Active Time) 行地址激活的时间。从一个行地址预充电之后,从激活到寻址再到读取完成所经过的整个时间 tRCD+tCL
tRCD(Read-to-Column Delay) 行地址激活(Active)命令发出之后,内存对行地址的操作所需要的时间。内存中某一行地址被激活时,我们称它为“open page”
tRCDR(Read-to-Column Command Delay) 行地址激活(Active)命令发出之后,内存对行地址的读操作所需要的时间。
tRCDW(Write-to-Column Command Delay) 行地址激活(Active)命令发出之后,内存对行地址的写操作所需要的时间。
nWR (Write Recovery Time) time delay between successive write commands to the same row.
tRP(RAS Precharge Time) 前一个行地址操作完成并在行地址关闭(page close)命令发出之后,准备对同一个bank中下一个行地址进行Active操作需要的时间(在对同一个bank的多个不同的行地址进行操作时影响才大)
CR(Command Rate) 首命令延迟。是指从选定bank之后到可以发出行地址激活命令所经过的时间。(如果CPU所需要的数据都在内存的一个行地址上,就不需要进行重复多次的bank选择,CR的影响就很小)
Tccd is the minimum amount of time between column operations
tRPRE The minimum pulse width of READ preamble
tRPST The minimum pulse width of READ postamble

XMP时序都没有介绍

不同的DRAM。随着频率提升,CL周期也同步提升,但是最后算出来的CL延迟时间却差不多(5~15ns)。其实当下memory的频率宽度过剩,integrated memory controller (IMC)才是瓶颈

在列信号之前还有行信号

如何连续两次访问同一行的不同列,则之间不需要额外的切换行信号。

参考文献

https://zhuanlan.zhihu.com/p/52272990

https://fantiq.github.io/2019/03/14/%E5%86%85%E5%AD%98-%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86/

https://people.inf.ethz.ch/omutlu/pub/stfm_micro07.pdf

https://www.micron.com/-/media/client/global/documents/products/data-sheet/dram/ddr4/4gb_ddr4_dram_2e0d.pdf

https://zhuanlan.zhihu.com/p/420994258

Hash map

hash的相关基本知识

https://www.cnblogs.com/guoben/p/13339280.html

散列表的装填因子定义为:α= 填入表中的元素个数 / 散列表的长度

α是散列表装满程度的标志因子。由于表长是定值,α与“填入表中的元素个数”成正比,所以,α越大,填入表中的元素较多,产生冲突的可能性就越大;α越小,填入表中的元素较少,产生冲突的可能性就越小。

unordered_map定义

插入元素时,根据待插入元素的关键码,以hashfunc计算出该元素的存储位置,在结构中按此位置进行存放

搜索元素时,对关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置去元素比较,若关键码相等,则搜索成功

C++11 定义了std::hash

template<
    class Key,
    class T,
    class Hash = std::hash<Key>,
    class KeyEqual = std::equal_to<Key>,
    class Allocator = std::allocator< std::pair<const Key, T> >
> class unordered_map;
很明显可以reserve设置大小

unordered_map详解

http://c.biancheng.net/view/7231.html

自定义结构体hash函数

hashfunc设计原则

哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许m个地址时,其值域必须在0-(m-1)之间

哈希函数计算出来的地址能均匀分布在整个空间中

哈希函数应该比较简单

举例

https://blog.csdn.net/HappyKocola/article/details/74188452

如果想让自定义的class作为key(unordered_map)来使用unordered_map,需要实现:

  1. 哈希函数,需要实现一个class重载operator(),将自定义class变量映射到一个size_t类型的数。一般常用std::hash模板来实现。
  2. For two parameters k1 and k2 that are equal, std::hash<Key>()(k1) == std::hash<Key>()(k2).
  3. For two different parameters k1 and k2 that are not equal, the probability that std::hash<Key>()(k1) == std::hash<Key>()(k2) should be very small, approaching 1.0/std::numeric_limits<std::size_t>::max().
  4. 判断两个自定义class类型的key变量是否相等的函数,一般在自定义class里重载operator==。

 template <>
    struct hash<Myclass>
    {
        size_t operator()(const Myclass &k) const
        {
            int h = k.first;
            for (auto x : k.second)
            {
                h ^= x;
            }
            return h;
        }
    };
或者
struct MyHash
{
  size_t operator() (const pair<int,int>& p) const
  {
    return hash<long long>()( (static_cast<long long>(x.first) ) 
    ^ ( (static_cast<long long>(x.first))<<32) );
  }
};

防止冲突的自定义hash

防止黑客攻击

https://blog.csdn.net/qq_21433411/article/details/88365164

struct custom_hash {
    static uint64_t splitmix64(uint64_t x) {
        // http://xorshift.di.unimi.it/splitmix64.c
        x += 0x9e3779b97f4a7c15;
        x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
        x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
        return x ^ (x >> 31);
    }

    size_t operator()(uint64_t x) const {
        static const uint64_t FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count();
        return splitmix64(x + FIXED_RANDOM);
    }
};

结构体万能hash

解释见转载的作者

template <typename T>
inline void hash_combine(size_t& seed, const T& val)
{
    seed ^= std::hash<T>()(val) + 0X9E3779B9 + (seed << 6) + (seed >> 2);
}

template <typename T>
inline void hash_val(size_t& seed, const T& val)
{
    hash_combine(seed, val);
}

template <typename T, typename... Types>
inline void hash_val(size_t& seed, const T& val, const Types&... args)
{
    hash_combine(seed, val);
    hash_val(seed, args...);
}

template <typename... Types>
inline size_t hash_val(const Types&... args)
{
    size_t seed = 0;
    hash_val(seed, args...);
    return seed;
}

class MyObject{
public:
    string a;
    char b;
    unsigned c;
    bool operator==(const MyObject& rhs) const
    {
        return a == rhs.a && b == rhs.b && c == rhs.c;
    }

    bool operator!=(const MyObject& rhs) const
    {
        return !operator==(rhs);
    }

    friend ostream& operator<<(ostream& os, const MyObject& ptr)
    {
        return os << ptr.a << "   " << ptr.b << "   " << ptr.c << endl;
    }
};

class MyHashFunction{
public:
    std::size_t operator()(const MyObject& obj) const {
        return hash_val(obj.a, obj.b, obj.c);
    }
};

unordered_map性能

加快 std::unorderd_map 的速度

为了避免动态改变哈希表的大小,可以预估容器中带插入元素的数量,并且预先申请好内存空间,避免动态分配过程中造成的 rehash 现象。当容器中的总元素超出了填充因子时,容器会重新申请更大的内存扩充桶的数量,然后重新哈希。

例如,我会预先调用 reverse 成员函数预先分配内存空间,以及设置好最大填充因子。

unordered_map<int,int> hash;
mp.reserve(1024);
mp.max_load_factor(0.25);

但是没什么用?

Google 开源的 abesil flat hash map

Google 则采用了开放寻址法中最简单的线性探测方法来解决哈希碰撞,取得了更快的速度。

开放定址法,当哈希表未满,在插入同义字时,可以把key值存放在下一个空位置(线性探测) 线性探测: 从发生冲突的位置开始,依次向后探测,直到寻找下一个空位置为止.

更快,解释开发寻址法,cache更优秀

高性能

https://github.com/greg7mdp/parallel-hashmap

https://github.com/greg7mdp/sparsepp

需要进一步的研究学习

暂无

遇到的问题

IPCC比赛的时候,发现用hash map会比红黑树的map慢。感觉不对劲,我hash空间声明大,冲突不就低了吗?

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

参考文献

Ubuntu server reInstall

思路

  1. 写U盘(见网上教程,注意备份U盘文件)
  2. 插U盘,开机前根据机器型号使用命令(F2,F4, F12或者Del)进入boit manager,选择从U盘boot
  3. 改引导启动顺序,把U盘启动改到最前面
  4. 根据提示安装ubuntu(服务器不用分区,默认就行,如果自己电脑双系统则需要根据内存设置分区)
  5. 设置root密码并进入root sudo passwd root
  6. 配置实验室网络

下载安装包

官网下载

重装系统怎么保留原本的磁盘文件

只要在安装系统时分出一个/home分区。你可以把Ubuntu的“/”分区看为Windows的C盘,重装Ubuntu时只格式化“/”分区,不格式化“/home”,这样就可以保留“/home”中的数据了。使用的时候就挂载就行

但是假如一开始没分区: 1. 可以插块新盘当作系统盘,系统安装好之后,把旧的文件系统挂载到新系统的某个目录下 2. 从旧的文件系统里划出一块区域,安装新系统。 1. 在你要安装的目标磁盘中,通过删除卷和删除分区操作腾出一块未分配的磁盘空间作为安装区

从已有分区中提取空间

xfs类型不可以直接缩减,只扩不减。如果是ext2,ext3,ext4可以在线缩减,如果xfs盘要缩小就要删除后重新添加

https://www.ywbj.cc/?p=712

非系统根分区LVM缩容

  1. 取消挂载 系统根分区无法在线取消挂载,所以这时请勿对系统根分区执行任何缩容操作
    umount /lv/ #取消挂载目录
    
  2. e2fsck检查修复磁盘完整性
    e2fsck -f /dev/vg0/lv0
    
  3. 缩小系统文件空间,即df -h查看的空间 不取消挂载,这步会报错,注:一定先减文件系统,再减逻辑卷
    resize2fs /dev/vg0/lv0 10G
    
  4. 缩小磁盘空间,即lsblk查看的空间 不执行上面的操作,直接执行这步,虽然成功,但是会操作数据丢失,系统无法启动。
    lvreduce -L 10G /dev/vg0/lv0
    
  5. 重新挂载
    mount -a
    
    最后查看lvs空间

系统根分区LVM缩容(救援模式)

正常启动系统进入救援模式 :启动按shift键,出现选择系统界面,按e。找到以单词 linux 开头的行,并在该行的末尾添加以下内容(要到达末尾,只需按下 CTRL+e 或使用 END 键或左右箭头键):

systemd.unit=rescue.target
添加完成后,只需按下 CTRL+xF10 即可继续启动救援模式。几秒钟后,你将以 root 用户身份进入救援模式(单用户模式)

需要进一步的研究学习

遇到的问题

暂无

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

参考文献

https://blog.csdn.net/weixin_40018873/article/details/109537532

https://www.ywbj.cc/?p=712

MPI Compilers

mpicc vs gcc

OpenMPI

# shaojiemike @ node5 in ~ [7:20:31]
$ mpicc -showme
gcc -I/usr/lib/x86_64-linux-gnu/openmpi/include/openmpi -I/usr/lib/x86_64-linux-gnu/openmpi/include -pthread -L/usr/lib/x86_64-linux-gnu/openmpi/lib -lmpi

IntelMPI

source /opt/intel/oneapi/setvars.sh

# shaojiemike @ node5 in ~/github/IPCC/SLIC on git:main x [15:45:06] C:1
$ mpicc -compile_info
gcc -I'/opt/intel/oneapi/mpi/2021.1.1/include' -L'/opt/intel/oneapi/mpi/2021.1.1/lib/release' -L'/opt/intel/oneapi/mpi/2021.1.1/lib' -Xlinker --enable-new-dtags -Xlinker -rpath -Xlinker '/opt/intel/oneapi/mpi/2021.1.1/lib/release' -Xlinker -rpath -Xlinker '/opt/intel/oneapi/mpi/2021.1.1/lib' -lmpifort -lmpi -lrt -lpthread -Wl,-z,now -Wl,-z,relro -Wl,-z,noexecstack -Xlinker --enable-new-dtags -ldl

> mpicc -v                  
mpigcc for the Intel(R) MPI Library 2021.5 for Linux*                                                                      
Copyright Intel Corporation.
Using built-in specs.       
COLLECT_GCC=gcc             
COLLECT_LTO_WRAPPER=/public1/soft/gcc/8.1.0/libexec/gcc/x86_64-pc-linux-gnu/8.1.0/lto-wrapper                              
Target: x86_64-pc-linux-gnu 
Configured with: ./configure --prefix=/public1/soft/gcc/8.1.0 --enable-languages=c,c++,fortran --disable-multilib          
Thread model: posix         
gcc version 8.1.0 (GCC) 

# shaojiemike @ node5 in ~/github/IPCC/SLIC on git:main x [15:45:16]
$ mpiicc -compile_info
icc -I'/opt/intel/oneapi/mpi/2021.1.1/include' -L'/opt/intel/oneapi/mpi/2021.1.1/lib/release' -L'/opt/intel/oneapi/mpi/2021.1.1/lib' -Xlinker --enable-new-dtags -Xlinker -rpath -Xlinker '/opt/intel/oneapi/mpi/2021.1.1/lib/release' -Xlinker -rpath -Xlinker '/opt/intel/oneapi/mpi/2021.1.1/lib' -lmpifort -lmpi -ldl -lrt -lpthread

# shaojiemike @ node5 in ~/github/IPCC/SLIC on git:main x [15:50:09] C:255
$ mpiicc -show
icc -I'/opt/intel/oneapi/mpi/2021.1.1/include' -L'/opt/intel/oneapi/mpi/2021.1.1/lib/release' -L'/opt/intel/oneapi/mpi/2021.1.1/lib' -Xlinker --enable-new-dtags -Xlinker -rpath -Xlinker '/opt/intel/oneapi/mpi/2021.1.1/lib/release' -Xlinker -rpath -Xlinker '/opt/intel/oneapi/mpi/2021.1.1/lib' -lmpifort -lmpi -ldl -lrt -lpthread

MPICH

ipcc22_0029@ln121 ~/github/IPCC2022-preliminary/run (float_trick*) [10:49:48]
> mpicc -show         
gcc -I/public1/soft/mpich/3.1.4-gcc8.1.0/include -L/public1/soft/mpich/3.1.4-gcc8.1.0/lib -Wl,-rpath -Wl,/public1/soft/mpich/3.1.4-gcc8.1.0/lib -Wl,--enable-new-dtags -lmpi
For MPICH, according to the mpicc man pages, mpicc -compile_info shows the flags for compiling a program, and mpicc -link_info shows the flags for linking a program.

-showme (Open MPI) or -show (Open MPI, MPICH and derivates) use -showme:compile and -showme:link to obtain the options automatically

安装选项查看

intelmpi

> mpirun -info
HYDRA build details:
    Version:                                 2021.5
    Release Date:                            20211102 (id: 9279b7d62)
    Process Manager:                         pmi
    Bootstrap servers available:             ssh slurm rsh ll sge pbs pbsdsh pdsh srun lsf blaunch qrsh fork
    Resource management kernels available:   slurm ll lsf sge pbs cobalt

MPICH

ipcc22_0029@ln121 ~  [11:55:08]
> mpiexec --version               
HYDRA build details:
    Version:                                 3.1.4
    Release Date:                            Fri Feb 20 15:02:56 CST 2015
    CC:                              gcc    
    CXX:                             g++    
    F77:                             gfortran   
    F90:                             gfortran   
    Configure options:                       '--disable-option-checking' '--prefix=/public1/soft/mpich/3.1.4-gcc8.1.0' 'CC=gcc' 'CXX=g++' 'FC=gfortran' '--cache-file=/dev/null' '--srcdir=.' 'CFLAGS= -O2' 'LDFLAGS= ' 'LIBS=-lpthread ' 'CPPFLAGS= -I/public1/home/deploy/amd-mpich/mpich-3.1.4/src/mpl/include -I/public1/home/deploy/amd-mpich/mpich-3.1.4/src/mpl/include -I/public1/home/deploy/amd-mpich/mpich-3.1.4/src/openpa/src -I/public1/home/deploy/amd-mpich/mpich-3.1.4/src/openpa/src -D_REENTRANT -I/public1/home/deploy/amd-mpich/mpich-3.1.4/src/mpi/romio/include'
    Process Manager:                         pmi
    Launchers available:                     ssh rsh fork slurm ll lsf sge manual persist
    Topology libraries available:            hwloc
    Resource management kernels available:   user slurm ll lsf sge pbs cobalt
    Checkpointing libraries available:       
    Demux engines available:                 poll select

ipcc22_0029@ln121 ~  [11:55:31]
> mpichversion
MPICH Version:          3.1.4
MPICH Release date:     Fri Feb 20 15:02:56 CST 2015
MPICH Device:           ch3:nemesis
MPICH configure:        --prefix=/public1/soft/mpich/3.1.4-gcc8.1.0/ CC=gcc CXX=g++ FC=gfortran
MPICH CC:       gcc    -O2
MPICH CXX:      g++   -O2
MPICH F77:      gfortran   -O2
MPICH FC:       gfortran   -O2

OpenMPI

## 安装了IB支持
> ompi_info | grep openib
                 MCA btl: openib (MCA v2.1.0, API v3.0.0, Component v3.1.6)

> mpiexec --version      
mpiexec (OpenRTE) 4.1.1

Report bugs to http://www.open-mpi.org/community/help/

ipcc22_0029@ln121 ~/slurm/MPIInit  [12:24:19]
> module load mpi/openmpi/4.1.1-gcc7.3.0 
ipcc22_0029@ln121 ~/slurm/MPIInit  [12:24:51]
> mpicc -v                              
Using built-in specs.
COLLECT_GCC=/public1/soft/gcc/7.3.0/bin/gcc
COLLECT_LTO_WRAPPER=/public1/soft/gcc/7.3.0/libexec/gcc/x86_64-pc-linux-gnu/7.3.0/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: ./configure --prefix=/public1/soft/gcc/7.3.0 --disable-multilib
Thread model: posix
gcc version 7.3.0 (GCC) 

需要进一步的研究学习

暂无

遇到的问题

暂无

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

参考文献

https://stackoverflow.com/questions/11312719/how-to-compile-mpi-with-gcc

Why MPI_Init is slow

MPI_Init的作用

MPI_Init(&argc, &argv);

StackOverflow的回答是,Init在调用过程中初始化MPI库,并且在进程间建立通讯和编号。

知乎的回答: OpenMPI会在调用MPI_Init时按照你传递给mpirun的指令新建进程,而你传递给MPI_Init的参数,会被传递给新建的进程。

这似乎在暗示,两个进程不是同时产生和运行的。

猜想1

有顺序的观点是不成立的

即使有顺序 malloc的时间也没这么长。

猜想2

难道是malloc的数据需要MPI_Init复制一遍?

简单将MPI_Init提前到最开始,时间也基本没变,也不对。

如果单独写一个只有MPI_Init的程序,IntelMPI还是要耗时800ms

ipcc22_0029@ln121 ~/slurm/MPIInit  [11:42:32]
> srun -p IPCC -N 2 -n 2 -c 64 -t 1  MPI
MPIInit          took 882.110047 ms
MPIInit          took 892.112269 ms

测试比较超算上MPI_Init的时间

以IPCC2022初赛的北京超算云 AMD机器举例

mpirun的选择 mpi版本 GCC或者ICC的选择版本 超算运行 MPI_Init时间(ms)
IntelMPI mpi/intel/2022.1 gcc/10.2.0 只能sbatch,不能srun 1282.24 ~ 1678.59
OpenMPI mpi/openmpi/4.1.1-gcc7.3.0 2706ms~3235ms
MPICH mpich/3.1.4-gcc8.1.0 17ms
mpich/3.4.2 gcc/10.2.0 107ms

不能srun IntelMPI的问题

缺少一个环境变量

需要export I_MPI_PMI_LIBRARY=libpmi2.so

VTune 分析MPI的程序

https://www.intel.com/content/www/us/en/develop/documentation/vtune-help/top/analyze-performance/code-profiling-scenarios/mpi-code-analysis.html

https://www.intel.com/content/www/us/en/develop/documentation/vtune-cookbook/top/configuration-recipes/profiling-mpi-applications.html

环境变量

设置这个Intel mpi 1200 -> 1100

export PMI_TIME=1
export SLURM_PMI_KVS_NO_DUP_KEYS=yes

需要进一步的研究学习

实在是弄不懂,为什么不同的实现,时间差别这么大。可能慢是因为额外的通路设置,是为了之后的快速传输??

3.1.4的安装选项也看不到

> mpiexec --version     
mpiexec: error while loading shared libraries: libslurm.so.35: cannot open shared object file: No such file or directory

遇到的问题

暂无

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

参考文献

oneApi

安装

命令行安装

Intel OneAPI base toolkit

必须先安装base,可以看到默认安装的内容

这个GDB好像可以分析多进程

Intel OneAPI HPC toolkit

Intel OneAPI HPC toolkit包括了icc,icpc,ifort和OpenMP,IntelMPI还有MKL(Intel® oneAPI Math Kernel Library (oneMKL))

Download界面选择版本, 选择online或者offline会有推荐指令,如下

wget https://registrationcenter-download.intel.com/akdlm/irc_nas/18679/l_HPCKit_p_2022.2.0.191.sh

sudo sh ./l_HPCKit_p_2022.2.0.191.sh

icx icpx

> icx -v
Intel(R) oneAPI DPC++/C++ Compiler 2022.0.0 (2022.0.0.20211123)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /opt/intel/oneapi/compiler/2022.0.2/linux/bin-llvm
Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/8
Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/9
Selected GCC installation: /usr/lib/gcc/x86_64-linux-gnu/9
Candidate multilib: .;@m64
Selected multilib: .;@m64
Found CUDA installation: /usr/local/cuda-11.5, version

> icpx --help
OVERVIEW: Intel(R) oneAPI DPC++/C++ Compiler

USAGE: clang++ [options] file...

需要进一步的研究学习

暂无

遇到的问题

暂无

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

参考文献

https://www.intel.com/content/www/us/en/develop/documentation/installation-guide-for-intel-oneapi-toolkits-linux/top/prerequisites/install-intel-gpu-drivers.html

Inline Assembly

GCC内联汇编

__asm__ __volatile__("Instruction List" : Output : Input : Clobber/Modify);
  1. __asm__或asm 用来声明一个内联汇编表达式,所以任何一个内联汇编表达式都是以它开头的,是必不可少的。
  2. __volatile__或volatile 是可选的。如果用了它,则是向GCC 声明不允许对该内联汇编优化,否则当 使用了优化选项(-O)进行编译时,GCC 将会根据自己的判断决定是否将这个内联汇编表达式中的指令优化掉。
  3. Instruction List 是汇编指令序列。它可以是空的,比如:__asm__ __volatile__(""); 或 __asm__ ("");都是完全合法的内联汇编表达式,只不过这两条语句没有什么意义。
  4. 但并非所有Instruction List 为空的内联汇编表达式都是没有意义的,比如:__asm__ ("":::"memory");就非常有意义,它向GCC 声明:“内存作了改动”,GCC 在编译的时候,会将此因素考虑进去。
  5. 当在"Instruction List"中有多条指令的时候,需要用分号(;)或换行符(\n)将它们分开。
  6. 指令中的操作数可以使用占位符引用C语言变量,操作数占位符最多10个,名称如下:%0,%1,…,%9。指令中使用占位符表示的操作数,总被视为long型(4个字节),
    1. 但对其施加的操作根据指令可以是字或者字节,当把操作数当作字或者字节使用时,默认为低字或者低字节
    2. 对字节操作可以显式的指明是低字节还是次字节。方法是在%和序号之间插入一个字母,"b"代表低字节,"h"代表高字节,例如:%h1
  7. Output/Input
    1. 格式为形如"constraint"(variable)的列表(逗号分隔)。按照出现的顺序分别与指令操作数"%0","%1"对应
    2. 每个输出操作数的限定字符串必须包含"="表示他是一个输出操作数。例子"=r" (value)
  8. Clobber/Modify(由逗号格开的字符串组成)
    1. 在Input/Output操作表达式所指定的寄存器,或当你为一些Input/Output操作表达式使用"r"约束,让GCC为你选择一个寄存器时,GCC知道这些寄存器是被修改的,你根本不需要在Clobber/Modify域再声明它们。
    2. 但是对于"Instruction List"中的临时寄存器,需要在Clobber/Modify域声明这些寄存器或内存,让GCC知道修改了他们
      1. 例子:__asm__ ("mov R0, #0x34" : : : "R0");寄存器R0出现在"Instruction List中",并且被mov指令修改,但却未被任何Input/Output操作表达式指定,所以你需要在Clobber/Modify域指定"R0",以让GCC知道这一点。
    3. Clobber/Modify域存在"memory",那么GCC会保证在此内联汇编之前,如果某个内存的内容被装入了寄存器,那么在这个内联汇编之后,如果需要使用这个内存处的内容,就会直接到这个内存处重新读取,而不是使用被存放在寄存器中的拷贝。因为这个 时候寄存器中的拷贝已经很可能和内存处的内容不一致了。

输入输出与指令的对应关系

寄存器约束符Operation Constraint

每一个Input和Output表达式都必须指定自己的操作约束Operation Constraint,这里将讨论在80386平台上所可能使用的操作约束。

当前的输入或输出需要借助一个寄存器时,需要为其指定一个寄存器约束,可以直接指定一个寄存器的名字。

常用的寄存器约束的缩写 约束 | 意义| |--------- | ----| r |表示使用一个通用寄存器,由 GCC 在%eax/%ax/%al,%ebx/%bx/%bl,%ecx/%cx/%cl,%edx/%dx/%dl中选取一个GCC认为合适的。 g |表示使用任意一个寄存器,由GCC在所有的可以使用的寄存器中选取一个GCC认为合适的。 q |表示使用一个通用寄存器,和约束r的意义相同。 a |表示使用%eax/%ax/%al b |表示使用%ebx/%bx/%bl c |表示使用%ecx/%cx/%cl d |表示使用%edx/%dx/%dl D |表示使用%edi/%di S |表示使用%esi/%si f |表示使用浮点寄存器 t |表示使用第一个浮点寄存器 u |表示使用第二个浮点寄存器

分类 |限定符 |描述 |--------- | ----|----------------| 通用寄存器 |“a”| 将输入变量放入eax 这里有一个问题:假设eax已经被使用,那怎么办?其实很简单:因为GCC 知道eax 已经被使用,它在这段汇编代码的起始处插入一条语句pushl %eax,将eax 内容保存到堆栈,然 后在这段代码结束处再增加一条语句popl %eax,恢复eax的内容 ||“b” |将输入变量放入ebx ||“c” |将输入变量放入ecx ||“d” |将输入变量放入edx ||“s” |将输入变量放入esi ||“d” |将输入变量放入edi ||“q” |将输入变量放入eax,ebx,ecx,edx中的一个 ||“r” |将输入变量放入通用寄存器,也就是eax,ebx,ecx,edx,esi,edi中的一个 ||"A"|把eax和edx合成一个64 位的寄存器(use long longs) 内存| “m” |内存变量 ||“o” |操作数为内存变量,但是其寻址方式是偏移量类型, 也即是基址寻址,或者是基址加变址寻址 ||“V”| 操作数为内存变量,但寻址方式不是偏移量类型 ||" "| 操作数为内存变量,但寻址方式为自动增量 ||“p” |操作数是一个合法的内存地址(指针) 寄存器或内存| “g” |将输入变量放入eax,ebx,ecx,edx中的一个 或者作为内存变量 ||“X” |操作数可以是任何类型 立即数 |“I” |0-31之间的立即数(用于32位移位指令) ||“J” |0-63之间的立即数(用于64位移位指令) ||“N” |0-255之间的立即数(用于out指令) ||“i” |立即数 ||“n” |立即数,有些系统不支持除字以外的立即数, 这些系统应该使用"n"而不是"i" 匹配 |" 0 ",“1” …“9” |, 表示用它限制的操作数与某个指定的操作数匹配,也即该操作数就是指定的那个操作数,例如"0"去描述"%1"操作数,那么"%1"引用的其实就是"%0"操作数,注意作为限定符字母的0-9 与 指令中的"%0"-"%9"的区别,前者描述操作数,后者代表操作数。 ||&; |该输出操作数不能使用过和输入操作数相同的寄存器 |操作数类型 |“=” |操作数在指令中是只写的(输出操作数) ||“+” |操作数在指令中是读写类型的(输入输出操作数) 浮点数| “f” |浮点寄存器 ||“t” |第一个浮点寄存器 ||“u” |第二个浮点寄存器| ||“G” |标准的80387浮点常数 ||% |该操作数可以和下一个操作数交换位置.例如addl的两个操作数可以交换顺序 (当然两个操作数都不能是立即数) ||# |部分注释,从该字符到其后的逗号之间所有字母被忽略 ||* |表示如果选用寄存器,则其后的字母被忽略

内存约束

如果一个Input/Output 操作表达式的C/C++表达式表现为一个内存地址,不想借助于任何寄存器,则可以使用内存约束。比如:

__asm__("lidt%0":"=m"(__idt_addr));
__asm__("lidt%0"::"m"(__idt_addr));

修饰符 |输入/输出 |意义 |---- | --- |--- = | O |表示此Output操作表达式是Write-Only的。 + | O |表示此Output操作表达式是Read-Write的。 & | O |表示此Output操作表达式独占为其指定的寄存器。 % | I |表示此Input 操作表达式中的C/C++表达式可以和下一 个Input操作表达式中的C/C++表达式互换

例子

Static __inline__ void __set_bit(int nr, volatile void * addr)
{
         __asm__(
                         "btsl %1,%0"
                         :"=m" (ADDR)
                         :"Ir" (nr));
}

第一个占位符%0与C 语言变量ADDR对应,第二个占位符%1与C语言变量nr对应。因此上面的汇编语句代码与下面的伪代码等价:btsl nr, ADDR

Clobber/Modify域存在"memory"的其他影响

使用"memory"是向GCC声明内存发生了变化,而内存发生变化带来的影响并不止这一点。

例如:

int main(int __argc, char* __argv[]) 
{ 
    int* __p = (int*)__argc; 
    (*__p) = 9999; 
    __asm__("":::"memory"); 
    if((*__p) == 9999) 
        return 5; 
    return (*__p); 
}
本例中,如果没有那条内联汇编语句,那个if语句的判断条件就完全是一句废话。GCC在优化时会意识到这一点,而直接只生成return 5的汇编代码,而不会再生成if语句的相关代码,而不会生成return (*__p)的相关代码。

但你加上了这条内联汇编语句,它除了声明内存变化之外,什么都没有做。

但GCC此时就不能简单的认为它不需要判断都知道 (*__p)一定与9999相等,它只有老老实实生成这条if语句的汇编代码,一起相关的两个return语句相关代码。

另外在linux内核中内存屏障也是基于它实现的include/asm/system.h中

# define barrier() _asm__volatile_("": : :"memory")
主要是保证程序的执行遵循顺序一致性。呵呵,有的时候你写代码的顺序,不一定是终执行的顺序,这个是处理器有关的。

Linux 源码例子

static inline char * strcpy(char * dest, const char *src)
{
    char *xdest = dest;
    __asm__ __volatile__
    ("1: \tmoeb %1@+, %0@+\n\t"   "jne 1b"  //这个冒号不是分隔符
    : "=a" (dest) , "=a" (stc)
    : "0"(dest), "1" (src)
     : "memory");
    return xdest;
}

需要进一步的研究学习

暂无

遇到的问题

暂无

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

参考文献

https://blog.csdn.net/yi412/article/details/80846083

https://www.cnblogs.com/elnino/p/4313340.html

Tar Zip Rar

各种指令的对比

命令|压缩空间效果|压缩时间效果|解压时间|说明 --- |--- |--- |--- |--- | tar -cf |2.9G |13.8s |3.3s | tar -cf archive.tar foo归档文件,没有压缩功能 tar -zcf|823M |1:44 |19s |tar -zcf archive.tar.gz foo归档并使用gzip压缩文件,gzip是zip的GNU实现,是最老的公开压缩方法 zip -1r |856M |48.6s |23.3s |zip -1qr intel.zip intel, -1 compress faster,unzip解压 zip -9r |824M |11:19 |24s |压缩这也太慢了吧 rar a |683M |2:02 |46s |unrar x解压

上述测试基于大小3GB的文件夹

关于rar

虽然说好像有专利的软件,但是sudo apt install rar貌似就可以安装。但是空间效果确实还行多压1/3,但是时间要多两倍。

还是建议tar.gz

因为 7z 和 zip 压缩格式都不能保留 unix 风格的文件权限,比如解压出个可执行文件要重新 chmod chown 才能恢复正常。而 tar 格式可以。而 tar 本身不提供压缩,无非就是把包括所有文件的內容和权限拼成一个文件而己,所以用另外如 gzip 格式压缩。为什么是 gzip,因为几乎所有 linux 都支持而已。

需要进一步的研究学习

暂无

遇到的问题

暂无

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

参考文献